package rodex;
/**
 * RodexApplication.java
 *
 *
 *
 * @author Duc M Le  <a href="mailto:dmle@doc.ic.ac.uk"><i>dmle@doc.ic.ac.uk</i></a>
 * Created on 09 March 2006
 * Department of Computing, Imperial College
 */
import java.io.IOException;
import uk.ac.ic.doc.automed.p2p.*;
import uk.ac.ic.doc.automed.*;
import uk.ac.ic.doc.automed.p2p.state.*;
import uk.ac.ic.doc.automed.util.Debugger;
import uk.ac.ic.doc.automed.util.Logger;
import uk.ac.ic.doc.automed.p2p.directory.P2PDirectory;
import uk.ac.ic.doc.automed.p2p.directory.PeerDescription;
import uk.ac.ic.doc.automed.reps.Model;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.rodex.p2p.gui.GuiApplication;
import uk.ac.ic.doc.automed.wrappers.AutoMedWrapper;
import uk.ac.ic.doc.automed.wrappers.AutoMedWrapperFactory;
import uk.ac.ic.doc.automed.wrappers.P2PWrapper;
import uk.ac.ic.doc.automed.wrappers.SQLWrapperFactory;
import uk.ac.ic.doc.automed.editor.Gui;
import uk.ac.ic.doc.automed.p2p.gui.PeerApplication;
import uk.ac.ic.doc.rodex.p2p.DirectoryServicePeer;
import uk.ac.ic.doc.rodex.p2p.Peer;
import uk.ac.ic.doc.automed.p2p.communication.PortInUseException;
import java.util.*;
import uk.ac.ic.doc.automed.p2p.AutoMedPeer.PeerProcessDescription;
import uk.ac.ic.doc.automed.reps.TransformationAbstraction;
import uk.ac.ic.doc.automed.util.LogOutputHandler;
import uk.ac.ic.doc.rodex.p2p.protocol.QEP;
import uk.ac.ic.doc.rodex.qproc.QuerySubGoal;
import utils.AutoMedUtils;

public class RodexApplication {
  private MediatorPeer pm;
  private DataPeer pd;
  private DSPeer pds;
  
  private String dspName = "<<D.S.P>>";
  private String pmName = "<<Pm>>";
  private String pdName = "<<Pd>>";
  private String remoteAddress = "127.0.0.1";
  private int peerDaemonPort = 0;
  private String protocol = "udp";
  private int kat = 2;    // keep-alive time period in secs
  
  private  String dsName = "university1";
  private  String srcSchemaName = "uni_s1_src";
  private  String schemaName = "uni_s1";
  private  String driver = "org.postgresql.Driver";
  private  String urlbase = "jdbc:postgresql://localhost/";
  private  String username = "admin";
  private  String password = "secret";
  
  private long sleepTime = 250;
  /**
   * Creates a new instance of RodexApplication
   */
  public RodexApplication() {
  }
  
  // the mediator peer
  public void runMediatorPeer() {
    // create a new peer
    new Thread() {
      public void run() {
//        while (pd == null || pd.getReadyState() == false) {
//          try {
//            Thread.sleep(2000);
//          } catch (InterruptedException ie) {
//            //
//          }
//        }
        System.out.println("\n++++++++++++++++++++++++++++++++++");
        System.out.println("Creating mediator peer " + pmName);
        System.out.println("++++++++++++++++++++++++++++++++++\n");
        // now start mediator peer
        pm = new MediatorPeer(pmName);
      }
    }.start();
  }
  
  // data provider peer
  public void runDataPeer() {
    System.out.println("\n++++++++++++++++++++++++++++++++++");
    System.out.println("Creating data peer " + pdName);
    System.out.println("++++++++++++++++++++++++++++++++++\n");
    
    new Thread() {
      public void run() {
        pd = new DataPeer(pdName);
      }
    }.start();
  }
  
