package nxm.sys.libg;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Hashtable;

import nxm.sys.inc.Keyable;
import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.KeyVector;
import nxm.sys.lib.Message;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Query;
import nxm.sys.lib.Table;

/**
     A class to implement a graphical query into an Object. <BR>
     <BR>
     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 method's 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.<BR>
     <BR>
     If the getXXX returns an object that cannot be handled by a known
     GWidget, a new GQuery is instantiated on the returned object for
     further introspection.<BR>
     <BR>
     Objects that wish to hide some of their getXXX methods from display
     can include a String propertyList="Prop1,Prop2,...PropN" in the class
     to override the dynamically created list.<BR>
     <BR>
     TBD: If a getPropN() method cannot be found, it will look for just propN().
     Thus, non-standard property methods can be made accessible.<BR>
     <BR>
     For Objects with a public propertynameList field (notice property name is in lower case),
     it will replace regular text prompt (GPrompt) with a start GMenu.
     For example: PLOT MENU &gt; VIEW (uses plot.viewList (constant string list) and plot.view (current )

   @author Jeff Schoen
 */
public class GQuery extends GDialog {

  /** widget flags */
  public static final String flagsList = GWidget.flagsList+",Filter";
  /** flags mask: apply default filter to limit introspection */
  public static final int FILTER=0x0100;

  private int maxitems=128,maxwidth=120;
  private Query query;
  private GList plist;

  /**
     Constructs a new GQuery using the parameter propertyList
     (comma-delimited list of properties) to query the object.

     @param parent the parent object
     @param name the name of this GQuery
     @param obj the object to be queried
     @param list the optional comma-delimited list of properties
     to display in the GQuery
     @param flags the widget flags
     @param mh the Message Handler
   */
  public GQuery (Object parent, String name, Object obj, String list,
      int flags, MessageHandler mh) {
    super(null,name,-50,-10,"Back,Pop,Redo,Exit","Back,Pop,Redo",flags,mh);
    // Since NXM 3.8.1 This is used to increment the amount of popups contained inside of MPLot.
    // Usually done in the super, done here because we send in null parent
    if( parent instanceof MPlot) ((MPlot)parent).popupCount++;
    open (name, list, obj);
    addTo(parent);
  }

  /**
     Constructs a new GQuery by dynamically querying the object's properties.

     @param parent the parent object
     @param name the name of this GQuery
     @param obj the object to be queried
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GQuery (Object parent, String name, Object obj, int flags, MessageHandler mh) {
    this(parent,name,obj,null,flags,mh);
  }

  /**
     Opens the graphics window with the given name and displays
     the query results for the given object.

     @param name the name of this GQuery
     @param list property list
     @param obj the object to be queried
   */
  public void open (String name, String list, Object obj) {
    super.open();
    query = new Query(obj,list,maxitems);
    setMaxWidgets(1);
    plist = new GList(this,"Properties","Properties",40,10,GList.MOMENT,this);
    formPropertiesList();
  }

  private void formPropertiesList() {
    query.parse();
    evalPropertiesList();
  }

  private void evalPropertiesList() {
    query.evaluate();
    int items = query.getItems();
    plist.removeItems();
    for (int i=0; i<items; i++) {
      String tmp = query.getName(i);
      if (query.getSetMethod(i)!=null) tmp += " => "; else tmp += " = ";
      tmp += query.getValue(i);
      if (tmp.length()>maxwidth) tmp = tmp.substring(0,maxwidth);
      plist.addItem(tmp);
    }
  }

  /**
     Process messages for this GQuery.
     @param msg {@inheritDoc}
     @return -1
   */
  public int processMessage (Message msg) {
    if (status==EXIT);
    else if (msg.name.equals("DIALOG")) {
      String action = (String)msg.data;
           if (action.equals("BACK")) back(false);
           if (action.equals("POP")) back(true);
      else if (action.equals("REDO")) formPropertiesList();
      else if (action.equals("EXIT")) close();
    }
    else if (msg.name.equals("PROPERTIES")) {
      select(msg.info-1); // GList is one based
    }
    else {
      query.setObject(msg.name,msg.data);
      evalPropertiesList();
      refresh();
    }
    return -1;
  }

  /** Process event messages for this widget locally.
      @param i one-based index of item to select
   */
  private void select (int i) {
    Object obj = query.getObject();
    String key = query.getName(i);
    Method get = query.getGetMethod(i);
    Method set = query.getSetMethod(i);
    boolean hss = query.getSetStringMethod(i) != null;
    try {
      Object object = query.getObject(i);
      // now go to the next level display
      if (object instanceof Hashtable ||
          object instanceof KeyVector ||
          object instanceof Table) {
        newQuery (key,object);
      }
      else if (set==null && get!=null) {
        if (object instanceof String);
        else newQuery (key,object);
      }
      else if (object instanceof String || object==null) {
        String menuList = null;
        String value = (String)object;
        String fieldname = key.toLowerCase()+"List";
        if (obj instanceof Keyable)
          menuList = (String)((Keyable)obj).getKey(fieldname.toUpperCase());
        if (menuList==null) try {
          Field field = obj.getClass().getField(fieldname);
          menuList = (String)field.get(obj);
        } catch (Exception e) { }
        if (menuList==null)
          new GPrompt (plist,key,value,0,this);
        else if (value.charAt(0)=='|') {
          int def = Parser.mask(menuList,value,0);
          new GMenu (plist,key,menuList,def,GMenu.TOGGLE|GMenu.TOGGLELIST,this);
        } else {
          int def = Parser.find(menuList,value,0);
          new GMenu (plist,key,menuList,def,0,this); // Popup?
        }
      }
      else {
        double val=0.0, dval=1.0; char type='L';
        if (object instanceof Double) {
          val = ((Double)object).doubleValue();
          type='D';
          dval=0.1*val;
        }
        else if (object instanceof Float) {
          val = ((Float)object).doubleValue();
          type='F';
          dval=0.1*val;
        }
        else if (object instanceof Integer)
          val = ((Integer)object).doubleValue();
        else if (object instanceof Short)
          val = ((Short)object).doubleValue();
        else if (object instanceof Byte)
          val = ((Byte)object).doubleValue();
        else type = 'O';
        if (type!='O') new GValue (plist,key,val,val,val,dval,type,0,this);
        else if (hss) new GPrompt (plist,key,object.toString(),0,this);
        else newQuery (key,object);
      }
    }
    catch (Exception e) { }
  }

  private void newQuery (String key, Object object) {
    Query newquery = new Query(object,null,maxitems);
    newquery.parent = query;
    query = newquery;
    setTitle(title+"."+key);
    formPropertiesList();
  }

  private void back (boolean pop) {
    if (query.parent==null) return;
    int i = title.lastIndexOf('.');
    String key = title.substring(i+1);
    if (pop) new GQuery(MW,title,query.getObject(),null,flags,mh);
    setTitle(title.substring(0,i));
    query = query.parent;
    formPropertiesList();
  }

}
