/*
 * RemoteServant.java
 *
 * Created on October 22, 2003, 9:20 PM
 */

package hep.aida.ref.remote;

import java.util.Enumeration;
import java.util.Map;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.*;

import hep.aida.IBaseHistogram;
import hep.aida.IDataPointSet;
import hep.aida.IManagedObject;
import hep.aida.dev.IDevTree;
import hep.aida.ITree;

import hep.aida.ref.Annotation;
import hep.aida.ref.ManagedObject;
import hep.aida.ref.event.AIDAListener;
import hep.aida.ref.event.IsObservable;
import hep.aida.ref.event.DataPointSetEvent;
import hep.aida.ref.event.HistogramEvent;
import hep.aida.ref.event.TreeEvent;
import hep.aida.ref.tree.Tree;
import hep.aida.ref.remote.RemoteTree;
import hep.aida.ref.remote.interfaces.AidaTreeClient;
import hep.aida.ref.remote.interfaces.AidaTreeServant;
import hep.aida.ref.remote.interfaces.AidaTreeServer;
import hep.aida.ref.remote.interfaces.AidaUpdateEvent;

/**
 * This is implementation of the AidaTreeServant. It mainly deals with the
 * Tree and Tree Objects - change in Tree structure, updates of Tree Objects'
 * Data, etc.
 * This class does not have any remote transport layer (RMI, CORBA, etc.),
 * so special adapter classes, like RmiRemoteServant, are used to provide
 * such functionality.
 * The default for useValidation = true;
 *
 * @author  serbo
 */

public class RemoteServant implements AidaTreeServant, AIDAListener  {
    
    private IDevTree tree;
    private AidaTreeClient client;
    private String clientID;
    private boolean duplex;
    private boolean appendAxisType;
    private boolean keepRunning;
    private Vector sources;
    private Hashtable hash;
    private RemoteServerQueue eventQueue;
    protected boolean useValidation;
    protected Logger remoteLogger;
    
    /** Creates a new instance of RemoteServant */
    public RemoteServant(IDevTree tree, String clientID) {
        this.tree = tree;
        this.clientID = clientID;
        this.duplex = false;
        this.appendAxisType = false;
        eventQueue = new RemoteServerQueue();
        remoteLogger = Logger.getLogger("hep.aida.ref.remote");
        init();
    }
    
    public RemoteServant(IDevTree tree, AidaTreeClient client) {
        this.tree = tree;
        this.client = client;
        this.duplex = true;
        this.appendAxisType = false;
        eventQueue = new RemoteServerQueue(client);
        remoteLogger = Logger.getLogger("hep.aida.ref.remote");
        init();
    }
    
    
    // Service methods
    
    // If "true", append ":date" or ":double" to the object type
    public void setAppendAxisType(boolean a) { appendAxisType = a; }
    public boolean getAppendAxisType() { return appendAxisType; }
    