  // directory service peer
  public void runDirectoryServicePeer() {
    System.out.println("\n++++++++++++++++++++++++++++++++++");
    System.out.println("Creating directory service peer " + dspName);
    System.out.println("++++++++++++++++++++++++++++++++++\n");
    
    new Thread() {
      public void run() {
        pds = new DSPeer(dspName);
      }
    }.start();
  }
  
  public DirectoryServicePeer runDirectoryServicePeer(String name) {
    AutoMedUtils.banner("Starting directory service peer " + dspName);
    pds = new DSPeer(dspName);
    return pds.getDSPeer();
  }
  
  // GUI application
  public void runGuiApplication(String peerName, PeerStateHandler stateHandler) {
    try {
      Peer guiPeer = null;
      if (peerName == null) peerName = STR.getIDString();
      if (peerName == null || stateHandler == null) {
        guiPeer = Peer.newPeer(peerName, new PeerStateHandler());
        guiPeer.setPeerState(PeerStateHandler.STATE_STAND_ALONE);
      } else {
        guiPeer = Peer.newPeer(peerName, stateHandler);
      }
      
      boolean standAlone = true;
      PeerApplication peerApp = new PeerApplication(guiPeer);
      //guiPeer.initMessagingPeer();
      guiPeer.initSICP();
      
      Gui gui = GuiApplication.openMainWindow(peerApp);
      
      if (guiPeer.getPeerState() >= PeerStateHandler.STATE_REGISTER_GLOBAL)
        peerApp.loggedIn();
      
      gui.setStandAlone(standAlone);
    } catch (AutoMedException ame) {
      ame.printStackTrace();
      System.exit(-1);
    }
  }
  
  public static void main(String args[]) throws Exception {
    // run peer depending on argument
    String peerType = null;
    
    String usage = "Usage: java -cp $CLASSPATH RodexApplication -t m|d|s|g [-d \"m.n (...)\"] [-c cfgFile]" +
      "\n\n  -t  peer type where m = mediator, d = data, s=directory service, g = GUI" +
      "\n   -d  one or more set of debug options in format section.level (if more than one then must escape with double quotes)" +
      "\n   -c  full path to the customised data source configuration file (default: $HOME/.automed/data_source_repository.cfg)" +
      "\n\n The data peer initialises a repository to wrap university_1 data source as uni_s1 and advertises it" +
      "\n to its directory service for mediator peer to wrap." +
      "\n To run both mediator and data peers, you must specify -c to use another data source configuration for at least one of these peers" +
      "\n\n Examples:" +
      "\n Run with default debug information:" +
      "\n -----------------------------------" +
      "\n java RodexApplication -t d " +
      "\n java RodexApplication -t m -c $HOME/.automed/data_source_repository_1.cfg" +
      "\n Run with detailed debug information:" +
      "\n ------------------------------------" +
      "\n java RodexApplication -t d -d \"0.5 3.8\"" +
      "\n java RodexApplication -t m -d \"0.5 3.9\" -c $HOME/.automed/data_source_repository_1.cfg";
    
    if (args == null || args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    } else {
      for (int i = 0; i < args.length; i++) {
        if (args[i].equals("-t")) {
          i++;
          peerType = (i < args.length) ? args[i] : null;
          break;
        }
      }
    }
    
    if (peerType == null || new String("mdsg").indexOf(peerType) < 0)  {
      System.out.println(usage);
      System.exit(0);
    }
    
    Debugger.setSectionDebugLevel(args);
    
    // initialise comms configuration file
    AppToolKit.getConfig(uk.ac.ic.doc.automed.p2p.protocol.P2PConstants.PEER_CFG_FILE);
    
    Logger.setOutputHandler(new LogOutputHandler(
      LogOutputHandler.CONSOLE + LogOutputHandler.FILE + LogOutputHandler.REMOTE));
    
    DSRConfiguration.setDefaultConfigFileName(args);
    RodexApplication peerApp = new RodexApplication();
    if (peerType.equals("d"))
      peerApp.runDataPeer();
    else if(peerType.equals("m"))
      peerApp.runMediatorPeer();
    else if(peerType.equals("s"))
      peerApp.runDirectoryServicePeer();
    else if(peerType.equals("g")) {
      peerApp.runGuiApplication(null, null);
    }
  }
  
  
  /**************************************************************************
   * Definitions of two peers for use in this application:<br>
   * <ul>
   *    <li>{@link MediatorPeer):   an {@link Peer} that serves as data integrator
   *    <li>{@link DataPeer}:       an {@link Peer} that provides data via public schemas
   * </ul>
   *
   * <p> The {@link DataPeer} instantiate a peer and a registry and a directory to serve requests<br>
   * coming from {@link MediatorPeer}. The instantiation of {@link MediatorPeer} is delayed until <br>
   * {@link DataPeer} is fully connected and a schema has been published<br>
   */
  
