/**
 * 
 */
package icy.system.audit;

import icy.file.FileUtil;
import icy.file.xml.XMLPersistent;
import icy.file.xml.XMLPersistentHelper;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginDescriptor.PluginIdent;
import icy.plugin.PluginLoader;
import icy.plugin.abstract_.Plugin;
import icy.system.IcyExceptionHandler;
import icy.util.DateUtil;
import icy.util.XMLUtil;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.w3c.dom.Node;

/**
 * @author Stephane
 */
public class AuditStorage implements XMLPersistent
{
    private static final String ID_PLUGIN = "plugin";

    private static final String AUDIT_FILENAME = "icy_usage.xml";

    private static final long SAVE_INTERVAL = 1000 * 60;

    private final Map<PluginIdent, PluginStorage> pluginStats;
    private long lastSaveTime;

    public AuditStorage()
    {
        super();

        pluginStats = new HashMap<PluginIdent, PluginStorage>();

        try
        {
            // load usage data from XML file
            XMLPersistentHelper.loadFromXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
            // clean obsoletes data
            clean();
        }
        catch (Exception e)
        {
            System.out.println("Warning: can't reload usage statistics data.");
            IcyExceptionHandler.showErrorMessage(e, false, false);
        }

        lastSaveTime = System.currentTimeMillis();
    }

    public void save()
    {
        try
        {
            // save XML data
            XMLPersistentHelper.saveToXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
        }
        catch (Exception e)
        {
            System.out.println("Warning: can't save usage statistics data.");
            IcyExceptionHandler.showErrorMessage(e, false, false);
        }
    }

    private void clean()
    {
        final List<PluginIdent> empties = new ArrayList<PluginIdent>();

        synchronized (pluginStats)
        {
            // clean statistics
            for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
            {
                entry.getValue().clean();
                if (entry.getValue().isEmpty())
                    empties.add(entry.getKey());
            }

            // remove empty ones
            for (PluginIdent ident : empties)
                pluginStats.remove(ident);
        }
    }

    private void autoSave()
    {
        final long currentTime = System.currentTimeMillis();

        // interval elapsed
        if ((currentTime - lastSaveTime) > SAVE_INTERVAL)
        {
            // save statistics to disk
            save();
            lastSaveTime = currentTime;
        }
    }

    private PluginStorage getStorage(PluginIdent ident, boolean autoCreate)
    {
        PluginStorage result;

        synchronized (pluginStats)
        {
            result = pluginStats.get(ident);

            if ((result == null) && autoCreate)
            {
                result = new PluginStorage();
                pluginStats.put(ident, result);
            }
        }

        return result;
    }

    public void pluginLaunched(Plugin plugin)
    {
        final PluginDescriptor descriptor;

        if (plugin.isBundled())
            descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
        else
            descriptor = plugin.getDescriptor();

        // ignore if no descriptor
        if (descriptor == null)
            return;
        // ignore if missing version info
        if (descriptor.getVersion().isEmpty())
            return;
        // ignore kernel plugins
        if (descriptor.isKernelPlugin())
            return;

        // increment launch
        getStorage(descriptor.getIdent(), true).incLaunch(DateUtil.keepDay(System.currentTimeMillis()));

        // save to disk if needed
        autoSave();
    }

    public void pluginInstancied(Plugin plugin)
    {
        final PluginDescriptor descriptor;

        if (plugin.isBundled())
            descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
        else
            descriptor = plugin.getDescriptor();

        // ignore if no descriptor
        if (descriptor == null)
            return;
        // ignore if missing version info
        if (descriptor.getVersion().isEmpty())
            return;
        // ignore kernel plugins
        if (descriptor.isKernelPlugin())
            return;

        // increment instance
        getStorage(descriptor.getIdent(), true).incInstance(DateUtil.keepDay(System.currentTimeMillis()));
        // save to disk if needed
        autoSave();
    }

    /**
     * Upload statistics to website
     */
    public boolean upload(int id)
    {
        final List<PluginIdent> dones = new ArrayList<PluginIdent>();
        final List<Entry<PluginIdent, PluginStorage>> entries;

        synchronized (pluginStats)
        {
            entries = new ArrayList<Entry<PluginIdent, PluginStorage>>(pluginStats.entrySet());
        }

        try
        {
            for (Entry<PluginIdent, PluginStorage> entry : entries)
            {
                if (entry.getValue().upload(id, entry.getKey()))
                    dones.add(entry.getKey());

                // interrupt upload
                if (Thread.interrupted())
                    break;
            }
        }
        finally
        {
            // remove stats which has been correctly uploaded
            synchronized (pluginStats)
            {
                for (PluginIdent ident : dones)
                    pluginStats.remove(ident);
            }
        }

        return pluginStats.isEmpty();
    }

    @Override
    public boolean loadFromXML(Node node)
    {
        if (node == null)
            return false;

        synchronized (pluginStats)
        {
            pluginStats.clear();
            for (Node n : XMLUtil.getChildren(node, ID_PLUGIN))
            {
                final PluginIdent ident = new PluginIdent();
                final PluginStorage storage = new PluginStorage();

                ident.loadFromXMLShort(n);
                storage.loadFromXML(n);

                pluginStats.put(ident, storage);
            }
        }

        return true;
    }

