package rodex;

import basic.QueryCacheExample;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
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.DSRConfiguration;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayController;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayToolKit;
import uk.ac.ic.doc.automed.qproc.cache.CacheManager;
import uk.ac.ic.doc.automed.qproc.cache.IQueryCache;
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.wrappers.AutoMedType;
import uk.ac.ic.doc.rodex.p2p.Peer;
import uk.ac.ic.doc.rodex.qproc.QueryAnalyser;
import uk.ac.ic.doc.rodex.qproc.QueryRewriteProcessor;
import uk.ac.ic.doc.rodex.qproc.QueryRewriterResult;
import utils.AutoMedUtils;

/**
 * BasicCON.java
 *
 * This application adapts the {@link EnhancedDON} application for
 * the cache overlay network. The primary objectives are to:
 * <ul>
 *  <li> rewrite each cached query into a set of sub-queries based on the ranges of the
 *      data set associated to that query
 *  <li> break the query's tuple set into an equivalent set of tuple sub-sets
 *  <li> publish the tuple sub-sets of each query to the network
 * </ul>
 *
 * <br> The tuple sub-sets are distributed in such a way that ensures a balanced
 * <b>load distribution</b> and data <b>randomness</b>
 *
 * @author Duc M Le  <a href="mailto:dmle@doc.ic.ac.uk"><i>dmle@doc.ic.ac.uk</i></a>
 * @version 1.0
 * Department of Computing, Imperial College
 */

public class BasicCON extends EnhancedDON {
  QueryCacheExample _qcex = null;
  
  private Map rangeDefs;
  private QueryRewriterResult result = null;
  
  /**
   * Creates a new instance of BasicCON
   */
  public BasicCON() throws Exception {
    super();
    rangeDefs = new HashMap();
  }
  
  /**
   * Distribute cache data to the network
   */
  public void distributeCacheData(int peerIndex) throws Exception {
    Peer p = getPeers()[peerIndex];
    IQueryCache cache = CacheManager.getQueryCache();
    
    // first rewrite each cache query using range-based method    
    while (cache.hasNext()) {
      
      QueryAnalyser qa = (QueryAnalyser) cache.next();
      
      if (qa == null) { // only consider queries that have been analysed
        Logger.debug("","","Peer "+p.getName()+": this entry does not have a query analyser",
          debugSection, Debugger.DEBUG_LEVEL_DETAILED);
        continue;
      }
      
      String query = qa.getQueryString();
      ASG[] extents = cache.getQuery(query);
      
      if (extents == null) { // skip those entries that donot have any results
        Logger.debug("","","Peer "+p.getName()+": no result found for query " + query,
          debugSection, Debugger.DEBUG_LEVEL_DETAILED);
        
        continue;
      }
      
      // assume one ASG-list result per query
      ASG extent = extents[0];
      
      Logger.debug("","","Peer "+p.getName()+": rewriting QUERY "+query+
        " based on EXTENT: "+extent,
        debugSection, debugLevel);
      
      QueryRewriteProcessor qproc = new QueryRewriteProcessor(null, qa, QueryRewriteProcessor.TYPE_RANGE_BASED);
      qproc.initRewriterEngine(extent);
      
      result = qproc.rewrite();
      
      // publish result tuples of this query to the network
      Map entries = result.getEntries();
      Iterator kit = entries.keySet().iterator();
      
      Logger.debug("","","----------------- HAS "+entries.size()+
        " views ----------------",
        debugSection, debugLevel);
      
      while (kit.hasNext()) {
        ASG v = (ASG) kit.next();
        ASG r = (ASG) result.getEntry(v);
        
        if (r == null) {
          Logger.debug("","", "Peer "+p.getName()+": View "+v+
            " has no associated tuples --> skipped", debugSection, debugLevel);
          continue;
        }
        
        Logger.debug("","","Peer "+p.getName()+": publishing query "+v+
          ", \n>>>>> tuple set = "+ r.toString(),
          debugSection, debugLevel);
        
        // distribute the query and its cached records
        AutoMedType[] range = result.getRange(v);
        int min = (int) range[0].doubleValue();
        int max = (int) range[1].doubleValue();
        ID id = getHashKeyForRange(null, min, max);
        int ttl = Integer.parseInt(getProperty("TTL"));
        distributeDBRecord(peerIndex, v, r, id, ttl);
      }
    }
  }
  
