package context.apps.InOutBoard;

import context.arch.widget.WPNIOSonic;

import context.arch.comm.DataObject;
import context.arch.handler.Handler;
import context.arch.InvalidMethodException;
import context.arch.MethodException;
import context.arch.util.Error;
import context.arch.util.XMLURLReader;
import context.arch.util.Configuration;
import context.arch.util.ConfigObjects;
import context.arch.util.ConfigObject;
import context.arch.util.ContextTypes;
import context.arch.util.Assert;
import context.arch.storage.AttributeFunctions;
import context.arch.storage.AttributeFunction;
import context.arch.storage.Attribute;
import context.arch.storage.AttributeNameValues;
import context.arch.storage.AttributeNameValue;
import context.arch.storage.Conditions;
import context.arch.storage.Storage;
import context.arch.storage.Retrieval;
import context.arch.storage.RetrievalResults;
import context.arch.widget.WidgetHandles;
import context.arch.widget.WidgetHandle;
import context.arch.interpreter.Interpreter;
import context.arch.interpreter.IIButton2NameExt;

import context.arch.comm.language.DecodeException;

import cmp.QuickSort.QuickSort;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.net.MalformedURLException;

public class InOutBoard implements Handler {

  private static final boolean DEBUG = false;

  /**
   * Version number of the In/Out Board app
   */
  public String VERSION_NUMBER = "1.1";
	
  /**
   * Maximum number of different users we want to deal with
   */
  public static final int MAX_PEOPLE = 20;
	
  /** 
   * Default port to start the BaseObject on is 5555
   */
  public static final int DEFAULT_PORT = 5555;

  /**
   * Subscription ID for PersonPresenceSonic UPDATE callback
   */
  public static final String SUBSCRIBER_ID = "InOutBoard";
  
  private static final String DefaultConfigURL = "http://www.cc.gatech.edu/fce/contexttoolkit/test.xml";
//  private static final String ConfigURL = "http://fire.cc.gt.atl.ga.us/context/config/inout-app.xml";
  private static final String TestConfigURL = "http://fire.cc.gt.atl.ga.us/context/config/inout-app-test.xml";
  
  private final int kOvertimeLimit = 18;	// we don't believe you can work more than 18hrs in a row! :-)
  
  // this shouldn't be needed???!!!
  public static final String RFID_ID = "RFIDP_Daniel";
  
  public final static String IN = WPNIOSonic.IN;
  public final static String OUT = WPNIOSonic.OUT;
  
  private static final boolean TESTUI = false;
  private static final boolean USERFID = false;

  private BoardWatcherThread bwt = null;	// our watcher
  private InOutBoardUI ui = null;		// pointer to our UI
  public InOutRecord[] people;
  public int peopleCount = 0;
  private Hashtable peopleSeen = new Hashtable ();
  private InOutBoardServer server = null;
	
