package rodex;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import uk.ac.ic.doc.automed.AutoMedException;
import uk.ac.ic.doc.automed.DSRConfiguration;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.util.Debugger;
import uk.ac.ic.doc.automed.util.LogOutputHandler;
import uk.ac.ic.doc.automed.util.Logger;
import uk.ac.ic.doc.automed.util.db.DBAccessFactory;
import uk.ac.ic.doc.automed.wrappers.SQLWrapperFactory;
import uk.ac.ic.doc.rodex.p2p.Peer;
import uk.ac.ic.doc.rodex.reps.AttributeDomain;
import utils.AutoMedUtils;
import org.apache.commons.cli.*;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayException;

/**
 * DONWithSchema.java
 *
 * @author Duc M Le  <a href="mailto:dmle@doc.ic.ac.uk"><i>dmle@doc.ic.ac.uk</i></a>
 * Created on 23 August 2006
 * Department of Computing, Imperial College
 */

public class DONWithSchema extends EnhancedDON {
  
  private String schemaName = "person";
  private String srcSchemaName = "person_src";
  private Schema schema;
  
  /**
   * Creates a new instance of DONWithSchema
   */
  public DONWithSchema() throws Exception {
    super();
  }
  
  /**
   * Publish the public schema to the network
   */
  public void publishSchema(int peerIndex) throws Exception {
    // First try to publish public schema
    String driver = DBAccessFactory.DRIVER_POSTGRES;
    String dbName = getProperty("DB_NAME");
    String user = getProperty("DB_USER");
    String pwd = getProperty("DB_PWD");
    String urlBase = getProperty("URL_BASE");
    String db = urlBase+dbName;
    
    List domainSpec = new ArrayList();
    int NUM_SUB_RANGES_ID = Integer.parseInt(getProperty("NUM_SUB_RANGES_ID"));
    int DOMAIN_ID_MIN = Integer.parseInt(getProperty("DOMAIN_ID_MIN"));
    int DOMAIN_ID_MAX = Integer.parseInt(getProperty("DOMAIN_ID_MAX"));
    int NUM_SUB_RANGES_YOB = Integer.parseInt(getProperty("NUM_SUB_RANGES_YOB"));
    int DOMAIN_YOB_MIN = Integer.parseInt(getProperty("DOMAIN_YOB_MIN"));
    int DOMAIN_YOB_MAX = Integer.parseInt(getProperty("DOMAIN_YOB_MAX"));
    domainSpec.add(new AttributeDomain("id", DOMAIN_ID_MIN, DOMAIN_ID_MAX, NUM_SUB_RANGES_ID));
    domainSpec.add(new AttributeDomain("yob", DOMAIN_YOB_MIN, DOMAIN_YOB_MAX, NUM_SUB_RANGES_YOB));
    
    int features = SQLWrapperFactory.PRIMARY_KEY +
      SQLWrapperFactory.FOREIGN_KEY + SQLWrapperFactory.DATA_TYPE;
    
    Schema schema = RodexApplication.wrapSQLDataSource(driver, urlBase,
      dbName, user, pwd, srcSchemaName, schemaName, domainSpec, features);
    
    Peer p = getPeers()[peerIndex];
    try {
      RodexApplication.publishSchema(p, schema);
    } catch (AutoMedException ame) {
      Logger.error("DONWithSchema", "distributeTableData()", "Failed to publish" +
        " schema " + schema.getName(), ame);
      throw ame;
    } catch (IOException ioe) {
      Logger.error("DONWithSchema", "distributeTableData()", "Failed to publish" +
        " schema " + schema.getName(), ioe);
      throw ioe;
    }    
  }
  
  /**
   * Obtain the public schema from the network
   */
  public void getPublicSchema(int peerIndex) throws AutoMedException {
    Peer p = getPeers()[peerIndex];
    schema = RodexApplication.getSchema(p, schemaName);
    
    Logger.debug("DONWithSchema", "getPublicSchema()", "Schema "+schemaName+" has attribute domains: " +
      schema.getAttributeDomains());
  }
  
