/*
 * BasicTreeClient.java
 *
 * Created on May 11, 2003, 6:46 PM
 */

package hep.aida.ref.remote.basic;

import java.io.IOException;
import java.util.*;

import hep.aida.dev.IDevMutableStore;
import hep.aida.ref.remote.basic.interfaces.AidaConnectionException;
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;

/**
 * This is Basic implementation of AidaTreeClient that support both "Duplex"
 * and "non-Duplex" modes of communication with the AIDA Tree server.
 * 
 * In "Duplex" mode AidaTreeServant call stateChanged() method to notify
 * BasicTreeClient about updates/changes in the server-side AIDA tree.
 *
 * In "non-Duplex" mode BasicTreeClient runs as a separate thread and
 * periodically calls updates() method of AidaTreeServant to get information
 * about updates/changes in the server-side AIDA tree.
 *
 * BasicTreeClient also implements IMutableStore, so it can be used as a
 * Store for any IDevTree.
 *
 * @author  serbo
 */
public class BasicTreeClient implements AidaTreeClient, Runnable {
    
    protected AidaTreeServer server;
    protected AidaTreeServant servant;
    protected IDevMutableStore store;
    protected UpdatableQueue queue;
    protected boolean duplex;
    protected boolean isConnected;
    protected int updateInterval;
    protected boolean keepUpdating;
    protected String clientID;
    
    private AidaTreeServer testServer; // Used for local tests only

    /** 
     * Creates a new instance of BasicTreeClient.
     * Duplex is default to "true". If AidaTreeServer does not support
     * Duplex mode, try to connect in non-Duplex.
     */
    public BasicTreeClient() {
        this(null, true);
    }
    
    public BasicTreeClient(IDevMutableStore store) {
        this(store, true);
    }
    
    public BasicTreeClient(IDevMutableStore store, boolean duplex) {
        init();
        this.store = store;
        this.duplex = duplex;
        this.keepUpdating = !duplex;
        queue = new UpdatableQueue();
    }
    
    /**
     * This constructor is used for local tests only.
     */
     BasicTreeClient(IDevMutableStore store, boolean duplex, AidaTreeServer server) {
        init();
        this.store = store;
        this.duplex = duplex;
        this.keepUpdating = !duplex;
        this.testServer = server;
    }
    
    
    // Service methods
    
    protected void init() {     
        server = null;
        servant = null;
        isConnected = false;
        updateInterval = 2000;
        clientID = "AidaTreeClient";
    }
    
    /**
     * Retrieves reference to the AidaTreeServer. Should be overwritten by 
     * subclasses.
     */
    protected AidaTreeServer getServer() {
        return testServer;
    }
    
    /**
     * Set time interval (in milliseconds) for AidaTreeClient to check for updates.
     * Relevant only for non-Duplex Mode, as in Duplex Mode updates are "pushed" 
     * into AidaTreeClient by AidaTreeServer calling "stateChanged()" method. 
     * Default value: updateInterval=500 milliseconds.
     */
    public synchronized void setUpdateTime(int updateInterval) {  
        this.updateInterval = updateInterval; 
    }
    
    /**
     * Set duplex mode. Default is "true".
     */
    public synchronized void setDuplex(boolean duplex) {  
        if (isConnected) {
            System.out.println("WARNING: Client is connected, can not change DUPLEX settings. "+
                               "Please disconnect first");
            return;
        }
        this.duplex = duplex;
        this.keepUpdating = !duplex;
    }
    
    /**
     * Retrieves Duplex AidaTreeServant from the AidaTreeServer.
     */
    protected void connectDuplex() throws AidaConnectionException {
        servant = server.connectDuplex(this);
        if (servant == null) {
            throw new AidaConnectionException("Can not retrieve non-Duplex AidaTreeServant from: "+server.treeName());
        }
        isConnected = true;
    }
    
