package rodex;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import ow.id.ID;
import uk.ac.bbk.dcs.automed.qproc.iql.ASG;
import uk.ac.ic.doc.automed.AutoMedException;
import uk.ac.ic.doc.automed.DSRConfiguration;
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.NumberToolkit;
import uk.ac.ic.doc.automed.util.db.DBAccess;
import uk.ac.ic.doc.automed.util.db.DBAccessFactory;
import uk.ac.ic.doc.rodex.p2p.Peer;
import utils.AutoMedUtils;


/**
 * DTCDemoOverlay.java<br><br>
 *
 * Demonstrate the overlay data network feature by starting a physical overlay
 * peer which joins an existing overlay network
 *
 * @author Duc M Le  <a href="mailto:dmle@doc.ic.ac.uk"><i>dmle@doc.ic.ac.uk</i></a>
 * @version 1.0
 * <br>Department of Computing, Imperial College
 */

public class DTCDemoOverlay extends DONWithSchema {
  // my props file
  protected static final String PROPS_FILE = "etc/dtc_demo_overlay.properties";
  
  // peer names to use
  public static final String[] PEER_NAMES = new String[] {
      DTCDemo.pd1Name, DTCDemo.pd2Name
  };
  // sensor relational output configurations
  public static final String[][] SENSOR_DB_CFGS = new String[][] {
    // [peerName]     [tablename]      [index attributes]
    new String[] {
      PEER_NAMES[0], "databasedata"+PEER_NAMES[0],
      "databasedatanumcivilians,databasedatatime,databasedatanumvehicles,databasedatadamage",
      "databasedatatime,databasedatadamage"},
    new String[] {
      PEER_NAMES[1], "databasedata"+PEER_NAMES[1],
      "", ""},
  };
  
  // index attributes range definition
  protected static int[][] rgTime;
  protected static int[][] rgDamage;
  
  static {
    
    props = new Properties();
    
    try {
      props.load(DTCDemoOverlay.class.getResourceAsStream(PROPS_FILE));
      
      // populate PK range
      int NUM_SUB_RANGES_TIME = Integer.parseInt(getProperty("NUM_SUB_RANGES_TIME"));
      int DOMAIN_TIME_MIN = Integer.parseInt(getProperty("DOMAIN_TIME_MIN"));
      int DOMAIN_TIME_MAX = Integer.parseInt(getProperty("DOMAIN_TIME_MAX"));
      rgTime = NumberToolkit.createNumberRanges(DOMAIN_TIME_MIN,
        DOMAIN_TIME_MAX, NUM_SUB_RANGES_TIME);
      
      // populate index attribute range
      int NUM_SUB_RANGES_DAMAGE = Integer.parseInt(getProperty("NUM_SUB_RANGES_DAMAGE"));
      int DOMAIN_DAMAGE_MIN = Integer.parseInt(getProperty("DOMAIN_DAMAGE_MIN"));
      int DOMAIN_DAMAGE_MAX = Integer.parseInt(getProperty("DOMAIN_DAMAGE_MAX"));
      rgDamage = NumberToolkit.createNumberRanges(DOMAIN_DAMAGE_MIN,
        DOMAIN_DAMAGE_MAX, NUM_SUB_RANGES_DAMAGE);
    } catch (IOException ioe) {
      ioe.printStackTrace();
//      Logger.error(ioe);
    }
  }
  
  /**
   * Creates a new instance of DTCDemoOverlay
   */
  public DTCDemoOverlay() throws Exception {
    super();
  }
  