  class MediatorPeer {
    private Peer localPeer = null;
//    private KeepAliveMessageHandler keepAliveObj = null;
    private AutoMedMessagingHelper mesgHelper = null;
    
    /**
     * MediatorPeer acting as a pure peer (without registry process)
     */
    public MediatorPeer(String name) {
      try {
        if (AutoMedUtils.dialogYesNo("Re-initialise local repository?", 0) == 1) {
          AutoMedUtils.banner("Initialising peer repository "+DSRConfiguration.getDefaultConfigFileName()+"...");
          Peer.initRepository();
        }
        
        AutoMedUtils.banner("Initialising a new peer "+name+"...");
        localPeer = Peer.newPeer(name, new PeerStateHandler());
        
        // initialise the registry
        AutoMedUtils.banner("Initialising registry attached to peer "+name+"...");
        try {
          localPeer.initRegistry();
        } catch (PortInUseException piue) {
          // registry is already created locally? try to use it
          Logger.debug("MediatorPeer", "", "Registry seems to be available, using it instead!");
        }
        
        // start hello multicast...
        AutoMedUtils.banner("Initialising hello protocol for peer " + name);
        localPeer.initHelloProtocol();
        
        // wait until receiving hello ack from server ...
        System.out.print("Waiting for server's hello acknowledgement");
        while (localPeer.getPeerState() < PeerStateHandler.STATE_HELLO_COMPLETE) {
          try {
            Thread.sleep(sleepTime);
            System.out.print(".");
          } catch (InterruptedException ie) {
            //
          }
        }
        System.out.println("\n");
        
        AutoMedUtils.banner("Initialising authentication "+name+"...");
        localPeer.initAuthentication();
        localPeer.authenticate();
        
        // don't need to initialise standard messaging helpers because we will
        // create our own
        AutoMedUtils.banner("Initialising messaging service for peer "+name+"...");
        localPeer.initMessagingPeer();
        
        // data peer description to use
        PeerDescription peerDesc = new PeerDescription(pdName, remoteAddress);
        
        if (AutoMedUtils.dialogYesNo("GET public schema " + schemaName+ "?", 0) == 1) {
          AutoMedUtils.banner("MediatorPeer: Getting public schema "+schemaName+" from peer...");
          Logger.debug("MediatorPeer", "", "Getting schema from peer...");
          getSchema(peerDesc, schemaName);
        }
        
        if (AutoMedUtils.dialogYesNo("WRAP public schema " + schemaName+ "?", 1) == 1) {
          AutoMedUtils.banner("MediatorPeer: Wrapping public schema "+schemaName+" from peer...");
          wrapSchema(peerDesc, schemaName);
        }
        
        // initialise SICP protocol
        AutoMedUtils.banner("Initialising SICP protocol for " + name);
        localPeer.initSICP();
        
        // initialise QEP protocol
        AutoMedUtils.banner("Initialising QEP protocol for " + name);
        QEP qep = localPeer.getQEP();
        
        // try obtain abstract pathway to source
        if (AutoMedUtils.dialogYesNo("TEST getting abstract pathway for schema " + schemaName+ "?", 0) == 1) {
          AutoMedUtils.banner("Obtaining abstract pathways for schema "+schemaName+"...");
          List pwdescs = localPeer.requestAbstractPathToSource(schemaName);
          System.out.println("Received "+pwdescs.size()+ "abstract pathways:");
          for (int i = 0; i < pwdescs.size(); i++) {
            PathwayDescription pwdesc = (PathwayDescription) pwdescs.get(i);
            System.out.println("\t pw " + i + ": " +
              "\n\tdata source: " + pwdesc.getDataSource().getName() +
              "\n\t quality:" + pwdesc.getQuality() +
              "\n\t abstraction: \n" + pwdesc.getTransformations());
          }
        }
        
        
        // test querying the definition of a schema object
        String schemeStr = "uni_s1:<<staff,name>>";
        if (AutoMedUtils.dialogYesNo("TEST getting definition for schema object " + schemeStr+ "?", 0) == 1) {
          QuerySubGoal qsg = new QuerySubGoal(schemeStr);
          PeerProcessDescription ppd = new PeerProcessDescription(peerDesc.getURL(), peerDesc.getName());
          qep.requestSODefinition(qsg, ppd);
          
          // display details of each query
          List queryDefs = qsg.getDefQueries();
          System.out.println("Received the following query definitions:");
          for (Iterator it = queryDefs.iterator(); it.hasNext();) {
            TransformationAbstraction ta = (TransformationAbstraction) it.next();
            System.out.println(ta.debugString());
          }
        }
        
        // Get pathway to source
        if (AutoMedUtils.dialogYesNo("Get pathways to source for schema " + schemaName+ "?", 1) == 1) {
          AutoMedUtils.banner("Getting pathways to source attached to "+schemaName+"...");
          getPathwayToSource(peerDesc, schemaName);
        }
        
        
      } catch (AutoMedException ame) {
        ame.printStackTrace();
        System.exit(-1);
      } catch (IOException ioe) {
        ioe.printStackTrace();
        System.exit(-1);
      }
      
    }
    