  /**
   * Constructor that creates an instance of BaseObject at the given port.  It
   * subscribes to the PNIOSonic and displays the board accordingly.
   *
   * @param location Location of the PNIOSonic widget
   * @param localport Port the app is running on
   * @param whs WidgetHandles to PNIOSonic widgets
   * @param rfidHost Host the RFID widget is running on
   * @param rfidPort Port the RFID widget is running on
   * @param rfidID Id of the RFID widget
   * @param i2nxHost Host the iButton2NameExt interpreter is running on
   * @param i2nxPort Port the iButton2NameExt interpreter is running on
   * @param i2nxID Id of the iButton2NameExt interpreter
   * @see context.arch.BaseObject
   * @see context.arch.BaseObject#subscribeTo(context.arch.handler.Handler, int, java.lang.String,
		java.lang.String, int, java.lang.String, java.lang.String, java.lang.String)
   * @see context.arch.widget.WPNIOSonic
   */
  public InOutBoard (String location, int localport,								// local params
                     WidgetHandles whs,   // PNIOSonic widgets
                     String rfidHost, int rfidPort, String rfidID,   // RFIDPresemce widget
                     String i2nxHost, int i2nxPort, String i2nxID) { // interpreter
  	
    server = new InOutBoardServer (localport);
    server.setApp (this);
    
    if (!TESTUI) {
      for (int i=0; i<whs.numWidgetHandles(); i++) {
        WidgetHandle wh = whs.getWidgetHandleAt(i);
        Error error = server.subscribeTo((Handler)this, localport, SUBSCRIBER_ID,
           wh.getHostName(), wh.getPort(), wh.getId(), WPNIOSonic.UPDATE, "presenceUpdate");
        if (DEBUG) {
          System.out.println("error was "+error);
        }
      }
    }
		
    // subscribe to RFID/presence widget
    if (USERFID) {
      Error error = server.subscribeTo((Handler)this, localport, SUBSCRIBER_ID,
         rfidHost, rfidPort, rfidID, WPNIOSonic.UPDATE, "RFIDPresenceUpdate");
      if (DEBUG) {
        System.out.println("error for RFID was "+error); 
      }
    }

    // Create the ui
    ui = new InOutBoardUI(this);

    // grab the list of users
    DataObject ul = server.runComponentMethod(i2nxHost,i2nxPort,i2nxID,
                                   IIButton2NameExt.LISTUSERSNAMES,null,null);
    // extract users from the Data Object
    AttributeNameValues atts = new AttributeNameValues(ul);
    int nPeople = atts.numAttributeNameValues();
    people = new InOutRecord[nPeople];

    WidgetHandle wh = whs.getWidgetHandleAt(0);
    AttributeFunctions afs = new AttributeFunctions();
    afs.addAttributeFunction(WPNIOSonic.TIMESTAMP,Attribute.LONG,AttributeFunction.FUNCTION_MAX);
    afs.addAttributeFunction(WPNIOSonic.INOUT);
    for (int i = 0; i < nPeople; i++) {
      String name = (String)atts.getAttributeNameValueAt(i).getValue();
      if (DEBUG) {
        System.out.println ("read = "+name);
      }				
      addNewUser(name);
    }

    // quicksort the array of people
    QuickSort.sort (people, people[0]);  // since InOutRecord implements Comparator

    // set up a hashtable for quick access
    for (int i = 0; i < people.length; i++) {
      peopleSeen.put (people[i].getName (), new Integer (i));
    }

    // And fire up the UI
    ui.start (peopleCount, people);

    for (int i = 0; i < nPeople; i++) {
      String name = (String)atts.getAttributeNameValueAt(i).getValue();
      Conditions conds = new Conditions();
      conds.addCondition(WPNIOSonic.USERNAME,Storage.EQUAL,name);
      Retrieval retrieval = new Retrieval(afs,conds);
      DataObject retrieve = server.retrieveDataFrom(wh.getHostName(), wh.getPort(), wh.getId(),retrieval);
      String retrieveError = new Error(retrieve).getError();
      if (retrieveError.equals(Error.NO_ERROR)) {
        RetrievalResults retrieveData = new RetrievalResults(retrieve);
        if (retrieveData != null) {
          AttributeNameValues anvs = retrieveData.getAttributeNameValuesAt(0);
          AttributeNameValue when = anvs.getAttributeNameValue(WPNIOSonic.TIMESTAMP);
          AttributeNameValue status = anvs.getAttributeNameValue(WPNIOSonic.INOUT);
          userSighting(name,"CRB",(String)when.getValue(),(String)status.getValue());
        }
      }
    }

    // setup a thread that will monitor the iob
    /* bwt = new BoardWatcherThread (this);
       bwt.start ();
    */	
  }

  public void setTestData () {

    /*
      InOutRecord gregory = new InOutRecord ("Gregory D. Abowd", IN, "906663088420");
      InOutRecord anind   = new InOutRecord ("Anind K. Dey", IN, "906663088420");
      InOutRecord daniel  = new InOutRecord ("Daniel Salber", OUT, "906663088420");
    */

    // people[peopleCount++] = gregory;
    // people[peopleCount++] = anind;
    // people[peopleCount++] = daniel;
  }
	