    @Override
    public boolean saveToXML(Node node)
    {
        if (node == null)
            return false;

        XMLUtil.removeAllChildren(node);

        synchronized (pluginStats)
        {
            for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
            {
                final Node n = XMLUtil.addElement(node, ID_PLUGIN);

                entry.getKey().saveToXMLShort(n);
                entry.getValue().saveToXML(n);
            }
        }

        return true;
    }

    private class PluginStorage implements XMLPersistent
    {
        private static final String ID_CLASSNAME = PluginIdent.ID_CLASSNAME;
        private static final String ID_VERSION = PluginIdent.ID_VERSION;

        private static final String ID_LAUNCH = "launch";
        private static final String ID_INSTANCE = "instance";

        private static final String ID_STATS_LAUNCH = "stats_" + ID_LAUNCH;
        private static final String ID_STATS_INSTANCE = "stats_" + ID_INSTANCE;
        private static final String ID_DATE = "date";
        private static final String ID_VALUE = "value";

        private static final long DAY_TO_KEEP = 30L;

        private final Map<Long, Long> launchStats;
        private final Map<Long, Long> instanceStats;

        public PluginStorage()
        {
            super();

            launchStats = new HashMap<Long, Long>();
            instanceStats = new HashMap<Long, Long>();
        }

        public void clean()
        {
            List<Long> olds = new ArrayList<Long>();
            final long dayInterval = 1000 * 60 * 60 * 24;
            final long timeLimit = System.currentTimeMillis() - (DAY_TO_KEEP * dayInterval);

            // find obsoletes entries
            olds.clear();
            for (Long date : launchStats.keySet())
                if (date.longValue() < timeLimit)
                    olds.add(date);

            // remove them
            for (Long date : olds)
                launchStats.remove(date);

            // find obsoletes entries
            olds.clear();
            for (Long date : instanceStats.keySet())
                if (date.longValue() < timeLimit)
                    olds.add(date);

            // remove them
            for (Long date : olds)
                instanceStats.remove(date);
        }

        public boolean isEmpty()
        {
            return launchStats.isEmpty() && instanceStats.isEmpty();
        }

        public long getLaunch(Long date)
        {
            final Long result = launchStats.get(date);

            if (result == null)
                return 0L;

            return result.longValue();
        }

        public long getInstance(Long date)
        {
            final Long result = instanceStats.get(date);

            if (result == null)
                return 0L;

            return result.longValue();
        }

        public void incLaunch(long date)
        {
            final Long key = Long.valueOf(date);
            launchStats.put(key, Long.valueOf(getLaunch(key) + 1L));
        }

        public void incInstance(long date)
        {
            final Long key = Long.valueOf(date);
            instanceStats.put(key, Long.valueOf(getInstance(key) + 1L));
        }

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

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

                return values;
            }

            return null;
        }

        public boolean upload(int id, PluginIdent ident)
        {
            // init params
            final Map<String, String> params = getIdParam(id);
            int offset;

            // set plugin identity
            params.put(ID_CLASSNAME, ident.getClassName());
            params.put(ID_VERSION, ident.getVersion().toString());

            offset = 0;
            // build params for launch statistic
            for (Entry<Long, Long> entry : launchStats.entrySet())
            {
                // set date
                params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
                // set value
                params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
                offset++;
            }

            offset = 0;
            // build params for instance statistic
            for (Entry<Long, Long> entry : instanceStats.entrySet())
            {
                // set date
                params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
                // set value
                params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
                offset++;
            }

            try
            {
                // null return means website did not accepted them...
                if (NetworkUtil.postData(Audit.URL_AUDIT_PLUGIN, params) == null)
                    return false;
            }
            catch (IOException e)
            {
                return false;
            }

            // clear stats just to be sure to not send them twice
            launchStats.clear();
            instanceStats.clear();

            return true;
        }

        @Override
        public boolean loadFromXML(Node node)
        {
            if (node == null)
                return false;

            launchStats.clear();
            for (Node n : XMLUtil.getChildren(node, ID_LAUNCH))
            {
                final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
                final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);

                launchStats.put(Long.valueOf(date), Long.valueOf(value));
            }

            instanceStats.clear();
            for (Node n : XMLUtil.getChildren(node, ID_INSTANCE))
            {
                final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
                final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);

                instanceStats.put(Long.valueOf(date), Long.valueOf(value));
            }

            return true;
        }

        @Override
        public boolean saveToXML(Node node)
        {
            if (node == null)
                return false;

            for (Entry<Long, Long> entry : launchStats.entrySet())
            {
                final Node n = XMLUtil.addElement(node, ID_LAUNCH);

                XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
                XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
            }

            for (Entry<Long, Long> entry : instanceStats.entrySet())
            {
                final Node n = XMLUtil.addElement(node, ID_INSTANCE);

                XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
                XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
            }

            return true;
        }
    }

}