  /**
   * Rewrite the local cache and perform a query over it 
   */
  public void testStandAlone(int peerIndex) throws Exception {
    Peer p = getPeers()[peerIndex];
    IQueryCache cache = CacheManager.getQueryCache();
    
    // first rewrite each cache query using range-based method
    Map newCache = new HashMap();
    
    while (cache.hasNext()) {
      
      QueryAnalyser qa = (QueryAnalyser) cache.next();
      
      if (qa == null) { // only consider queries that have been analysed
        Logger.debug("","","Peer "+p.getName()+": this entry does not have a query analyser",
          debugSection, Debugger.DEBUG_LEVEL_DETAILED);
        continue;
      }
      
      String query = qa.getQueryString();
      ASG[] extents = cache.getQuery(query);
      
      if (extents == null) { // skip those entries that donot have any results
        Logger.debug("","","Peer "+p.getName()+": no result found for query " + query,
          debugSection, Debugger.DEBUG_LEVEL_DETAILED);
        
        continue;
      }
      
      // assume one ASG-list result per query
      ASG extent = extents[0];
      
      Logger.debug("","","Peer "+p.getName()+": rewriting QUERY "+query+
        " based on EXTENT: "+extent,
        debugSection, debugLevel);
      
      QueryRewriteProcessor qproc = new QueryRewriteProcessor(null, qa, QueryRewriteProcessor.TYPE_RANGE_BASED);
      qproc.initRewriterEngine(extent);
      
      result = qproc.rewrite();
      
      // publish result tuples of this query to the network
      Map entries = result.getEntries();
      Iterator kit = entries.keySet().iterator();
      
      Logger.debug("","","----------------- HAS "+entries.size()+
        " views ----------------",
        debugSection, debugLevel);
      
      while (kit.hasNext()) {
        ASG v = (ASG) kit.next();
        ASG r = (ASG) result.getEntry(v);
        
        if (r == null) {
          Logger.debug("","", "Peer "+p.getName()+": View "+v+
            " has no associated tuples --> skipped", debugSection, debugLevel);
          continue;
        }
        
        Logger.debug("","","Peer "+p.getName()+": publishing query "+v+
          ", \n>>>>> tuple set = "+ r.toString(),
          debugSection, debugLevel);
        
        newCache.put(v, r);        
      }
    } 
    
    //
    String testQuery = "[{x,y} | {x,y} <- <<person,dob>>; y > 1970; y < 1976]";
    Logger.debug("","","Peer "+p.getName()+": rewriting query "+testQuery+
      " over the local cache",
      debugSection, debugLevel);
    QueryRewriteProcessor mqproc = new QueryRewriteProcessor(null, new ASG(testQuery), 
      QueryRewriteProcessor.TYPE_MINICON_NONCONJUNCTIVE);
    
    QueryAnalyser[] views = new QueryAnalyser[newCache.size()];
    int index = 0;
    for (Iterator it = newCache.keySet().iterator(); it.hasNext();) {
      ASG v = (ASG) it.next();
      ASG t = (ASG) newCache.get(v);
      QueryAnalyser va = new QueryAnalyser(v);
      va.analyse();
      views[index++] = va;
    }
    
    mqproc.initRewriterEngine(views);
    QueryRewriterResult rwResult = mqproc.rewrite();
    
    StringBuffer sb = new StringBuffer();
    
    sb.append("Cached views: \n");
    if (rwResult != null) {      
      for (int i = 0; i < rwResult.length(); i++) {
        java.util.List viewSet = rwResult.getViewSet(i);
        sb.append("cached view set: " + i + "\n");
        for (Iterator vsIt = viewSet.iterator(); vsIt.hasNext();) {
          ASG view = (ASG) vsIt.next();
          sb.append("\t" + view + "--> result: "+newCache.get(view)+"\n");
        }
        i++;
      }
    } else {
      sb.append("Not found!");
    }
    
    Logger.debug("","",sb.toString(),debugSection, debugLevel);
    
  }
    
  /**
   * Use this method to calculate a hashed-key for a numeric range of an index
   * attribute.
   */
  public static ID getHashKeyForRange(String field, int rgMin, int rgMax) {
    // this method calculates hashed-key from lower bound of the range
    ID hid = OverlayToolKit.createIDFromNumber(rgMin);
    return hid;
  }
  
  /**
   * Extend parent's method to return ranges for the cache's test data
   */
  public int[][] getRangeGuardDef(String field) {
    AutoMedType[][] rgDef = result.getRangeDef(field);
    return NumberToolkit.toIntegerArray(rgDef);
  }
  
  /**
   * Create a test data cache
   */
  public void createDataCache(int peerIndex) throws Exception {
    if (_qcex == null)
      _qcex = new QueryCacheExample();
    
    _qcex.addPersonData();
  }
  