  /**
   * Extends the query-network method to check whether schema exists. Also 
   * query is reformulated using the schema
   */
  public void getValuesFromQuery(int peerIndex) throws OverlayException {  
    // check schema
    if (!Schema.exists(this.schemaName)) {
      out.println("Schema " + schemaName + " does not exist. Get it first.");
      return;
    }
    
    // reformulate query using the schema
    
    // execute query 
    super.getValuesFromQuery(peerIndex);
  }
  
  /**
   * Default command-line options
   */
  public static Options getDefaultOptions() {
    Options opts = new Options();
    opts.addOption(OptionBuilder.withArgName("numHosts")
                    .hasArg()
                    .withDescription("number of virtual hosts to create. E.g.: -n 5")
                    .isRequired(true)
                    .create("n"));
    opts.addOption(OptionBuilder.withArgName("scenarioFile")
                    .hasArg()
                    .withDescription("scenario file to use. E.g.: etc/DONScenario1.properties")
                    .isRequired(true)
                    .create("f"));
    opts.addOption(OptionBuilder.withArgName("startNumber")
                    .hasArg()
                    .withDescription("starting host number")
                    .isRequired(false)
                    .create("s"));
    opts.addOption("x", false, "without a directory service peer");
    opts.addOption("y", false, "does not start the AutoMed's non-structured peer service");
    opts.addOption(OptionBuilder.withArgName("host:port")
                    .hasArgs()
                    .withDescription("join DHT with an existing host on the specified port. " +
                      "E.g.: -j host5:8805")
                      .isRequired(false)
                      .create("j"));
    opts.addOption(OptionBuilder.withArgName("m.n (...)")
                    .hasArg()
                    .withDescription("one or more set of debug options in format section.level (if more " +
                      "than one then must enclose inside double quotes). E.g.: -d \"9.8 20.5 21.1\"")
                      .isRequired(false)
                      .create("d"));
    opts.addOption(OptionBuilder.withArgName("cfgFile")
                    .hasArg()
                    .withDescription("full path to the customised data source configuration file " +
                      "(default: $HOME/.automed/data_source_repository.cfg)")
                      .isRequired(false)
                      .create("c"));
    
    return opts;
  }
  
  /**
   * Main application entry point
   */
  public static void main(String[] args) throws Exception{
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_HELLO, Debugger.DEBUG_LEVEL_ALL);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_P2P, Debugger.DEBUG_LEVEL_STD);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_P2P_OVERLAY, Debugger.DEBUG_LEVEL_DETAILED);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_QUERY, Debugger.DEBUG_LEVEL_ALL);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_QUERY_REWRITE, Debugger.DEBUG_LEVEL_INFO);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_GENERAL, Debugger.DEBUG_LEVEL_INFO);
    // e.g. -d "9.8 20.5 21.1"
    
    Options opts = getDefaultOptions();
    CommandLineParser parser = new GnuParser();
    CommandLine cli = null;
    
    int numPeers = 5;
    int startNum = 0;
    String hostPort = null;
    boolean withoutDSP = false;
    String scenario = null;
    int[] actions = null;
    
    if (args == null || args.length < 1) {
      AutoMedUtils.printCmdUsage("DONWithSchema", opts);
      System.exit(0);
    } else {
      try {
        // parse the command line arguments
        cli = parser.parse( opts, args );
      } catch( ParseException exp ) {
        // oops, something went wrong
        System.err.println("Parsing failed.  Reason: " + exp.getMessage());
        System.exit(0);
      }
      
      if (!cli.hasOption("n") || !cli.hasOption("f")) {
        AutoMedUtils.printCmdUsage("DONWithSchema", opts);
        System.exit(0);
      }
      
      numPeers = Integer.parseInt(cli.getOptionValue("n"));
      scenario = cli.getOptionValue("f");
      actions = AutoMedUtils.readScenarioFile(scenario, DONWithSchema.class);
      
      if (cli.hasOption("s")) {
        startNum = Integer.parseInt(cli.getOptionValue("s"));
      }
      
      if (cli.hasOption("j")) {
        hostPort = cli.getOptionValue("j");
        if (hostPort != null && hostPort.length()==0)
          hostPort = null;
      }
      
      if (cli.hasOption("x")) {
        withoutDSP = true;
      }
    }
    
    Debugger.setSectionDebugLevel(args);
    DSRConfiguration.setDefaultConfigFileName(args);
    