  /**
   * Default command-line options
   */
  public static Options getDefaultOptions() {
    Options opts = new Options();
    opts.addOption(OptionBuilder.withArgName("scenarioFile")
    .hasArg()
    .withDescription("scenario file to use. E.g.: etc/DONScenario1.properties")
    .isRequired(true)
    .create("f"));
    opts.addOption(OptionBuilder.withArgName("startHostIndex")
    .hasArg()
    .withDescription("the index of the first host (or this host if the number of hosts is 1)" +
      " on the network")
    .isRequired(false)
    .create("s"));
    opts.addOption("x", false, "start a directory service?");
    opts.addOption("y", false, "join an existing directory service?");
    opts.addOption("o", false, "start a test overlay network?");
    opts.addOption(OptionBuilder.withArgName("host:port")
    .hasArgs()
    .withDescription("join an existing host on the specified port (leave out if this " +
      "is the first host on the network) " +
      "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"));
    opts.addOption(OptionBuilder.withArgName("peerName")
    .hasArg()
    .withDescription("AutoMed name of this peer (E.g.: -m <<Pd1>>")
//    .isRequired(true)
    .create("m"));
    
    return opts;
  }
  
  /**
   * Override parent's method to distribute data for the sensor database
   */
  public void distributeTableData(int peerIndex) throws Exception {
    //identify the db cfg
    Peer p = getPeers()[peerIndex];
    String[] dbCfg = getPeerDBCfg(p.getName());
    if (dbCfg == null)
      throw new AutoMedException("Could not find db config for peer " + p.getName());
    
    String tblName = dbCfg[0];
    String attrStr = dbCfg[1];
    String indexAttrStr = dbCfg[2];
    String[] attrs = uk.ac.ic.doc.automed.Util.split(attrStr, ",");
    String[] indexAttrs = uk.ac.ic.doc.automed.Util.split(indexAttrStr, ",");
    
    // get the peer data from the relational source
    
    // distribute the data per each index attributes
    for (int i = 0; i < indexAttrs.length; i++) {
      distributeTableDataByAttribute(peerIndex, tblName,
        attrStr, indexAttrs[i]);
    }
  }
  
  public void distributeTableDataByAttribute(int peerIndex, String tbl,
    String tblFieldStr, String indexField) throws Exception {
    // access db and get database records
    /**@TODO: divide into multiple reads*/
    String driver = getProperty("DB_DRIVER");
    String dbName = getProperty("DB_NAME");
    String user = getProperty("DB_USER");
    String pwd = getProperty("DB_PWD");
    String db = getProperty("URL_BASE")+dbName;
    
    DBAccess dba = DBAccessFactory.getDBAccessInstance(driver, db, user, pwd, null);
    
    String sql = "SELECT * FROM "+tbl+" order by " + indexField;
    rs = dba.executeQuery(sql);
    
    List fields = new ArrayList();
    String[] tblFields = uk.ac.ic.doc.automed.Util.split(tblFieldStr, ",");
    for (int i = 0; i < tblFields.length; i++) {
      fields.add(tblFields[i]);
    }
    
    Vector v = null;
    
    int TTL = Integer.parseInt(getProperty("TTL"));
    
    // do with ID first
    String[] indexAttrs = new String[] { indexField };
    Map idHashStack = new HashMap();
    Peer p = super.getPeers()[peerIndex];
    while (rs.next()) {
      // get range guard for the specified attribute
      int[][] rgSet = getRangeGuardDef(indexField);
      int val = rs.getInt(indexField);
      
      int[][] inRG = getRangeGuard(val, rgSet);
      
      int rgMin = inRG[0][0];
      int rgMax = inRG[0][1];
      
      // get hash key for this range
      ID hid = (ID) idHashStack.get(rgMin+"");
      if (hid == null) {
        //hid = OverlayToolKit.createIDFromNumber(rgMin);
        hid = RodexDON.getHashKeyForRange(indexField, rgMin, rgMax);
        idHashStack.put(rgMin+"", hid);
      }
      
      // prepare record object to put into overlay network
      v = new Vector(tblFields.length);
      StringBuffer sbTblQuery = new StringBuffer();
      StringBuffer sbTblQuerySuffix = new StringBuffer();
      StringBuffer sbTuple = new StringBuffer();
      sbTblQuery.append("[{"+tblFieldStr+"} | ");
      
      sbTuple.append("[{");
      for (int j = 0; j < tblFields.length; j++) {
        Integer fldVal = new Integer(rs.getInt(tblFields[j]));
        v.add(fldVal);
        sbTblQuery.append("{t,"+tblFields[j]+"} <- <<"+tbl+","+tblFields[j]+">>;");
        sbTblQuerySuffix.append(tblFields[j]).
          append(" = ").append(fldVal).append((j < tblFields.length - 1) ? ";" : "");
        sbTuple.append(fldVal).append((j < tblFields.length - 1) ? "," : "");
      }
      sbTblQuerySuffix.append("]");
      sbTuple.append("}]");
      
      ASG tableQuery = null, tuple = null;
//      try {
      tableQuery = new ASG(sbTblQuery.append(sbTblQuerySuffix).toString());
      tuple = new ASG(sbTuple.toString());
//      } catch (ParseException ex) {
//        Logger.error("DTCDemoOverlay","distributeTableData()", ex.getMessage(), ex);
//        throw ex;
//      }
      Logger.debug("DTCDemoOverlay","distributeTableDataByAttribute()",
        "Peer "+p.getName()+": Start distributing record with attribute: " +
        indexField + " = " + val +
        ((Debugger.atSectionDebugLevel(Debugger.DEBUG_QUERY,
        Debugger.DEBUG_LEVEL_DETAILED)) ?
          "\n\tASG query: "+ tableQuery.toString() +
        "\n\tASG tuple: " + tuple.toString() : ""),
        debugSection, debugLevel);
      
      distributeDBRecord(peerIndex, tableQuery, tuple, hid, TTL);
    }
  }
  
