package nxm.sys.net;

import nxm.sys.lib.Command;
import nxm.sys.lib.Convert;
import nxm.sys.lib.KeyObject;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Query;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;
import nxm.sys.libg.GLabel;
import nxm.sys.libg.GPrompt;
import nxm.sys.libg.GWidget;

/**
  A class to implement a graphical query into an Object.

  This widget introspects an object and makes its set and get methods
  available for status and control.  A method is considered a property
  if it has a getXXX method with no arguments.  If a setXXX method of
  the same name with one argument of the getXXX methods return type,
  that property is considered controllable and will show "=&gt;" rather than
  just "=" between the property name and its short string value in the display.
  @author Jeff Schoen

*/
public class HQuery extends HSource {

  private String list;
  private Object object;
  private Query query;
  private int maxitems;

  /** HQuery Constructor
   *  @param name     the name of the HQuery
   *  @param object   the object to be queried
   *  @param list     property list
   *  @param maxitems maximum size of setters, getters, etc. arrays
   */
  public HQuery (String name, Object object, String list, int maxitems) {
    this.name = name;
    this.object = object;
    this.maxitems = maxitems;
    this.list = list;
    query = new Query(object,list,maxitems);
    query.parse();
  }

  synchronized public void handleRequest (String uri, HPage hp) {

    int i,j,k;
    Query q = query;
    Object obj = object;

    int lu = uri.length();
    if (uri.charAt(lu-1)=='/') uri=uri.substring(0,lu-1);

    // look for a set command
    String command = null;
    int qm = uri.indexOf('?');
    if (qm>0) {
      command = uri.substring(qm+1);
      uri = uri.substring(0,qm);
    }

    // walk the chain
    for (j=1; (k=uri.indexOf("/",j)) > j; j=k) {
      k++; i = uri.indexOf("/",k);
      if (i<0) i=uri.length();
      String key = uri.substring(k,i);
      q.parse();
      if (key.equals("Set")) { doCommand("Ack",uri.substring(0,k),command,obj,q,hp); return; } // since 3.7.0 for BUG 2847
      if (key.equals("Get")) { doCommand("Ret",uri.substring(0,k),command,obj,q,hp); return; } // since 3.7.0 for BUG 2847
      obj = q.getObject(key);
      q = new Query(obj,list,maxitems);
    }

    // special case for GWidgets
    if (obj instanceof GWidget) {
      int qs = uri.lastIndexOf('/');
      String name = uri.substring(qs+1);
      uri = uri.substring(0,qs);
      Table t = new Table();
      t.put(name,obj);
      q = new Query(t,list,maxitems);
    }

    q.parse();

    // evaluate set command
    if (command != null) doCommand ("Set",uri,command,obj,q,hp);

    q.evaluate();

    queryPage (uri,q,hp);
  }

  private void waitForSync() {
    Midas M = server.getMidas();
    Command cmd = M.macro;
    cmd.queue.put("NULL",0,null,cmd,cmd);
    while (cmd.queue.avail()>0) Time.sleep(0.01);
  }

  private void doCommand (String mode, String uri, String command, Object obj, Query q, HPage hp) {
    if (command.startsWith("{")) command = command.substring(1,command.length()-1);
    Parser p = new Parser(command);
    String string = "";
    if (mode.equals("Set") || mode.equals("Ack")) {
      while (p.more()) {
        String entry = p.next();
        int ie = entry.indexOf('=');
        String key = (ie<0)? entry : entry.substring(0,ie);
        Object value = (ie<0)? null : entry.substring(ie+1);
        Object o = q.getObject(key);
             if (key.equals("/WAIT")) Time.sleep(Convert.o2d(value));
        else if (key.equals("/SYNC")) waitForSync();
        else if (ie==entry.length()-1) value = (o instanceof GWidget)? ((GWidget)o).getValueString() : KeyObject.getKey(obj,key); // no value - get
        else if (o instanceof GWidget) ((GWidget)o).setAction(value.toString());
        else KeyObject.setKey(obj,key,value);
        if (string.length()>0) string+=",";
	string += key+"="+value;
      }
    }
    if (mode.equals("Ret") || mode.equals("Ack")) {
      while (p.more()) {
        String entry = p.next();
        int ie = entry.indexOf('=');
        String key = (ie<0)? entry : entry.substring(0,ie);
        Object o = q.getObject(key);
        Object value = (o instanceof GWidget)? ((GWidget)o).getValueString() : KeyObject.getKey(obj,key);
        if (string.length()>0) string+=",";
	string += key+"="+value;
      }
      hp.setContentType("text/plain");
      hp.open();
      hp.write(uri+mode+"?{"+string+"}");
      hp.close();
    }
  }

