/*
 * BasicTreeServant.java
 *
 * Created on May 12, 2003, 11:16 AM
 */

package hep.aida.ref.remote.basic;

import java.util.*;

import hep.aida.*;
import hep.aida.ref.event.AIDAListener;
import hep.aida.ref.event.IsObservable;
import hep.aida.ref.event.HistogramEvent;
import hep.aida.ref.event.TreeEvent;
import hep.aida.ref.ManagedObject;
import hep.aida.ref.remote.basic.interfaces.AidaTreeClient;
import hep.aida.ref.remote.basic.interfaces.AidaTreeServer;
import hep.aida.ref.remote.basic.interfaces.AidaTreeServant;
import hep.aida.ref.remote.basic.interfaces.UpdateEvent;
import hep.aida.ref.tree.Tree;

/**
 * Basic implementation of the AidaTreeServant, no remote stuff.
 * @author  serbo
 */
public class BasicTreeServant extends Thread implements AidaTreeServant, AIDAListener {
    
    protected ITree tree;
    protected AidaTreeClient client;
    protected String clientID;
    protected boolean duplex;
    protected boolean keepRunning;
    protected Vector sources;
    protected ServerQueue queue;
    protected boolean useValidation;
    
    /** Creates a new instance of BasicTreeServant */
    public BasicTreeServant(ITree tree, String clientID) {
        System.out.println("BasicTreeServant() clientID="+clientID);
        this.tree = tree;
        this.clientID = clientID;
        this.client = null;
        duplex = false;
        init();
    }
    
    public BasicTreeServant(ITree tree, AidaTreeClient client) {
        System.out.println("BasicTreeServant() client="+client);
        this.tree = tree;
        this.clientID = null;
        this.client = client;
        duplex = true;
        init();
        this.start(); // Start thread that pushes updates into the client.
    }
    
    // Service methods
    