    /**
     * Retrieves non-Duplex AidaTreeServant from the AidaTreeServer.
     */
    protected void connectNonDuplex() throws AidaConnectionException {
        servant = server.connectNonDuplex(clientID);
        if (servant == null) {
            throw new AidaConnectionException("Can not retrieve non-Duplex AidaTreeServant from: "+server.treeName());
        }
        //if (!clientID.equals(newClientID)) clientID = newClientID; 
        new Thread(this).start(); // Start asking servant for updates.
        isConnected = true;
    }
    

    // AidaTreeClient methods
    
     public String[] listObjectNames(String path) throws IllegalArgumentException {
        if (!isConnected) {
            System.out.println("WARNING: Client is not connected.");
            return null;
        }
        String[] names = servant.listObjectNames(path);
        return names;
     }

     public String[] listObjectTypes(String path) throws IllegalArgumentException {
        if (!isConnected) {
            System.out.println("WARNING: Client is not connected.");
            return null;
        }
        String[] types = servant.listObjectTypes(path);
        return types;
      }


     public Object find(String path) throws IllegalArgumentException {
        if (!isConnected) {
            System.out.println("WARNING: Client is not connected.");
            return null;
        }
        Object obj = servant.find(path);
        return obj;
      }

      /**
     * In this implementation stateChanged(UpdateEvent[] events) method simply
     * schedules updates in the UpdatableQueue. Later queue invokes stateChanged(UpdateEvent event)
     * method on a separate thread to process updates.
     */
    public void stateChanged(UpdateEvent[] events) {
        if (events != null && events.length > 0) {
            for (int i=0; i<events.length; i++) {
                queue.schedule(store, events[i]);
            }
        }
    }
    
    public boolean isConnected() {
        return isConnected;
    }  
    
    public boolean connect() throws AidaConnectionException {
        if (isConnected) {
            String name = "null";
            if (server != null) name = server.treeName();
            System.out.println("WARNING: Already connected to AidaTreeServer: "+ name);
            return false;
        }
        queue = new UpdatableQueue();
        server = getServer();
        System.out.println("Connecting:  duplex="+duplex);
        if (server == null) 
            throw new AidaConnectionException("Can not get reference to AidaTreeServer.");
        try {
            if (duplex) {
                boolean supportsDuplex = server.supportDuplexMode();
                if (!supportsDuplex) {
                    System.out.println("Warning: AidaTreeServer \""+server.treeName()+
                                        "\" does not support DUPLEX mode. \nWill try to connect using non-DUPLEX mode.");
                    duplex = false;
                    keepUpdating = !duplex;
                    connectNonDuplex();
                } else {
                    connectDuplex();
                }
            } else {
                connectNonDuplex();
            }
        } catch (AidaConnectionException ae) { 
            throw ae;
        } catch (Exception e) { 
            String name = "null";
            if (server != null) name = server.treeName();
            throw new AidaConnectionException("Can not connect to AidaTreeServer: "+name, e);
        }
        return true;
    }
    
    public boolean disconnect() {
        System.out.print("\nBasicTreeClient.disconnect: for Client="+clientID+" ... ");
        queue.close();
        queue = null;
        if (!isConnected) {
            keepUpdating = false;
            server = null;
            servant = null;
            return true;
        }
        boolean status = true;
        keepUpdating = false;
        if (server != null) {
            if (duplex) status = server.disconnectDuplex(this);
            else status = server.disconnectNonDuplex(clientID);
        }
        server = null;
        servant = null;
        System.out.print(" Done.\n");
        return status;
    }
    
    
    // Runnable methods
    
    public void run() {
        while (keepUpdating) {
            if (isConnected) {
                UpdateEvent[] events = servant.updates();
                //System.out.println("BasicTreeClient.run: GOT "+events.length+" events.");
                if (events != null && events.length > 0) stateChanged(events);
            }
	    try {
		Thread.sleep(updateInterval);
            } catch (InterruptedException ie) {
                System.out.println("AidaTreeClient non-DUPLEX Update Thread InterruptedException.");
                ie.printStackTrace();
                
	    } catch (Exception ex) { ex.printStackTrace(); }
	}
    }
        
    
    // Do some simple tests here
    public static void main(String[] args) {
    }
            
}
