/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.system.audit;

import icy.file.FileUtil;
import icy.gui.frame.progress.CancelableProgressFrame;
import icy.gui.main.MainFrame;
import icy.main.Icy;
import icy.network.NetworkUtil;
import icy.plugin.abstract_.Plugin;
import icy.preferences.ApplicationPreferences;
import icy.preferences.GeneralPreferences;
import icy.preferences.XMLPreferences;
import icy.system.IcyExceptionHandler;
import icy.system.SystemUtil;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;
import icy.util.XMLUtil;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * General audit tools class.
 * 
 * @author Stephane
 */
public class Audit
{
    // network URL
    static final String URL_REGISTER = NetworkUtil.WEBSITE_URL + "register/registerClient.php?";
    static final String URL_LINK_USER = NetworkUtil.WEBSITE_URL + "register/linkUser.php?";
    static final String URL_GET_USER_INFO = NetworkUtil.WEBSITE_URL + "register/getLinkedUserInfo.php?";
    static final String URL_AUDIT_VERSION = NetworkUtil.WEBSITE_URL + "register/auditVersion.php?";
    static final String URL_AUDIT_PLUGIN = NetworkUtil.WEBSITE_URL + "register/auditPlugin.php?";

    // prefs & network id
    static final String ID_REQUEST = "requestId";
    static final String ID_ACTION = "action";
    static final String ID_ICY_ID = "IcyId";
    static final String ID_CLIENT_ARCH = "clientArch";
    static final String ID_CLIENT_ID = "clientId";
    static final String ID_CLIENT_VERSION = "clientVersion";
    static final String ID_CLIENT_CPUNUMBER = "clientCpuNumber";
    static final String ID_CLIENT_TOTAL_MEMORY = "clientTotalMemory";
    static final String ID_CLIENT_MAXJAVA_MEMORY = "clientMaxJavaMemory";
    static final String ID_JAVA_NAME = "javaName";
    static final String ID_JAVA_VERSION = "javaVersion";
    static final String ID_JAVA_ARCH = "javaArch";
    static final String ID_LAST_UPLOAD_DATE = "lastUploadDate";

    // xml id
    static final String XMLID_CLIENT_ID_REQUESTED = "client_id_requested";
    static final String XMLID_USER_LOGIN = "user_login";
    static final String XMLID_USER_NAME = "user_name";

    // directly use application preferences here
    static XMLPreferences prefs;

    static AuditStorage storage;
    private static boolean initialized = false;
    private static boolean auditDone;