    protected void init() {
        sources = new Vector();
        queue = new ServerQueue();
        useValidation = true;
        keepRunning = duplex;
        if (tree instanceof IsObservable) {
            ((IsObservable) tree).addListener(this);
            sources.add(tree);
            ((IsObservable) tree).setValid(this);
        }
        if (tree instanceof hep.aida.ref.tree.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 void setUseValidation(boolean state) { useValidation = state; }
    
    public void close() {
        synchronized ( this ) {
            System.out.print("\n\tClosing BasicTreeServant ... "); 
            keepRunning = false;
            if (duplex) synchronized ( queue ) { queue.notify(); }
	    for (int i=0; i<sources.size(); i++) {
		IsObservable o = (IsObservable) sources.get(i);
		    if (o != null) o.removeListener(this);
	    }
	    sources.clear();
	    sources = null;
	    tree = null;
	    client = null;
	    queue.close();
	    queue = null;
            System.out.print(" Done\n");
	}
    }
    
    // AidaTreeServant methods
    
    public java.lang.Object find(String path) {
        IManagedObject mo = tree.find(path);
        //System.out.println("BasicTreeServant.find path="+path+",  mo="+mo);
        if (mo instanceof IsObservable && !sources.contains(mo)) {
            ((IsObservable) mo).addListener(this);
            sources.add(mo);
        }
        if (!useValidation && mo instanceof IsObservable) ((IsObservable) mo).setValid(this);
        return mo;
    }
    
    public String[] listObjectNames(String path) { 
        //System.out.println("BasicTreeServant.listObjectNames path="+path);
        if (tree instanceof hep.aida.ref.tree.Tree) 
            ((Tree) tree).setFolderIsWatched(path, true);
        return tree.listObjectNames(path); 
    }    

    public String[] listObjectTypes(String path) { 
        //System.out.println("BasicTreeServant.listObjectTypes path="+path);
        if (tree instanceof hep.aida.ref.tree.Tree) 
            ((Tree) tree).setFolderIsWatched(path, true);
        return tree.listObjectTypes(path); 
    }
    
    public void setValid(String[] paths) { 
        if (paths == null || paths.length == 0) return;
        for (int i=0; i<paths.length; i++) {
            if (paths[i] == null || paths[i].equals("") || paths[i].equals("/") ) {
                if (tree instanceof IsObservable) ((IsObservable) tree).setValid(this);
            } else {
                IManagedObject mo =  tree.find(paths[i]);
                if (mo instanceof IsObservable && !sources.contains(mo)) {
                    ((IsObservable) mo).addListener(this);
                    sources.add(mo);
                }
                //System.out.println("BasicTreeServant.setValid: name="+mo.name()+",  path="+paths[i]);
                if (mo instanceof IsObservable) ((IsObservable) mo).setValid(this);
            }
        }
    }
    
    /**
     * This method can be used only in non-Duplex mode. Never returns null.
     */
    public UpdateEvent[] updates() {
        UpdateEvent[] events = new UpdateEvent[0];
        if (!duplex) {
            events = queue.getEvents();
        }
        //System.out.println("BasicTreeServant.updates: return "+events.length+" events.");
        return events;
    }
    
    // AIDAListener methods
    
    /**
     * Create new UpdateEvent from the EventObject and put it in the queue
     * of current updates. This method is called by "IsObservable" sources
     * to report their change of state.
     */
    public void stateChanged(EventObject ev) {
        int id = -1;
        String pathString = "";
        String nodeType = "null";
        if (ev instanceof TreeEvent) {
            TreeEvent tev = (TreeEvent) ev;
            //System.out.println("BasicTreeServant.stateChanged GOT TreeEvent: id="+tev.getID()+", type="+
            //                    tev.getType()+", flags="+tev.getFlags()+
            //                    ", path="+(tev.getPath())[(tev.getPath()).length-1]);
            
            String[] path = tev.getPath();
            if (path != null) for (int i=0; i<path.length; i++) { pathString += "/" + path[i]; }
            
            if (tev.getType() != null)  nodeType = tev.getType().getName();
            if (tev.getFlags() == TreeEvent.FOLDER_MASK) nodeType = "dir";
            
            if ( tev.getID()== TreeEvent.NODE_ADDED) {
                id = UpdateEvent.NODE_ADDED;
                IManagedObject hist = tree.find(pathString);
                if (hist instanceof ManagedObject) nodeType = ((ManagedObject) hist).getAIDAType();
                else nodeType = hist.getClass().getName();
            }
            else if ( tev.getID()== TreeEvent.NODE_DELETED) id = UpdateEvent.NODE_DELETED;
            //else if ( tev.getID()== TreeEvent.NODE_RENAMED) id = UpdateEvent.NODE_UPDATED;
            else id = -1;
            
	    if (tree instanceof IsObservable) ((IsObservable) tree).setValid(this);
        } else if (ev instanceof HistogramEvent) {
            IBaseHistogram hist = (IBaseHistogram) ev.getSource();
            id = UpdateEvent.NODE_UPDATED;
            pathString = tree.findPath((IManagedObject) hist);
            if (hist instanceof ManagedObject) nodeType = ((ManagedObject) hist).getAIDAType();
            else nodeType = hist.getClass().getName();
            if (!useValidation && hist instanceof IsObservable) ((IsObservable) hist).setValid(this);
        }

        if (id >= 0) {
            queue.schedule((UpdateEvent) (new BasicUpdateEvent(id, pathString, nodeType)));
            //System.out.println("TreeServant: process Event: id = "+id+",  path = "+pathString+",  type = "+nodeType);
        }
    }
 
    // Thread methods
    public void run() {
        while (duplex && keepRunning) {
            int size = 0;
            UpdateEvent[] events = null;
            try {
                synchronized ( queue ) {
		    if(queue.size() == 0) queue.wait();
                    size = (queue == null) ? 0 : queue.size();
                    if (size > 0) events = queue.getEvents();
                }
                //System.out.println("ServerQueue.run Processing: "+size);
                if (events == null || events.length == 0) return;
                client.stateChanged(events);
           } catch (InterruptedException e2) {
                System.out.println("ServerQueue Thread InterruptedException.");
                e2.printStackTrace();
                
	    } catch (Exception e3) {
                System.out.println("Problems in ServerQueue!.");
                e3.printStackTrace();
            } // end of try/catch
        } // end of while
    }
    
    public static void main(String[] args) {
        java.util.Random r = new java.util.Random();
        IAnalysisFactory af = IAnalysisFactory.create();

        ITreeFactory tf = af.createTreeFactory();
        ITree serverTree = tf.create();
        ((IsObservable) serverTree).addListener(new TestBasic((Tree) serverTree));
        
        IHistogramFactory histogramFactory = af.createHistogramFactory(serverTree);
        
        int nEntries = 1000;
        int xbins = 10;
        double xLowerEdge = -10.;
        double xUpperEdge = 10.;

        serverTree.mkdir("/dir1");
        IHistogram1D h1 = histogramFactory.createHistogram1D("Hist-1",xbins,xLowerEdge,xUpperEdge);
        IHistogram1D h2 = histogramFactory.createHistogram1D("Hist-2",xbins,xLowerEdge,xUpperEdge);
        /* Fill the histogram */
        for (int i=0; i<nEntries; i++) {
            double xval = r.nextGaussian()*3+2.;
            h1.fill( xval );
            xval = r.nextGaussian()*3+2.;
            h2.fill( xval );
        }

        System.out.println("Creating TreeServant ...");
        BasicTreeServant servant = new BasicTreeServant(serverTree, "Test Servant");
        
        try {
            System.out.println("Servant is ready. To add Hist-3 press ENTER");
            System.in.read();

            IHistogram1D h3 = histogramFactory.createHistogram1D("Hist-1",xbins,xLowerEdge,xUpperEdge);

            System.out.println("To call \"updates()\" press ENTER");
            System.in.read();
            UpdateEvent[] ue = servant.updates();
            System.out.println("Got "+ue.length+"  events");
            for (int i=0; i<ue.length; i++)
                System.out.println("Event "+i+"   id="+ue[i].id()+"  path="+ue[i].path()+"   type="+ue[i].nodeType());
            
        }  catch(Exception e) {
            e.printStackTrace();
        }
    }
}
