/**
 * 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.Service;

/**
 * BioMoby services model implementation class.
 * The class provides "readonly" functionality providing at the same time a local cache support.
 * The cache is automatically updated.
 *
 * @author Dmitry Repchevsky
 */

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

    private boolean useCache;

    private Thread thread;
    private volatile boolean isThreadAlive;
    
    private MobyCentral central;
   
    private List<Service> services;

    private ServicesModel()
    {
        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 services from
     */
    public synchronized void setCentral(MobyCentral central)
    {
        stop();

        if (central == null)
        {
            this.central = central;
            services = new ArrayList<Service>();
        }
        else if (!central.equals(this.central))
        { 
            this.central = central;
            services = new ArrayList<Service>();

            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 get all services from the model
     *
     * @return - a list of BioMoby services
     */
    public synchronized List<Service> getElements()
    {
        while (thread != null)
        {
            try { wait(); }
            catch (InterruptedException ex) {}
        }

        return services;
    }

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

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

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

    private void stop()
    {
        if (thread != null)
        {
            isThreadAlive = false;
            notifyAll();

            while (thread != null)
            {
                try { wait(); }
                catch (InterruptedException ex) {}
            }
        }

        notifyCleared();
    }

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

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

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

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

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

        services = list;

        return true;
    }

    @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) {}

    @Override
    public void run()
    {
        try
        {
            if (!useCache || !loadFromCache())
            {
                MobyCentralOperations operations = MobyCentralFactory.getCentralDigest(central);
                Service template = new Service(); // empty service
                services = operations.findService(template, null, false, false);

                if (isThreadAlive)
                {
                    saveToCache();
                }
            }

            for (Service service : services)
            {
                notifyObjectInserted(service);
            }
        }
        catch(Throwable ex)
        {
            ex.printStackTrace();
        }

        synchronized(this)
        {
            thread = null;
            notifyAll(); // we've done!
        }
    }

   public static synchronized ServicesModel instance()
   {
       if (model == null)
       {
           model = new ServicesModel();
       }
       
       return model;
   }
}