  private String now () {
    Long now = new Long (System.currentTimeMillis());
    return (now.toString());
  }
	
  private void addNewUser (String who, String what, String when) {
    InOutRecord newUser = new InOutRecord (who, what, when);  // if we keep track ourselves, 
                                                              // assume new users are out
    people[peopleCount++] = newUser;
  }
		
  private void addNewUser (String who, String when) {
    addNewUser (who, OUT, when);
  }

  private void addNewUser (String who) {
    addNewUser (who, now());
  }
		
  public DataObject userSighting (String who, String where, String when, String status) {
    // either looking at /where/ tells us if in/out
    // or we keep track ourselves.
    // we should make sure that /where/ is the right location (?)
    // have we seen that user before?
    Integer userIndex = (Integer)peopleSeen.get(who);
    int ix;
    DataObject result = null;	 // we return this for feedback

    if (userIndex != null) {
      ix = userIndex.intValue ();
      InOutRecord userRecord = people[ix];
      userRecord.setStatus(status);
      userRecord.setInfo (when);
    }				
    else {
      addNewUser (who, when);
      /*
        peopleSeen.put (who, new Integer (peopleCount));
        InOutRecord newUser = new InOutRecord (who, IN, when);  // if we keep track ourselves, assume new users are just in
        people[peopleCount++] = newUser;
      */
      ix = peopleCount - 1; // to be used next line
    }

    // update UI
    if (DEBUG) {
      System.out.println ("about to update UI for: " + ix);
    }
     
    ui.setInOut (ix, people[ix]);
    return result;
  }
	
  public DataObject handle (String callback, DataObject data) throws InvalidMethodException, MethodException {
    DataObject result = null;  // we return this
    if ((callback.equals("presenceUpdate")) || (callback.equals("RFIDPresenceUpdate"))) {
      AttributeNameValues anvs = new AttributeNameValues(data);
      AttributeNameValue who = anvs.getAttributeNameValue(WPNIOSonic.USERNAME);
      AttributeNameValue where = anvs.getAttributeNameValue(WPNIOSonic.LOCATION);
      AttributeNameValue when = anvs.getAttributeNameValue(WPNIOSonic.TIMESTAMP);
      AttributeNameValue status = anvs.getAttributeNameValue(WPNIOSonic.INOUT);
      
    	if (DEBUG) {
        System.out.println("\n\n\n**********************************************\n");
        System.out.print(who.getValue()+" was seen going ");
        System.out.println(status.getValue()+" ");
        System.out.print(where.getValue()+" at time ");
        System.out.println(when.getValue()+"\n");
        System.out.println("**********************************************\n");
      }

      result = userSighting ((String)who.getValue(), (String)where.getValue(), (String)when.getValue(), (String)status.getValue());
			
      if (DEBUG) {
        System.out.println ("userSighting returned: " + result);
      }

      return result;
    }
    else {
      throw new InvalidMethodException(Error.UNKNOWN_CALLBACK_ERROR);
    }
  }
  
  public void quitApp () {
    /*
      bwt.stop();
    */
    System.exit (0);
  }


  // forces a refresh of the UI (called by the watcher thread every night)
  public void midnightRefresh () {
    ui.refreshAll ();
  }
	
  // checks if users forgot to log out. Called every night by the watcher thread.
  public void checkOvertime () {
    for (int i=0; i<people.length; i++) {
      InOutRecord p = people[i];
    }
  }
	