    public void getSchema(PeerDescription peerDesc, String name) throws AutoMedException {
      //
      // get schema from peer
      SchemaGetter sgetter = new SchemaGetter(localPeer);
      try {
        sgetter.getSchema(name);
        Logger.debug(this.getClass(),"getSchema()", "Public schema "+
          name+" got from " + pdName);
      } catch (Exception e) {
        throw new AutoMedException("Failed to get public schema " + name + " from " + peerDesc.getName(), e);
      }
    }
    
    // wrapping a schema from directory
    public void wrapSchema(PeerDescription destPeerDesc, String name) throws AutoMedException {
      //
      try {
        AutoMedWrapper.selectNewAutoMedWrapper(destPeerDesc.getName(),"",
          null, P2PWrapper.class.getName(),
          "p2p:", name, null);
        
        Logger.debug(this.getClass(),"wrapSchema()", "Public schema "+
          name+" wrapped from " + pdName);
      } catch(Exception e) {
        throw new AutoMedException("Fail to wrap public schema "+
          name+" from " + pdName, e);
      }
    }
    
    // get all possible pathways to a data source
    public void getPathwayToSource(PeerDescription destPeerDesc, String schemaName)
    throws AutoMedException {
      try {
        new PathwayGetter(localPeer).
          getPathToSource(destPeerDesc.getName(), destPeerDesc.getURL(), schemaName);
        
        Logger.debug(this.getClass(),"getPathwayToSource()", "Paths to source on peer "+destPeerDesc.getName()+
          " were obtained");
      } catch(Exception e) {
        throw new AutoMedException("Failed to obtain paths to source on peer "+
          destPeerDesc.getName(), e);
      }
    }
  }
  
  /**
   * Directory service peer
   */
  class DSPeer {
    private DirectoryServicePeer localPeer = null;
    private P2PRegistry registry = null;
    
    private P2PDirectory directory = null;
    