  public int[][] getRangeGuardDef(String field) {
    if (field.equalsIgnoreCase("databasedatatime")) {
      return rgTime;
    } else if (field.equalsIgnoreCase("databasedatadamage")) {
      return rgDamage;
    }
    // add more cases here...
    
    return null;
  }
  
  /**
   * Get peer db cfg
   */
  private String[] getPeerDBCfg(String peerName) {
    String[] dbCfg = new String[3];
    boolean found = false;
    for (int i = 0; i < SENSOR_DB_CFGS.length; i++) {
      String[] cfgItem = SENSOR_DB_CFGS[i];
      if(cfgItem[0].equalsIgnoreCase(peerName)) {
        found = true;
        dbCfg[0] = cfgItem[1]; // table name
        dbCfg[1] = cfgItem[2]; // attributes
        dbCfg[2] = cfgItem[3]; // index attributes
        break;
      }
    }
    
    return (found) ? dbCfg : null;
  }
  
  public static String getPeerNames() {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < PEER_NAMES.length; i++) {
      sb.append("'").append(PEER_NAMES[i]).append("'").append(" ");
    }
    
    return sb.toString();
  }
  
  /**
   * Main application entry point
   */
  public static void main(String[] args) throws Exception{
    
    // command line options
    Options opts = getDefaultOptions();
    CommandLineParser parser = new GnuParser();
    CommandLine cli = null;
    
    int testNetworkPeers = 10;
    
    int numPeers = 1;
    int startNum = 0;
    String hostPort = null;
    boolean withoutDSP = false;
    boolean joinDS = true;
    String scenario = null;
    int[] actions = null;
    String peerName = null;
    boolean testNetwork = false;
    
    if (args == null || args.length < 1) {
      AutoMedUtils.printCmdUsage("DTCDemoOverlay", 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("f") ||
        (!cli.hasOption("o") && !cli.hasOption("m"))) {
        out.println("ERROR: When starting a peer, you must specify " +
          " the peer's name. \n Valid names are: " + getPeerNames());
        AutoMedUtils.printCmdUsage("DTCDemoOverlay", opts);
        System.exit(0);
      }
      
      scenario = cli.getOptionValue("f");      
      actions = AutoMedUtils.readScenarioFile(scenario, DTCDemoOverlay.class);
      
      peerName = cli.getOptionValue("m");
      
      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;
      }
      
      if (cli.hasOption("y")) {
        joinDS = false;
      }
      
      if (cli.hasOption("o")) {
        testNetwork = true;
      }
    }
    
    Debugger.setSectionDebugLevel(args);
    DSRConfiguration.setDefaultConfigFileName(args);
    
    Logger.setOutputHandler(new LogOutputHandler(LogOutputHandler.CONSOLE +
      LogOutputHandler.FILE));
    
    boolean useRandomID = false;
    DTCDemoOverlay dtco = null;
    
    int peerIndex = startNum;
    
    if (testNetwork) {  // run test network
      AutoMedUtils.banner("Starting a test overlay network with " + testNetworkPeers + " peers...");
      Peer.initOverlay(startNum, testNetworkPeers);
      dtco = new DTCDemoOverlay();
      dtco.startAutoMedPeers(testNetworkPeers, useRandomID, hostPort, !withoutDSP, joinDS);
      
      do {
        for (int i = 0; i < actions.length; i++) {
          switch (actions[i]) {
            case 1:
              if (AutoMedUtils.dialogYesNo("Display Overlay Network status ?", 0) == 1) {
                dtco.displayOverlayStatus();
              }
              break;
            case 5:
              // Query the network to get data
              if (peerIndex < 0)
                peerIndex  = dtco.displayPeerList();
              
              // Query the network
              String testQuery = "[{x,y} | {x,y} <- <<databasedata,databasedatatime>>; y >=8 ; y <= 9]";
              if (AutoMedUtils.dialogYesNo("Query the network ?", 1) == 1) {
                dtco.getValuesFromQuery(peerIndex, testQuery);
              }
              break;
            case 10:
              // Display a peer status
              if (AutoMedUtils.dialogYesNo("Display peer status ?", 1) == 1) {
              
                peerIndex  = dtco.displayPeerList();

                dtco.displayOverlayStatus(peerIndex);
              }
              break;
            default:
              out.println("Action " + actions[i] + " is Not supported");
          }
        }
      } while (AutoMedUtils.dialogYesNo("Run again ?", 0) == 1);
    }
    // END test network
    else { // run one peer
      AutoMedUtils.banner("Starting an overlay peer with name '" + peerName + "' ...");
      Peer.initOverlay(startNum, numPeers);
      
      peerIndex = 0;
      
      do {
        if (dtco != null && AutoMedUtils.dialogYesNo("Start a new peer ?", 0) == 1) {
          dtco.finalize();
          dtco = new DTCDemoOverlay();
          dtco.startAutoMedPeers(startNum, numPeers, useRandomID, hostPort, !withoutDSP, joinDS);
        } else if (dtco == null) {
          dtco = new DTCDemoOverlay();
          dtco.startAutoMedPeers(startNum, numPeers, useRandomID, hostPort, !withoutDSP, joinDS);
        }
        
        dtco.getPeers()[peerIndex].setName(peerName);
        
        for (int i = 0; i < actions.length; i++) {
          switch (actions[i]) {
            case 1:
              if (AutoMedUtils.dialogYesNo("Display Overlay Network status ?", 0) == 1) {
                dtco.displayOverlayStatus();
              }
              break;
            case 3:
              // distribute data to the network
              if (AutoMedUtils.dialogYesNo("Distribute my local sensor data to the network ?", 1) == 1) {
                dtco.distributeTableData(peerIndex);
              }
              
              break;
            case 5:
              // Query the network
              String testQuery = "[{x,y} | {x,y} <- <<databasedata,databasedatatime>>; y >=8 ; y <= 9]";
              if (AutoMedUtils.dialogYesNo("Query the network ?", 1) == 1) {
                dtco.getValuesFromQuery(peerIndex, testQuery);
              }
              break;
            default:
              out.println("Action " + actions[i] + " is Not supported");
          }
        }
      } while (AutoMedUtils.dialogYesNo("Run again ?", 0) == 1);
    }
    // END DTCDemo overlay peer
    
    AutoMedUtils.banner("~END~");
    System.exit(0);    }
}