  public static void main (String argv[]) {
    String location = null;
    int localPort = -1;
    String rfidHost = null;
    int rfidPort = -1;
    String rfidID = null;
    String ii2nxHost = null;
    int ii2nxPort = -1;
    String ii2nxID = null;
    WidgetHandles whs = new WidgetHandles();

    try {
      XMLURLReader cfgr;
      if (TESTUI) {
        cfgr = new XMLURLReader(TestConfigURL); 
      }
      else if (argv.length == 1) {
        cfgr = new XMLURLReader(argv[0]);
      }
      else {
        cfgr = new XMLURLReader(DefaultConfigURL);
      }
      DataObject cfg = cfgr.getParsedData();
      Configuration config = new Configuration(cfg);

      if (DEBUG) {
        System.out.println (cfg);
      }

      AttributeNameValues params = config.getParameters();
      if (params != null) {
        // location
        AttributeNameValue loc = params.getAttributeNameValue("LOCATION");
        if (loc != null) {
          location = (String)loc.getValue();
        }

        // localport
        AttributeNameValue port = params.getAttributeNameValue("LOCALPORT");
        if (port != null) {
          localPort = Integer.parseInt((String)port.getValue());
        }
      }
		
      ConfigObjects widgets = config.getWidgetConfigurations();

      if (widgets != null) {
        for (Enumeration e=widgets.getEnumeration(); e.hasMoreElements(); ) {
          ConfigObject widget = (ConfigObject)e.nextElement();
          if (DEBUG) {
            System.out.println ("Widget: "+widget);
          }
          if (widget.getId() != null) {
            // find out which widget this is
            if (widget.getId().equals(RFID_ID)) {  
              // id
              rfidID = widget.getId();
              // host
              if (widget.getHost() != null) {
                rfidHost = widget.getHost();
              }
              // port
              if (widget.getPort() != null) {
                rfidPort = Integer.parseInt(widget.getPort());
              }
            }
            else { // PNIOSonic widget	
              // id
              if ((widget.getHost() != null) && (widget.getPort() != null)) {
                whs.addWidgetHandle(widget.getId(),widget.getHost(),Integer.parseInt(widget.getPort()));
              }
            }
          }
        }
      }

      // now the interpreter
      ConfigObjects interpreters = config.getInterpreterConfigurations();
      if (interpreters != null) {
        ConfigObject interpreter = (ConfigObject)interpreters.getEnumeration().nextElement();
        if (interpreter != null) {
          // host
          if (interpreter.getHost() != null) {
            ii2nxHost = interpreter.getHost();
          }
          if (interpreter.getPort() != null) {
            ii2nxPort = Integer.parseInt(interpreter.getPort());
          }
          // id
          if (interpreter.getId() != null) {
            ii2nxID = interpreter.getId();
          }
        }   
      }
    } catch (MalformedURLException mue) {
        System.out.println("InOutBoard MalformedURL: "+mue);
    } catch (DecodeException de) {
        System.out.println("InOutBoard Decode: " + de);
    }
		
    // print out the config
    if (DEBUG) {
      System.out.println ("Attempting to create an InOutBoard. Configuration is:");
      System.out.println ("location = "+location);
      System.out.println ("localPort = "+localPort);
      for (int i=0; i<whs.numWidgetHandles(); i++) {
        WidgetHandle wh = whs.getWidgetHandleAt(i);
        System.out.println ("wpnioHost "+i+" = "+wh.getHostName());
        System.out.println ("wpnioPort "+i+" = "+wh.getPort());
        System.out.println ("wpnioID "+i+" = "+wh.getId());
      }
      System.out.println ("rfidHost = "+rfidHost);
      System.out.println ("rfidPort = "+rfidPort);
      System.out.println ("rfidID = "+rfidID);
      System.out.println ("ii2nxHost = "+ii2nxHost);
      System.out.println ("ii2nxPort = "+ii2nxPort);
      System.out.println ("ii2nxID = "+ii2nxID);
    }

    InOutBoard iobApp = new InOutBoard(location, localPort, whs,
                                        rfidHost, rfidPort, rfidID,
                                        ii2nxHost, ii2nxPort, ii2nxID);
  }

}