  /**
   * Create a test data cache
   */
  public void initialiseCache(int peerIndex) throws Exception {
    if (_qcex == null)
      _qcex = new QueryCacheExample();
    
    _qcex.clear();
  }
  
  /**
   * Application entry point
   */
  public static void main(String[] args) throws Exception{
    
    Options opts = DONWithSchema.getDefaultOptions();
    CommandLineParser parser = new GnuParser();
    CommandLine cli = null;
    
    int numPeers = OverlayController.DEF_NUM_HOSTS;
    int startNum = 0;
    String hostPort = null;
    boolean withoutDSP = false;
    boolean joinDS = true;
    String scenario = null;
    int[] actions = null;
    
    if (args == null || args.length < 1) {
      AutoMedUtils.printCmdUsage("BasicCON", 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("BasicCON", opts);
        System.exit(0);
      }
      
      numPeers = Integer.parseInt(cli.getOptionValue("n"));
      scenario = cli.getOptionValue("f");
      actions = AutoMedUtils.readScenarioFile(scenario, BasicCON.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;
      }
      
      if (cli.hasOption("y")) {
        joinDS = false;
      }
    }
    
    Debugger.setSectionDebugLevel(args);
    DSRConfiguration.setDefaultConfigFileName(args);
    
    Logger.setOutputHandler(new LogOutputHandler(LogOutputHandler.CONSOLE +
      LogOutputHandler.FILE));
    
    Peer.initOverlay(startNum, numPeers);
    
    BasicCON conEx = null;
    boolean useRandomID = true;
    
    do {
      if (conEx != null && AutoMedUtils.dialogYesNo("Start a new set of peers ?", 0) == 1) {
        conEx.finalize();
        try {Thread.sleep(5 * 1000);} catch (InterruptedException ie) {}
        conEx = new BasicCON();
        conEx.startAutoMedPeers(numPeers, useRandomID, hostPort, !withoutDSP, joinDS);
      } else if (conEx == null) {
        conEx = new BasicCON();
        conEx.startAutoMedPeers(numPeers, useRandomID, hostPort, !withoutDSP, joinDS);
      }
      
      // 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) {
              conEx.displayOverlayStatus();
            }
            break;
          case 2:
            // publish schema --> N/A
            break;
          case 3:
            // prompt user for a peer
            break;
          case 4:
            // obtain schema from the network --> N/A
            break;
          case 5:
            // prompt user for a peer
            if (peerIndex < 0)
              peerIndex  = conEx.displayPeerList();
            
            // Query the cache overlay network           
            String testQuery = "[{x,y} | {x,y} <- <<person,dob>>; y > 1985; y <= 1990]";
            if (AutoMedUtils.dialogYesNo("Query the network ?", 1) == 1) {
//              conEx.getValuesFromQuery(peerIndex);
//              System.out.println("********** BasicCON: querying the CON with query: " + 
//                testQuery);
              conEx.getValuesFromQuery(peerIndex, testQuery);
            }
            break;
          case 6: // reinitialise the cache
            if (peerIndex < 0)
              peerIndex  = conEx.displayPeerList();
            
            if (AutoMedUtils.dialogYesNo("Reinitialise the data cache ?", 1) == 1) {
              conEx.initialiseCache(peerIndex);
            }
            break;
          case 7: // add person's test data
            if (peerIndex < 0)
              peerIndex  = conEx.displayPeerList();
            
            if (AutoMedUtils.dialogYesNo("Add person test data to cache?", 1) == 1) {
              conEx.createDataCache(peerIndex);
            }
            break;
          case 8: // run stand-alone test
            if (peerIndex < 0)
              peerIndex  = conEx.displayPeerList();
            
            // run stand alone test
            if (AutoMedUtils.dialogYesNo("Run stand-alone test ?", 1) == 1) {
              conEx.testStandAlone(peerIndex);
            }
            break;
          case 9: // distribute cache data
            if (peerIndex < 0)
              peerIndex  = conEx.displayPeerList();
            
            // Publish cache data to the network
            if (AutoMedUtils.dialogYesNo("Distribute cache data to the network ?", 1) == 1) {
              conEx.distributeCacheData(peerIndex);
            }
            break;
          default:
            out.println("Action " + actions[i] + " is Not supported");
        }
      }
    } while (AutoMedUtils.dialogYesNo("Run again ?", 0) == 1);
    
    AutoMedUtils.banner("~END~");
    System.exit(0);
  }
}