    public DSPeer(String name) {
      try {
        /**@TODO Creates an error when another peer is trying to get pathways to source attached to this*/
//        AutoMedUtils.banner("Initialising peer repository "+DSRConfiguration.getDefaultConfigFileName()+"...");
//        Peer.initRepository();
        
        System.out.println("Initialising DS peer "+name+"...");
        localPeer = DirectoryServicePeer.getInstance(name, new PeerStateHandler());
        
        // initialise directory and its repository
//        if (AutoMedUtils.dialogYesNo("Re-initialise directory repository?", 0) == 1) {
//          AutoMedUtils.banner("Initialising directory repository attached to peer "+name+"...");
//          localPeer.initDirectoryRepository();
//        }
        
        // start multicast server, this must be done after initialising directory service
        AutoMedUtils.banner("Initialising hello protocol for peer " + name);
        localPeer.initHelloProtocol();
        
        // initialise SICP
        AutoMedUtils.banner("Initialising SICP protocol for " + name);
        localPeer.initSICP();
        
      } catch (AutoMedException ame) {
        ame.printStackTrace();
        System.exit(-1);
      }
    }
    
    public DirectoryServicePeer getDSPeer() {
      return localPeer;
    }
  }
  
  /**
   * DataPeer with registry process peers to join locally
   */
  class DataPeer {
    private Peer localPeer = null;
    private KeepAliveMessageHandler keepAliveObj = null;
    private AutoMedMessagingHelper mesgHelper = null;
    private P2PRegistry registry = null;
    
    public DataPeer(String name) {
      try {
        /**@TODO Creates an error when another peer is trying to get pathways to source attached to this*/
//        if (AutoMedUtils.dialogYesNo("Re-initialise local repository?", 0) == 1) {
        
//          AutoMedUtils.banner("Initialising peer repository "+DSRConfiguration.getDefaultConfigFileName()+"...");
//          Peer.initRepository();
//        }
        
        AutoMedUtils.banner("Initialising a new peer "+name+"...");
        localPeer = Peer.newPeer(name, new PeerStateHandler());
        
        // initialise the registry
        // initialise the registry
        AutoMedUtils.banner("Initialising registry attached to peer "+name+"...");
        try {
          registry = localPeer.initRegistry();
        } catch (PortInUseException piue) {
          // registry is already created locally? try to use it
          Logger.debug("MediatorPeer", "", "Registry seems to be available, using it instead!");
        }
        
        // start hello multicast...
        AutoMedUtils.banner("Initialising hello protocol for peer " + name);
        localPeer.initHelloProtocol();
        
        // wait until receiving hello ack from server ...
        System.out.print("Waiting for server's hello acknowledgement");
        while (localPeer.getPeerState() < PeerStateHandler.STATE_HELLO_COMPLETE) {
          try {
            Thread.sleep(sleepTime);
            System.out.print(".");
          } catch (InterruptedException ie) {
            //
          }
        }
        System.out.println("\n");
        
        AutoMedUtils.banner("Initialising authentication and wait to proceed...");
        localPeer.initAuthentication();
        
        AutoMedUtils.banner("Start authenticating peer "+name+"...");
        localPeer.authenticate();
        
        // don't need to initialise standard messaging helpers because we will
        // create our own
        AutoMedUtils.banner("Initialising messaging service for peer "+name+"...");
        localPeer.initMessagingPeer();
        
      } catch (AutoMedException ame) {
        ame.printStackTrace();
//        try {
//          localPeer.close();
//        } catch (AutoMedException amex) {
//          //
//        }
        System.exit(-1);
      }
      
      // operations start...
      try {
        if (AutoMedUtils.dialogYesNo("Wrap data source '"+urlbase+dsName+"'?", 1) == 1) {
          
          AutoMedUtils.banner("Wrapping "+urlbase+dsName+" data source...");
          Schema sqlSchema = wrapSQLDataSource(dsName, srcSchemaName, schemaName);
        }
        
        // must reload all models to refresh references b/w model objects
        Model.reloadModels();
        
        // initialise SICP protocol
        AutoMedUtils.banner("Initialising SICP protocol for " + name);
        localPeer.initSICP();
        
        // initialise QEP protocol
        AutoMedUtils.banner("Initialising QEP protocol for " + name);
        localPeer.getQEP();
        
        if (AutoMedUtils.dialogYesNo("Publish schema " +schemaName+"?", 1) == 1) {
          AutoMedUtils.banner("Publishing schema "+schemaName+"...");
          // get schema from peer
          SchemaPublisher spub = new SchemaPublisher(localPeer);
          spub.publishSchema(schemaName, "schema "+schemaName+" from " + name);
        }
        
      } catch (Exception e) {
        e.printStackTrace();
//        try {
//          localPeer.close();
//        } catch (AutoMedException ame) {
//          //
//        }
        System.exit(-1);
      }
    }
    
