/**
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 * Copyright (C)
 * <a href="http://www.inab.org">Spanish National Institute of Bioinformatics (INB)</a>
 * <a href="http://www.bsc.es">Barcelona Supercomputing Center (BSC)</a>
 * <a href="http://inb.bsc.es">Computational Node 6</a>
 */

package org.inb.biomoby.central.model;

import org.inb.biomoby.central.cache.*;
import java.util.ArrayList;
import java.util.List;
import org.inb.biomoby.central.MobyCentral;
import org.inb.biomoby.central.MobyCentralFactory;
import org.inb.biomoby.central.MobyCentralOperations;
import org.inb.biomoby.shared.registry.Namespace;

/**
 * BioMoby namespaces model implementation class.
 *
 * @author Dmitry Repchevsky
 */

public class NamespacesModel extends AbstractModel<Namespace> implements ModelListener<MobyCentral>, Runnable
{
    private static NamespacesModel model;

    private boolean useCache;
    private volatile boolean isThreadAlive;

    private MobyCentral central;

    private List<Namespace> namespaces;

    private NamespacesModel()
    {
        MobyCentralModel.instance().addListener(this);
    }

    /**
     * Even the method declared public it is recommended to set BioMoby Registry globaly through a MobyCentralModel.
     *
     * @param central - BioMoby Registry to get the namespaces from
     */
    public synchronized void setCentral(MobyCentral central)
    {
        stop();

        if (central == null)
        {
            this.central = central;
            namespaces = new ArrayList<Namespace>();

            notifyCleared();
        }
        else if (!central.equals(this.central))
        {
            this.central = central;
            namespaces = new ArrayList<Namespace>();

            loadModel(CacheConfig.instance().isCached());
        }
    }

    /**
     * Method to reload the model from a BioMoby Registry (also updating a cache)
     */
    public synchronized void reload()
    {
        stop();

        if (central != null)
        {
            loadModel(false);
        }
    }

    /**
     * Method to find a BioMoby namespace object by its name
     *
     * @param namespace - a name of BioMoby namespace to be found
     * @return - a found namespace or null
     */
    public synchronized Namespace get(String namespace)
    {
        if(namespace != null)
        {
            List<Namespace> list = getElements();

            for (Namespace nmsp : list)
            {
                if (namespace.equals(nmsp.getName()))
                {
                    return nmsp;
                }
            }
        }
        return null;
    }

    /**
     * Method to get all namespaces from the model.
     *
     * @return - a list of BioMoby namespaces
     */
    public synchronized List<Namespace> getElements()
    {
        while(isThreadAlive)
        {
            try { wait(); }
            catch (InterruptedException ex) {}
        }

        return namespaces;
    }

    /**
     * Method not implemented and throws an exeption
     *
     * @param element - a new namespace to be registered with BioMoby Registry
     * @return - true if operation succeed
     */
    @Override
    public synchronized boolean addElement(Namespace element)
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Method to unregister a namespace from a BioMoby Registry.
     * Method is not implemented.
     *
     * @param element - namespace to be unregistered
     * @return - true if operation succeed
     */
    @Override
    public synchronized boolean removeElement(Namespace element)
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * A method that updates an element referred by element1 with data provided by element2.
     * Method is not implemented.
     *
     * @param element1 - an namespace in a model to be updated
     * @param element2 - an namespace that used as a data source to updated one
     * @return - true if operation succeed
     */
    @Override
    public synchronized boolean updateElement(Namespace element1, Namespace element2)
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Method to wait untill currently running thread finishes its work
     */
    private void stop()
    {
        while(isThreadAlive)
        {
            try { wait(); }
            catch (InterruptedException ex) {}
        }

        notifyCleared();
    }

    private void loadModel(boolean useCache)
    {
        this.useCache = useCache;

        isThreadAlive = true;
        Thread thread = new Thread(this);
        thread.setDaemon(true);
        thread.start();
    }

    private void saveToCache() throws Exception
    {
        CacheConfig.instance().save(namespaces, central, "MobyNamespaceList");
    }

    private boolean loadFromCache() throws Exception
    {
        List<Namespace> list = CacheConfig.instance().load(central, "MobyNamespaceList");

        if (list == null)
        { // no cache found
            return false;
        }

        namespaces = list;

        return true;
    }

    /**
     * An observer method that starts a model reloading once the default BioMoby Registry model has been changed
     */
    @Override
    public void modelCleared()
    {
        MobyCentral mobyCentral = MobyCentralModel.instance().getSelectedCentral();
        setCentral(mobyCentral);
    }

    @Override public void modelObjectInserted(MobyCentral mobyCentral) {}
    @Override public void modelObjectRemoved(MobyCentral mobyCentral) {}
    @Override public void modelObjectChanged(MobyCentral mobyCentral1, MobyCentral mobyCentral2) {}

    public void run()
    {
        try
        {
            if (!useCache || !loadFromCache())
            {
                MobyCentralOperations operations = MobyCentralFactory.getCentralDigest(central);

                namespaces = operations.retrieveNamespaces();

                saveToCache();
            }
        }
        catch(Throwable ex)
        {
            ex.printStackTrace();
        }

        synchronized(this)
        {
            isThreadAlive = false;
            notifyAll(); // we've done!
        }
    }

    /**
     * A singleton model creation method.
     *
     * @return - A NamespacesModel singleton
     */
    public static synchronized NamespacesModel instance()
    {
        if (model == null)
        {
            model = new NamespacesModel();
        }

        return model;
    }
}
