// CentralDigestImpl.java
//
//    senger@ebi.ac.uk
//    September 2004
//

package org.biomoby.client;

import org.biomoby.shared.CentralDigest;
import org.biomoby.shared.event.Notifier;
import org.biomoby.shared.event.NotificationEvent;
import org.biomoby.shared.event.NotificationListener;
import org.biomoby.shared.MobyDataType;
import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyService;
import org.biomoby.shared.MobyServiceType;
import org.biomoby.shared.MobyNamespace;

import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * A default implementation of {@link
 * org.biomoby.shared.CentralDigest} to get (also) cumulated data from
 * a Moby registry. <p>
 *
 * @author <A HREF="mailto:senger@ebi.ac.uk">Martin Senger</A>
 * @version $Id: CentralDigestImpl.java,v 1.14 2008/10/30 02:33:25 gordonp Exp $
 */

public class CentralDigestImpl
    extends CentralImpl
    implements CentralDigest, Notifier {

    private static org.apache.commons.logging.Log log =
       org.apache.commons.logging.LogFactory.getLog (CentralDigestImpl.class);

    // cached (digested) Moby
    protected MobyDataType[] dataTypes = new MobyDataType[] {};
    protected MobyServiceType[] serviceTypes = new MobyServiceType[] {};
    protected MobyService[] services = new MobyService[] {};
    protected MobyNamespace[] namespaces = new MobyNamespace[] {};

    protected boolean stopDT = false;
    protected boolean stopST = false;
    protected boolean stopS  = false;

    /*************************************************************************
     * Default constructor. It connects to a default Moby registry
     * (as defined in {@link #DEFAULT_ENDPOINT}) using a default namespace
     * (as defined int {@link #DEFAULT_NAMESPACE}).
     *************************************************************************/
    public CentralDigestImpl()
	throws MobyException {
	super();
    }

    /*************************************************************************
     * Constructor allowing to specify which Moby Registry to use.
     *************************************************************************/
    public CentralDigestImpl (String endpoint)
	throws MobyException {
	super (endpoint);
    }

    /*************************************************************************
     * Constructor allowing to specify which Moby Registry and what
     * namespace to use. If any of the parameters is null, its default
     * value is used instead.
     *************************************************************************/
    public CentralDigestImpl (String endpoint, String namespace)
	throws MobyException {
	super (endpoint, namespace);
    }

    /*************************************************************************
     * Physically gather together all data types.
     *************************************************************************/
    protected MobyDataType[] readDataTypes()
	throws MobyException {
	try {
	    Vector v = new Vector();
	    fireEvent (DATA_TYPES_START);
	    Map types = getDataTypeNames();
	    fireEvent (DATA_TYPES_COUNT, new Integer (types.size()));
	    for (Iterator it = types.entrySet().iterator(); it.hasNext(); ) {
		Map.Entry entry = (Map.Entry)it.next();
		String name = (String)entry.getKey();
		fireEvent (DATA_TYPE_LOADING, name);
		v.addElement (getDataType (name));
		fireEvent (DATA_TYPE_LOADED, name, v.lastElement());
		if (stopDT) {
		    v = new Vector();
		    break;
		}
	    }
	    MobyDataType[] result = new MobyDataType [v.size()];
	    v.copyInto (result);
	    return result;

	} catch (Exception e) {
	    throw new MobyException (formatException (e), e);
	} finally {
	    fireEvent (stopDT ? DATA_TYPES_CANCELLED : DATA_TYPES_END);
	    stopDT = false;
	}
    }

    /*************************************************************************
     * Physically gather together all service types.
     *************************************************************************/
    protected MobyServiceType[] readServiceTypes()
	throws MobyException {
	try {
	    Vector v = new Vector();
	    fireEvent (SERVICE_TYPES_START);
	    Map types = getServiceTypes();
	    fireEvent (SERVICE_TYPES_COUNT, new Integer (types.size()));
	    for (Iterator it = types.entrySet().iterator(); it.hasNext(); ) {
		Map.Entry entry = (Map.Entry)it.next();
		String typeName = (String)entry.getKey();
		fireEvent (SERVICE_TYPE_LOADING, typeName);
		MobyServiceType serviceType = new MobyServiceType (typeName);
		serviceType.setDescription ((String)entry.getValue());
		serviceType.setParentNames (getServiceTypeRelationships (typeName, false));
		v.addElement (serviceType);
		fireEvent (SERVICE_TYPE_LOADED, typeName, v.lastElement());
		if (stopST) {
		    v = new Vector();
		    break;
		}
	    }
	    MobyServiceType[] result = new MobyServiceType [v.size()];
	    v.copyInto (result);
	    return result;

	} catch (Exception e) {
	    throw new MobyException (formatException (e), e);
	} finally {
	    fireEvent (stopST ? SERVICE_TYPES_CANCELLED : SERVICE_TYPES_END);
	    stopST = false;
	}
    }

    /*************************************************************************
     * Physically gather together all namespaces. It is not that hard
     * (comparing to other Biomoby entities) because all namespaces
     * are delivered by a single call to Biomoby.
     *************************************************************************/
    protected MobyNamespace[] readNamespaces()
	throws MobyException {
	try {
	    fireEvent (NAMESPACES_START);
	    return super.getFullNamespaces();
	} finally {
	    fireEvent (NAMESPACES_END);
	}
    }

    /*************************************************************************
     * Physically gather together all service instances.
     *************************************************************************/
    protected MobyService[] readServices()
	throws MobyException {
	try {
	    Vector v = new Vector();
	    fireEvent (AUTHORITIES_START);
	    Map authorities = getServiceNamesByAuthority();
	    fireEvent (AUTHORITIES_COUNT, new Integer (authorities.size()));
	    for (Iterator it = authorities.entrySet().iterator(); it.hasNext(); ) {
		Map.Entry entry = (Map.Entry)it.next();
		String authority = (String)entry.getKey();
		fireEvent (AUTHORITY_LOADING, authority);
		String[] names = (String[])entry.getValue();
		for (int i = 0; i < names.length; i++) {
		    MobyService pattern = new MobyService (names[i], authority);
		    pattern.setCategory ("");
		    MobyService[] servs = findService (pattern);
		    for (int j = 0; j < servs.length; j++)
			v.addElement (servs[j]);
		}
		fireEvent (AUTHORITY_LOADED, authority);
		if (stopS) {
		    v = new Vector();
		    break;
		}
	    }
	    MobyService[] result = new MobyService [v.size()];
	    v.copyInto (result);
	    return result;

	} catch (Exception e) {
	    throw new MobyException (formatException (e), e);
	} finally {
	    fireEvent (stopS ? AUTHORITIES_CANCELLED : AUTHORITIES_END);
	    stopS = false;
	}
    }

    /*************************************************************************
     * Do more for NullPointerException...
     *************************************************************************/
    protected String formatException (Exception e) {
	if ( (e instanceof java.lang.NullPointerException) ||
	     (e instanceof java.lang.ClassCastException) ) {
	    StringBuffer buf = new StringBuffer();
	    buf.append (e.toString());
            StackTraceElement[] stes = e.getStackTrace();
	    for(int i = 0; i < stes.length; i++)
                buf.append (stes[i].toString()+"\n");
	    return new String (buf);
	} else if (e instanceof MobyException) {
	    return e.getMessage();
	} else {
	    return e.toString();
	}
    }

    /*************************************************************************
     *
     *************************************************************************/
    protected void message (String str) {
 	if (debug)
	    System.out.print (str);
    }
    /*************************************************************************
     *
     *************************************************************************/
    protected void messageLn (String str) {
 	if (debug)
	    System.out.println (str);
    }


    /*************************************************************************
     *
     * Methods re-writing the same methods from the superclass.
     *
     *************************************************************************/
    public void setCacheMode (boolean shouldCache) {
	super.setCacheMode (shouldCache);
	if ( ! getCacheMode() ) {
	    dataTypes = new MobyDataType[] {};
	    serviceTypes = new MobyServiceType[] {};
	    services = new MobyService[] {};
	}
    }

    /*************************************************************************
     *
     * Methods implementing CentralDigest interface.
     *
     *************************************************************************/

    /*************************************************************************
     *
     *************************************************************************/
    public MobyDataType[] getDataTypes()
	throws MobyException {
	synchronized (dataTypes) {
	    if (getCacheMode()) {
		if (dataTypes.length == 0)
		    dataTypes = readDataTypes();
		return dataTypes;
	    } else {
		return readDataTypes();
	    }
	}
    }

    /*************************************************************************
     *
     *************************************************************************/
    public MobyServiceType[] getFullServiceTypes()
	throws MobyException {
	synchronized (serviceTypes) {
	    if (getCacheMode()) {
		if (serviceTypes.length == 0)
		    serviceTypes = readServiceTypes();
		return serviceTypes;
	    } else {
		return readServiceTypes();
	    }
	}
    }

    /*************************************************************************
     *
     *************************************************************************/
    public MobyNamespace[] getFullNamespaces()
	throws MobyException {
	synchronized (namespaces) {
	    if (getCacheMode()) {
		if (namespaces.length == 0)
		    namespaces = readNamespaces();
		return namespaces;
	    } else {
		return readNamespaces();
	    }
	}
    }

    /*************************************************************************
     *
     *************************************************************************/
    public MobyService[] getServices()
	throws MobyException {
	synchronized (services) {
	    if (getCacheMode()) {
		if (services.length == 0)
		    services = readServices();
		return services;
	    } else {
		return readServices();
	    }
	}
    }

    /*************************************************************************
     *
     * Methods implementing Notifier interface.
     *
     *************************************************************************/

    private Vector listeners;

    /*********************************************************************
     *
     ********************************************************************/
    public synchronized void addNotificationListener (NotificationListener l) {
	if (listeners == null)
	    listeners = new Vector();
	listeners.addElement (l);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void addNotificationListeners (NotificationListener[] l) {
	for (int i = 0; i < l.length; i++)
	    addNotificationListener (l[i]);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void removeNotificationListener (NotificationListener l) {
	if (listeners != null)
	    listeners.removeElement (l);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void removeNotificationListeners (NotificationListener[] l) {
	for (int i = 0; i < l.length; i++)
	    removeNotificationListener (l[i]);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public NotificationListener[] getNotificationListeners() {
     	if (listeners == null)
	    return new NotificationListener[] {};
	synchronized (listeners) {
	    NotificationListener[] result = new NotificationListener [listeners.size()];
	    listeners.copyInto (result);
	    return result;
	}
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void callback (int signal) {
	switch (signal) {
	case SIGNAL_CANCEL_DATA_TYPES:
	    stopDT = true; break;
	case SIGNAL_CANCEL_SERVICE_TYPES:
	    stopST = true; break;
	case SIGNAL_CANCEL_SERVICES:
	    stopS = true; break;
	}
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void fireEvent (int type, Object message, Object details) {
	if (listeners == null) 
	    return;
	NotificationEvent event = null;
	if (details == null)
	    event = new NotificationEvent (this, type, message);
	else
	    event = new NotificationEvent (this, type, message, details);
	fireEvent (event);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void fireEvent (NotificationEvent event) {
	if (listeners != null) {
	    if (log.isDebugEnabled())
		log.debug (event.toString());
	    synchronized (listeners) {
		for (int i = 0; i < listeners.size(); i++) {
		    NotificationListener listener = (NotificationListener)listeners.elementAt(i);
		    listener.notified (event);
		}
	    }
	}
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void fireEvent (int type) {
	fireEvent (type, "", null);
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void fireEvent (int type, Object message) {
	fireEvent (type, message, null);
    }






    // Work in progress - implement the methods above by getting RDF
    // graphs in-one-go and parsing them. Actually ability to get such
    // graphs was the main motivation for this implementation, and for
    // the CentralDigest interface in the first place. But it has not
    // happen yet because the RDF graphs are not yet fully supported
    // by Moby registry (e.g. the object graph does not include HAS[A]
    // relationaships).

    // http://biomoby.org/RESOURCES/MOBY-S/Objects
    // http://biomoby.org/RESOURCES/MOBY-S/Services
    // http://biomoby.org/RESOURCES/MOBY-S/Namespaces
    // http://biomoby.org/RESOURCES/MOBY-S/ServiceInstances
    // http://biomoby.org/RESOURCES/MOBY-S/FULL


}