    protected void init() {
        sources = new Vector();
        hash = new Hashtable();
        useValidation = true;
        keepRunning = duplex;
        Object lock = tree.getLock();
        if (lock != null) {
            //synchronized (lock) {
            if (tree instanceof IsObservable) {
                ((IsObservable) tree).addListener(this);
                sources.add(tree);
                ((IsObservable) tree).setValid(this);
            }
            if (tree instanceof Tree)
                ((Tree) tree).setFolderIsWatched("/", true);
            //}
        } else {
            if (tree instanceof IsObservable) {
                ((IsObservable) tree).addListener(this);
                sources.add(tree);
                ((IsObservable) tree).setValid(this);
            }
            if (tree instanceof Tree)
                ((Tree) tree).setFolderIsWatched("/", true);
        }
    }
    
    
    /**
     * If useValidation = true, client has to call "setValid" method after
     * receiving update from the ManagedObject in order to reseive next update.
     * If useValidation = false, client receives all updates.
     */
    public synchronized void setUseValidation(boolean state) { useValidation = state; }
    
    
    /**
     * Close all connections and release all allocated resources.
     */
    public void close() {
        synchronized ( this ) {
            remoteLogger.fine("\n\tClosing RemoteServant ... ");
            keepRunning = false;
            eventQueue.close();
            Object lock = tree.getLock();
            if (lock != null) {
                //synchronized (lock) {
                if (tree instanceof IsObservable) {
                    ((IsObservable) tree).removeListener(this);
                }
                for (int i=0; i<sources.size(); i++) {
                    IsObservable o = (IsObservable) sources.get(i);
                    if (o != null) o.removeListener(this);
                }
                //}
            } else {
                if (tree instanceof IsObservable) {
                    ((IsObservable) tree).removeListener(this);
                }
                for (int i=0; i<sources.size(); i++) {
                    IsObservable o = (IsObservable) sources.get(i);
                    if (o != null) o.removeListener(this);
                }
            }
        }
        sources.clear();
        hash.clear();
        sources = null;
        hash = null;
        tree = null;
        client = null;
        eventQueue = null;
        remoteLogger.fine("  ... RemoteServant is closed\n");
    }
    
    
    public void setValid(String path) {
        if (path == null || path.equals("") || path.equals("/") ) {
            if (tree instanceof IsObservable) ((IsObservable) tree).setValid(this);
        } else {
            IManagedObject mo =  tree.find(path);
            if (!sources.contains(mo) && mo instanceof IsObservable) {
                ((IsObservable) mo).addListener(this);
                sources.add(mo);
            }
            if (mo instanceof IsObservable) {
                ((IsObservable) mo).setValid(this);
            }
        }
    }
    
    // AidaTreeServant methods
    
    /**
     * Just return an IManagedObject itself. Later the transport layer class
     * (like RmiRemoteServant) will have to extract the data from the IManagedObject
     * and send the data over the network.
     */
    public java.lang.Object find(String path) {
        remoteLogger.finest("RemoteServant.find for path="+path);
        IManagedObject mo =  tree.find(path);
        remoteLogger.finest("RemoteServant.find for path="+path+",  found MO="+mo.toString());
        
        if (mo instanceof IsObservable) {
            if (!sources.contains((IsObservable) mo)) {
                ((IsObservable) mo).addListener(this);
                sources.add((IsObservable) mo);
                hash.put(path, mo);
                if (!useValidation) ((IsObservable) mo).setValid(this);
                //if (useValidation) ((IsObservable) mo).setValid(this);
            }
        }
        return mo;
    }
    
    public String[] listObjectNames(String path) {
        remoteLogger.finest("RemoteServant.listObjectNames for path="+path);
        if (tree instanceof Tree) ((Tree) tree).setFolderIsWatched(path, true);
        String[] list =  tree.listObjectNames(path);
        /*
        if (list == null || list.length == 0) return list;
        if (tree instanceof Tree) {
            Tree tTree = (Tree) tree;
            for (int i=0; i<list.length; i++) {
                String objPath = list[i];
                if (!useValidation) {
                    IManagedObject mo =  tTree.findObject(objPath);
                    if (mo instanceof IsObservable) {
                        ((IsObservable) mo).setValid(this);
                    }
                }
            }
        }
         */
        return list;
    }
    