  /** Queries a given page with a given query
   *  @param uri the uri for the page
   *  @param q   the query to be run
   *  @param hp  the page to be queried
   */
  public static void queryPage (String uri, Query q, HPage hp) {
    boolean view = true;
    boolean cTable = uri.endsWith("/Controls");
    hp.openToBody("HQuery Page");
    hp.writeln ("<h3><a href='' onClick=\"location.reload(); return false;\">Query</a> = "+uri+"</h3>");
    hp.writeln ("<table>");
    for (int i=0; i<q.getItems(); i++) {
      String name = q.getName(i);
      Object o = q.getObject(i);
      String value = q.getValue(i).toString();
      boolean isCntrl = cTable && (o instanceof GWidget);
      boolean isLabel = cTable && (o instanceof GLabel);
      boolean isPrompt= cTable && (o instanceof GPrompt);
      boolean isFiles = cTable && isPrompt && ((GPrompt)o).isFileChooser();
      if (isCntrl) value = lookForChoices(name,value,isLabel);
      else value = lookForBraces(value);
      if (view || isLabel) {
        hp.writeln ("<tr>");
        if (isLabel) hp.writeln ("<td nowrap><a href =\""+uri+"?"+name+"=Toggle\">"+name+"</a></td>");
        else if (isCntrl) hp.writeln ("<td nowrap></td>");
        else hp.writeln ("<td nowrap><a href =\""+uri+"/"+name+"\">"+name+"</a></td>");
        hp.writeln ("<td nowrap>"+value+"</td>");
        hp.writeln ("</tr>");
      }
      if (isLabel) view = ((GLabel)o).getActive()>0;
    }
    hp.writeln ("</table>");
    hp.closeFromBody();
  }

  /** Looks for GWidget choices
   *  @param name    the message name from a GWidget
   *  @param value   the value of the GWidget as a String
   *  @param isLabel whether or not the name represents a label
   *  @return a GWidget choice
   */
  public static String lookForChoices (String name, String value, boolean isLabel) {
    if (value==null) return value;
    int fb = value.indexOf('[');
    int rb = value.indexOf(']');
    if (fb<0 || rb<0) return value;
    int i,j;
    String selects = "";
    int la = value.lastIndexOf('<');
    int ra = value.indexOf('>');
    String ditem = (la>0 && ra>la)? value.substring(la+1,ra) : "";
    for (i=fb; i<rb; i=j) {
      j=value.indexOf(',',i+1);
      if (j<0) j=rb;
      String item = value.substring(i+1,j); int il=item.length()-1;
      if (il>0 && item.charAt(0)=='<' && item.charAt(il)=='>') {	// current
        item = item.substring(1,il);
        selects += " <a href =\"?"+name+"="+item+"\"><b>"+item+"</b></a> ";
      } else if (il>0 && item.charAt(0)=='>') {				// increment
        String sitem = ditem+"+"+item.substring(1);
        selects += " <a href =\"?"+name+"="+sitem+"\">"+item+"</a> ";
      } else if (il>0 && item.charAt(il)=='<') {			// decrement
        String sitem = ditem+"-"+item.substring(0,il);
        selects += " <a href =\"?"+name+"="+sitem+"\">"+item+"</a> ";
      } else {
        if (isLabel && !(item.equals("Open") || item.equals("Closed") || item.equals("Toggle"))) continue;
        selects += " <a href =\"?"+name+"="+item+"\">"+item+"</a> ";
      }
      if (j!=rb) selects += ',';
    }
    return value.substring(0,fb+1)+selects+value.substring(rb);
  }

  private static String lookForBraces (String value) {
    for (int i=value.length()-1; i>=0; i--) {
      char c = value.charAt(i);
      if (c=='<') value = value.substring(0,i)+"&lt"+value.substring(i+1);
      if (c=='>') value = value.substring(0,i)+"&gt"+value.substring(i+1);
    }
    return value;
  }

}