    /**
     * Audit process on application start.<br>
     * Check id, register...
     */
    public static synchronized void prepare()
    {
        if (initialized)
            return;

        // get preferences
        prefs = ApplicationPreferences.getPreferences();

        // probably a new installation --> need to reset id
        if (needToResetId())
        {
            // reset user info if needed
            unlinkUser();
            // reset id
            ApplicationPreferences.setId(-1);
        }

        // store current infos
        storeInfos();
        // init audit storage
        storage = new AuditStorage();

        final int id = ApplicationPreferences.getId();

        // id assigned ?
        if (id != -1)
        {
            final long currentTime = System.currentTimeMillis();
            final long dayInterval = 1000 * 60 * 60 * 24;

            // upload each 24 hours
            if (currentTime > (prefs.getLong(ID_LAST_UPLOAD_DATE, 0L) + dayInterval))
            {
                // save upload time whatever happened
                prefs.putLong(ID_LAST_UPLOAD_DATE, System.currentTimeMillis());

                // do that in background as it can take sometime if website does not reply...
                ThreadUtil.bgRun(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // upload usage statistics
                        storage.upload(id);
                    }
                });
            }
        }

        initialized = true;
        auditDone = false;
    }

    /**
     * Audit process on network connection
     */
    public static void onConnect()
    {
        prepare();

        if (!auditDone)
            processIdAudit();

        updateUserLink();

        final MainFrame frame = Icy.getMainInterface().getMainFrame();
        // refresh user infos
        if (frame != null)
            frame.refreshUserInfos();
    }

    /**
     * Save audit data
     */
    public static void save()
    {
        // save audit data
        if (initialized)
            storage.save();
    }

    /**
     * Plugin launched event audit
     */
    public static void pluginLaunched(Plugin plugin)
    {
        prepare();
        storage.pluginLaunched(plugin);
    }

    /**
     * Plugin instancied event audit
     */
    public static void pluginInstancied(Plugin plugin)
    {
        prepare();
        storage.pluginInstancied(plugin);
    }

    /**
     * Returns <code>true</code> if we need to reset the internal id (usually mean new installation)
     */
    private static boolean needToResetId()
    {
        if (!StringUtil.equals(ApplicationPreferences.getOs(), SystemUtil.getOSArchIdString()))
            return true;

        final int cpu = prefs.getInt(ID_CLIENT_CPUNUMBER, 0);
        final long mem = prefs.getLong(ID_CLIENT_TOTAL_MEMORY, 0);
        final String appFolder = ApplicationPreferences.getAppFolder();

        // ignore difference on first launch else it will regenerate id for everyone
        if ((cpu != 0) && (cpu != SystemUtil.getNumberOfCPUs()))
            return true;
        if ((mem != 0) && (mem != SystemUtil.getTotalMemory()))
            return true;
        if (!StringUtil.isEmpty(appFolder) && !StringUtil.equals(appFolder, FileUtil.APPLICATION_DIRECTORY))
            return true;

        return false;
    }

    /**
     * Store system and application informations in preferences
     */
    private static void storeInfos()
    {
        ApplicationPreferences.setOs(SystemUtil.getOSArchIdString());
        prefs.putInt(ID_CLIENT_CPUNUMBER, SystemUtil.getNumberOfCPUs());
        prefs.putLong(ID_CLIENT_TOTAL_MEMORY, SystemUtil.getTotalMemory());
        ApplicationPreferences.setAppFolder(FileUtil.APPLICATION_DIRECTORY);
    }

    private static void processIdAudit()
    {
        final Map<String, String> values = new HashMap<String, String>();
        final int id = ApplicationPreferences.getId();

        values.put(ID_CLIENT_ARCH, SystemUtil.getOSArchIdString());
        values.put(ID_CLIENT_VERSION, Icy.version.toString());

        // need to register
        if (id == -1)
        {
            // ask for registration
            values.put(ID_REQUEST, "1");

            final Document doc = XMLUtil.loadDocument(URL_REGISTER + NetworkUtil.getContentString(values));

            if (doc != null)
            {
                final Node root = XMLUtil.getRootElement(doc);
                final int newId = XMLUtil.getElementIntValue(root, XMLID_CLIENT_ID_REQUESTED, -1);

                // valid id --> save it
                if (newId != -1)
                    ApplicationPreferences.setId(newId);
            }
        }
        else
        {
            // just audit infos
            values.put(ID_CLIENT_ID, Integer.toString(id));

            values.put(ID_CLIENT_CPUNUMBER, Integer.toString(SystemUtil.getNumberOfCPUs()));
            values.put(ID_CLIENT_TOTAL_MEMORY, Long.toString(SystemUtil.getTotalMemory() / 10485760L));
            values.put(ID_CLIENT_MAXJAVA_MEMORY, Long.toString(SystemUtil.getJavaMaxMemory() / 10485760L));
            values.put(ID_JAVA_NAME, SystemUtil.getJavaName());
            values.put(ID_JAVA_VERSION, SystemUtil.getJavaVersion());
            values.put(ID_JAVA_ARCH, Integer.toString(SystemUtil.getJavaArchDataModel()));

            try
            {
                NetworkUtil.postData(URL_AUDIT_VERSION, values);
            }
            catch (IOException e)
            {
                // silent fail...
                // IcyExceptionHandler.showErrorMessage(e, false, false);
            }
        }

        auditDone = true;
    }

    private static Map<String, String> getIdParam()
    {
        final int id = ApplicationPreferences.getId();

        // id ok ?
        if (id != -1)
        {
            final Map<String, String> values = new HashMap<String, String>();

            // set id
            values.put(ID_ICY_ID, Integer.toString(id));

            return values;
        }

        return null;
    }

    public static void updateUserLink()
    {
        final Map<String, String> params = getIdParam();

        // id param ok ?
        if (params != null)
        {
            // and retrieve user infos
            final Document doc = XMLUtil.loadDocument(URL_GET_USER_INFO + NetworkUtil.getContentString(params));

            if (doc != null)
            {
                final Node root = XMLUtil.getRootElement(doc);

                // set attached user login and name
                GeneralPreferences.setUserLogin(XMLUtil.getElementValue(root, XMLID_USER_LOGIN, ""));
                GeneralPreferences.setUserName(XMLUtil.getElementValue(root, XMLID_USER_NAME, ""));
            }
        }
    }

    public static boolean isUserLinked()
    {
        return !StringUtil.isEmpty(GeneralPreferences.getUserLogin());
    }

    public static void linkUser()
    {
        final int id = ApplicationPreferences.getId();

        // id ok ?
        if (id != -1)
        {
            // launch browser with link identity request
            NetworkUtil.openBrowser(URL_LINK_USER + ID_ICY_ID + "=" + id + "&" + ID_ACTION + "=link");

            if (!Icy.getMainInterface().isHeadLess())
            {
                // display linking in progress
                new Thread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        final CancelableProgressFrame waitFrame = new CancelableProgressFrame(
                                "Waiting for user to link account...");

                        while (!Thread.interrupted() && !waitFrame.isCancelRequested())
                        {
                            try
                            {
                                Thread.sleep(1000);
                                updateUserLink();

                                // user linked !
                                if (isUserLinked())
                                {
                                    // stop wait
                                    waitFrame.cancel();

                                    // refresh user infos
                                    final MainFrame frame = Icy.getMainInterface().getMainFrame();
                                    if (frame != null)
                                        frame.refreshUserInfos();
                                }
                            }
                            catch (InterruptedException e)
                            {
                                waitFrame.cancel();
                            }
                        }

                        // close wait frame
                        waitFrame.close();
                    }
                }).start();
            }
        }
    }

    public static void unlinkUser()
    {
        final Map<String, String> params = getIdParam();

        // id param ok ?
        if (params != null)
        {
            // do that in background as it can take sometime...
            ThreadUtil.bgRun(new Runnable()
            {
                @Override
                public void run()
                {
                    // set action
                    params.put(ID_ACTION, "unlink");

                    try
                    {
                        // and post
                        NetworkUtil.postData(URL_LINK_USER, params);
                    }
                    catch (IOException e)
                    {
                        // can't unlink on web site, not a big deal...
                        System.err.print("Warning: cannot unlink online user infos.");
                        IcyExceptionHandler.showErrorMessage(e, false, false);
                    }
                }
            });
        }

        // reset attached user login and name
        GeneralPreferences.setUserLogin("");
        GeneralPreferences.setUserName("");
    }
}