    public String[] listObjectTypes(String path) {
        remoteLogger.finest("RemoteServant.listObjectTypes for path="+path+",  appendAxisType="+appendAxisType);
        if (tree instanceof Tree) ((Tree) tree).setFolderIsWatched(path, true);
        String[] names =  tree.listObjectNames(path);
        String[] list  =  tree.listObjectTypes(path);
        if (list == null || list.length == 0) return list;
        
        if (tree instanceof Tree) {
            Tree tTree = (Tree) tree;
            for (int i=0; i<names.length; i++) {
                String objPath = names[i];
                IManagedObject mo =  tTree.findObject(objPath);
                if (appendAxisType && mo instanceof RemoteManagedObject) {
                    String xType = "double";
                    RemoteManagedObject rmo = (RemoteManagedObject) mo;
                    rmo.setFillable(true);
                    Annotation a = null;
                    if (rmo instanceof IBaseHistogram) {
                        try {
                            a = (Annotation) ((IBaseHistogram) rmo).annotation();
                            a.setFillable(true);
                            String tmp = a.value("xAxisType");
                            if (tmp != null && !tmp.equals("")) {
                                xType = tmp;
                                list[i] = list[i]+":"+xType;
                            }
                        } catch (IllegalArgumentException e) {}
                    } else if (rmo instanceof IDataPointSet) {
                        try {
                            a = (Annotation) ((IDataPointSet) rmo).annotation();
                            a.setFillable(true);
                            String tmp = a.value("xAxisType");
                            if (tmp != null && !tmp.equals("")) {
                                xType = tmp;
                                list[i] = list[i]+":"+xType;
                            }
                        } catch (IllegalArgumentException e) {}
                    }
                    if (a != null) a.setFillable(false);
                    rmo.setFillable(false);
                }
                if (!useValidation && mo instanceof IsObservable) {
                    ((IsObservable) mo).setValid(this);
                }
                
            }
        }
        return list;
    }
    
    public void setValid(String[] path) {
        if (path != null && path.length != 0) {
            for (int i=0; i<path.length; i++) {
                //System.out.println("RemoteServant.setValid: path["+i+"] = "+path[i]);
                setValid(path[i]);
            }
        }
    }
    
    public AidaUpdateEvent[] updates() {
        AidaUpdateEvent[] events = eventQueue.getEvents();
        return events;
    }
    
    
    // AIDAListener methods
    