    // wrap a local data source
    public Schema wrapSQLDataSource(String dsName, String srcSchemaName, String schemaName)
    throws AutoMedException{
      AutoMedWrapperFactory wf = null;
      
      // Wrap the SQL schema from the UAV
      if (Schema.exists(srcSchemaName)) Schema.retract(srcSchemaName);
      if (Schema.exists(schemaName)) Schema.retract(schemaName);
      
      AutoMedWrapper sw=
        AutoMedWrapper.selectNewAutoMedWrapper(username,password,null,driver,
        urlbase+dsName,null,null);
      // Create a SQL wrapper factory
      wf=sw.getAutoMedWrapperFactory();
      wf.clearFeatures();
      wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
      wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
      wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
      
      Schema sourceSQLSchema=sw.getSchema(srcSchemaName);
      //sourceSQLSchema.listSchema();
      
      // Generate an AutoMed oriented schema
      Schema sqlSchema=sw.newAutoMedSchema(schemaName);
      
      //sqlSchema.listSchema();
      
//      Model.reloadModels();
      return sqlSchema;
    }
  }
  
  public static Schema wrapSQLDataSource(String driver, String urlbase, String dsName,
    String username, String password,
    String srcSchemaName, String schemaName,
    List domainSpec, int features)  throws AutoMedException {
    AutoMedWrapperFactory wf = null;
    
    // Wrap the SQL schema from the UAV
    if (Schema.exists(srcSchemaName)) Schema.retract(srcSchemaName);
    if (Schema.exists(schemaName)) Schema.retract(schemaName);
    
    AutoMedWrapper sw=
      AutoMedWrapper.selectNewAutoMedWrapper(username,password,null,driver,
      urlbase+dsName,null,null);
    // Create a SQL wrapper factory
    wf=sw.getAutoMedWrapperFactory();
    wf.clearFeatures();
//    wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
//    wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
//    wf.setFeatures(SQLWrapperFactory.DATA_TYPE);

    wf.setFeatures(features);
    
    if (domainSpec != null) {
      wf.setAttributeDomains(domainSpec);
    }
    
    Schema sourceSQLSchema=sw.getSchema(srcSchemaName);
    
    // Generate an AutoMed oriented schema
    Schema sqlSchema=sw.newAutoMedSchema(schemaName);
    
    // must reload all models to refresh references b/w model objects
    Model.reloadModels();

    return sqlSchema;
  }
  
  /**
   * Publish a public schema to the network
   */
  public static void publishSchema(Peer p, Schema s) throws AutoMedException, IOException {
    // initialise SICP protocol
    p.initSICP();
    
    String schemaName = s.getName();
    String name = p.getName();
    
    AutoMedUtils.banner("Publishing schema "+schemaName+"...");
    // get schema from peer
    SchemaPublisher spub = new SchemaPublisher(p);
    spub.publishSchema(schemaName, "schema "+schemaName+" from " + name);
  }
  
  public static Schema getSchema(Peer p, String name) throws AutoMedException {
    //
    // get schema from peer
    SchemaGetter sgetter = new SchemaGetter(p);
    try {
      Schema s = sgetter.getSchema(name);
      Logger.debug("","getSchema()", "Public schema "+
        name+" was obtained from network");
      
      return s;
    } catch (Exception e) {
      throw new AutoMedException("Failed to get public schema " + name + " from network", e);
    }
  }
  
}
