/**
 * 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 java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * An abstract class for all BioMoby Registry models.
 * Any BioMoby Registry model extends this class to provide a basic functionality
 * such as an entity insertion/deletion/modification. The class also provides a
 * basic notification mechanism through event listeners.
 * 
 * @author Dmitry Repchevsky
 */

public abstract class AbstractModel<T>
{
    private CopyOnWriteArrayList<SoftReference<ModelListener<T>>> listeners;

    public abstract Collection<T> getElements();

    /**
     * Method returns a model element referenced by a provided one (using equals() method).
     *
     * @param element - a reference object to find a model element using an equals() method
     * @return - found model element or null
     */
    public T getElement(T element)
    {
        for (T t : getElements())
        {
            if (t.equals(element))
            {
                return t;
            }
        }

        return null;
    }

    /**
     * Method to add a model element.
     *
     * @param element - an element to be added to the model
     * @return - true if element has been added
     */
    public abstract boolean addElement(T element);

    /**
     * Method to remove a model element
     *
     * @param element - an element to be removed from the model
     * @return - true if element has been removed
     */
    public abstract boolean removeElement(T element);

    /**
     * A method that updates an element referred by element1 with data provided by element2
     * Note that updated element is not necesary the same object as elemen1, but those located in
     * an underlied collection. The method to find an updated element is a collection (and implementation)
     * specific.
     *
     * @param element1 - an object in a model to be updated
     * @param element2 - an object that used as a data source to updated object
     * @return - true if element has been updated
     */
    public abstract boolean updateElement(T element1, T element2);

    /**
     * Method to add a listener to be notified about a model changes.
     *
     * @param listener - a listener to be added to a list of listeners
     */
    public final void addListener(ModelListener<T> listener)
    {
        if (listeners == null)
        {
            listeners = new CopyOnWriteArrayList<SoftReference<ModelListener<T>>>();
        }
        
        listeners.add(new SoftReference(listener));

        listener.modelCleared();
        for (T element : getElements())
        {
            listener.modelObjectInserted(element);
        }
    }

    protected final void notifyCleared()
    {
        if (listeners != null)
        {
            Iterator<SoftReference<ModelListener<T>>> iter = listeners.iterator();

            while(iter.hasNext())
            {
                SoftReference<ModelListener<T>> ref = iter.next();

                ModelListener listener = ref.get();

                if (listener != null)
                {
                    listener.modelCleared();
                }
                else
                {
                    iter.remove();
                }
            }
        }
    }

    protected final void notifyObjectInserted(T object)
    {
        if (listeners != null)
        {
            Iterator<SoftReference<ModelListener<T>>> iter = listeners.iterator();

            while(iter.hasNext())
            {
                SoftReference<ModelListener<T>> ref = iter.next();

                ModelListener listener = ref.get();

                if (listener != null)
                {
                    listener.modelObjectInserted(object);
                }
                else
                {
                    iter.remove();
                }
            }
        }
    }

    protected final void notifyObjectRemoved(T object)
    {
        if (listeners != null)
        {
            Iterator<SoftReference<ModelListener<T>>> iter = listeners.iterator();

            while(iter.hasNext())
            {
                SoftReference<ModelListener<T>> ref = iter.next();

                ModelListener listener = ref.get();

                if (listener != null)
                {
                    listener.modelObjectRemoved(object);
                }
                else
                {
                    iter.remove();
                }
            }
        }
    }

    protected final void notifyObjectChanged(T object1, T object2)
    {
        if (listeners != null)
        {
            Iterator<SoftReference<ModelListener<T>>> iter = listeners.iterator();

            while(iter.hasNext())
            {
                SoftReference<ModelListener<T>> ref = iter.next();

                ModelListener listener = ref.get();

                if (listener != null)
                {
                    listener.modelObjectChanged(object1, object2);
                }
                else
                {
                    iter.remove();
                }
            }
        }
    }
}