    /**
     * Mainly this method translates Tree, DataPointSet, Histogram, etc.
     * event into AidaUpdateEvent. Also add event to the queue.
     */
    public void stateChanged(java.util.EventObject ev) {
        remoteLogger.finest("RemoteServant: got   Event: "+ev.toString());
        
        AidaUpdateEvent event = null;
        int id = -1;
        String pathString = "";
        String nodeType = "null";
        String xAxisType = "double";
        
        if (ev instanceof AidaUpdateEvent) {
            AidaUpdateEvent aev = (AidaUpdateEvent) ev;
            id = aev.id();
            pathString = aev.path();
            String nodeTypeString = aev.nodeType();
            nodeType = nodeTypeString;
            xAxisType = "double";
            
            // Parce out possible X Axis type
            int indexT = nodeTypeString.lastIndexOf(":");
            if (indexT > 0) {
                nodeType  = nodeTypeString.substring(0, indexT);
                String tmpType = nodeTypeString.substring(indexT+1);
                if (tmpType != null && !tmpType.equals("")) xAxisType = tmpType;
            } else if (event instanceof RemoteUpdateEvent) {
                String tmp = ((RemoteUpdateEvent) event).getXAxisType();
                if (tmp != null) xAxisType = tmp;
            }
            
        } else if (ev instanceof TreeEvent) {   //TreeEvent
            TreeEvent tev = (TreeEvent) ev;
            if (tev.getType() != null)  nodeType = tev.getType().getName();
            if (tev.getFlags() == TreeEvent.FOLDER_MASK) {
                nodeType = "dir";
            }
            
            String[] path = tev.getPath();
            if (path != null) for (int i=0; i<path.length; i++) { pathString += "/" + path[i]; }
            
            if (tev.getID() == TreeEvent.NODE_ADDED) {
                id = AidaUpdateEvent.NODE_ADDED;
            } else if (tev.getID() == TreeEvent.NODE_DELETED) {
                id = AidaUpdateEvent.NODE_DELETED;
            }
        } else if (ev instanceof HistogramEvent) {
            IBaseHistogram mo  = (IBaseHistogram) ev.getSource();
            try {
                String tmp = mo.annotation().value("xAxisType");
                if (tmp != null) xAxisType = tmp;
            } catch (IllegalArgumentException e) {}
            
            pathString = tree.findPath((IManagedObject) mo);
            id = AidaUpdateEvent.NODE_UPDATED;
            if (mo instanceof ManagedObject) nodeType = ((ManagedObject) mo).getAIDAType();
            else nodeType = mo.getClass().getName();
            //if (!useValidation && mo instanceof IsObservable) ((IsObservable) mo).setValid(this);
            
        } else if (ev instanceof DataPointSetEvent) {
            IDataPointSet mo = (IDataPointSet) ev.getSource();
            try {
                String tmp = mo.annotation().value("xAxisType");
                if (tmp != null) xAxisType = tmp;
            } catch (IllegalArgumentException e) {}
            
            pathString = tree.findPath((IManagedObject) mo);
            id = AidaUpdateEvent.NODE_UPDATED;
            if (mo instanceof ManagedObject) nodeType = ((ManagedObject) mo).getAIDAType();
            else nodeType = mo.getClass().getName();
            //if (!useValidation && mo instanceof IsObservable) ((IsObservable) mo).setValid(this);
        } else {  // Unknown Event
            remoteLogger.fine("RemoteServant.stateChanged Unknown Event: "+ev);
            return;
        }
        
        
        if (!pathString.equalsIgnoreCase("dir") && pathString.endsWith("/")) pathString = pathString.substring(0, pathString.length()-1);
        if (pathString.equalsIgnoreCase("dir") && !pathString.endsWith("/")) pathString += "/";
        
        if (id == AidaUpdateEvent.NODE_ADDED) {
            if (nodeType.equalsIgnoreCase("dir")) {
                // nothing to do here
            } else {
                try {
                    IManagedObject mo = tree.find(pathString);
                    try {
                        if ( mo instanceof IBaseHistogram) {
                            String tmp = ((IBaseHistogram) mo).annotation().value("xAxisType");
                            if (tmp != null) xAxisType = tmp;
                        } else if ( mo instanceof IDataPointSet) {
                            String tmp = ((IDataPointSet) mo).annotation().value("xAxisType");
                            if (tmp != null) xAxisType = tmp;
                        }
                    } catch (IllegalArgumentException e) { e.printStackTrace(); }
                    
                    if (mo instanceof ManagedObject) nodeType = ((ManagedObject) mo).getAIDAType();
                } catch (IllegalArgumentException ex) {
                    ex.printStackTrace();
                    //ITree mt = tree.findTree(pathString);
                    //String mn = mt.storeName();
                    //nodeType = "mnt";
                }
            }
        } else if (id == AidaUpdateEvent.NODE_DELETED) {
            
            if (nodeType.equalsIgnoreCase("dir")) {
                Enumeration en = hash.keys();
                while (en.hasMoreElements()) {
                    String tmpPath = (String) en.nextElement();
                    if (tmpPath.startsWith(pathString)) {
                        IsObservable mo = (IsObservable) hash.remove(pathString);
                        if (mo != null) {
                            ((IsObservable) mo).removeListener(this);
                            sources.remove(mo);
                        }
                    }
                }
            } else {
                IsObservable mo = (IsObservable) hash.remove(pathString);
                if (mo != null) {
                    ((IsObservable) mo).removeListener(this);
                    sources.remove(mo);
                }
            }
        }
        
        if (id < 0) {
            remoteLogger.fine("RemoteServant.stateChanged wrong event ID="+id);
            return;
        }
        
        if (!useValidation && tree instanceof IsObservable) ((IsObservable) tree).setValid(this);
        
        
        
        remoteLogger.finest("RemoteServant: process Event: id = "+id+",  path = "+pathString+
        ",  type = "+nodeType+",  xAxisType="+xAxisType);
        event = new RemoteUpdateEvent(id, pathString, nodeType, xAxisType);
        eventQueue.schedule(event);
    }
    
}