//    Logger.setOutputHandler(new LogOutputHandler(LogOutputHandler.CONSOLE +
//      LogOutputHandler.FILE));
    
    Logger.setOutputHandler(new LogOutputHandler(LogOutputHandler.CONSOLE +
      LogOutputHandler.REMOTE));

    Peer.initOverlay(startNum, numPeers);
    
    DONWithSchema oex = null;
    boolean useRandomID = true;
    
    do {
      if (oex != null && AutoMedUtils.dialogYesNo("Start a new set of peers ?", 0) == 1) {
        oex.finalize();
        try {Thread.sleep(5 * 1000);} catch (InterruptedException ie) {}
        oex = new DONWithSchema();
        oex.startAutoMedPeers(numPeers, useRandomID, hostPort, !withoutDSP);
      } else if (oex == null) {
        oex = new DONWithSchema();
        oex.startAutoMedPeers(numPeers, useRandomID, hostPort, !withoutDSP);
      }
      
      // run pre-configured actions
      int peerIndex = -1;
      for (int i = 0; i < actions.length; i++) {
        switch (actions[i]) {
          case 1:
            if (AutoMedUtils.dialogYesNo("Display Overlay Network status ?", 0) == 1) {
              oex.displayOverlayStatus();
            }
            break;
          case 2:
            // prompt user for a peer
            if (peerIndex < 0) 
              peerIndex  = oex.displayPeerList();

            // Publish person table data to the network according to a given scheme
            if (AutoMedUtils.dialogYesNo("Publish person schema to network ?", 1) == 1) {
              oex.publishSchema(peerIndex);
            }
            
            break;
          case 3:
            // prompt user for a peer
            if (peerIndex < 0) 
              peerIndex  = oex.displayPeerList();

            // Publish person table data to the network according to a given scheme
            if (AutoMedUtils.dialogYesNo("Distribute person table data to network ?", 1) == 1) {
              oex.distributeTableData(peerIndex);
            }

            break;
          case 4:
            // prompt user for a peer
            if (peerIndex < 0) 
              peerIndex  = oex.displayPeerList();

            // Publish person table data to the network according to a given scheme
            if (AutoMedUtils.dialogYesNo("Obtain person schema from the network ?", 1) == 1) {
              oex.getPublicSchema(peerIndex);
            }

            break;
          case 5:
            // prompt user for a peer
            if (peerIndex < 0) 
              peerIndex  = oex.displayPeerList();

            // Query the network
            String testQuery = "[{x,y} | {x,y} <- <<person,yob>>; y > 1971; y <= 1979]";
            if (AutoMedUtils.dialogYesNo("Query the network ?", 1) == 1) {
              oex.getValuesFromQuery(peerIndex, testQuery);
            }            
            break;
          default:
            out.println("Action " + actions[i] + " is Not supported");
        }
      }
      
//      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": display overlay status ?", 1) == 1) {
//        oex.displayOverlayStatus(peerIndex);
//      }
//
//      // try query objects using exact match of record id
//      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": get values for a key from the network ?", 1) == 1) {
//        oex.getValuesForNumericKey(peerIndex);
//      }
//
//      while (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": try another key ?", 1) == 1) {
//        oex.getValuesForNumericKey(peerIndex);
//      }
//
//      // try query objects using query
//      while (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": query object ?", 1) == 1) {
//        oex.getValuesFromQuery(peerIndex);
//      }
      
    } while (AutoMedUtils.dialogYesNo("Run again ?", 0) == 1);
    
    AutoMedUtils.banner("~END~");
    System.exit(0);
  }
}
