package nxm.sys.intr;

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;

import nxm.sys.inc.Installation;
import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.KeyObject;
import nxm.sys.lib.KeyVector;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.libg.GAlert;
import nxm.sys.libg.GCalendar;
import nxm.sys.libg.GDialog;
import nxm.sys.libg.GFiles;
import nxm.sys.libg.GLabel;
import nxm.sys.libg.GLine;
import nxm.sys.libg.GList;
import nxm.sys.libg.GMenu;
import nxm.sys.libg.GPanel;
import nxm.sys.libg.GPipeMonitor;
import nxm.sys.libg.GProgress;
import nxm.sys.libg.GPrompt;
import nxm.sys.libg.GQuery;
import nxm.sys.libg.GSText;
import nxm.sys.libg.GText;
import nxm.sys.libg.GValue;
import nxm.sys.libg.GWidget;
import nxm.sys.libg.MColor;
import nxm.sys.libg.MWindow;
import nxm.sys.libg.WidgetPanel;

/**
  Creates a graphical control widget for a user interface.

  @author Jeff Schoen
*/
public class Gcontrol extends Intrinsic {

  private String type,name;
  private GWidget widget;
  private Object parent;
  private MessageHandler owner;
  private boolean oneshot,defMsg;
  private int flags;
  private GMenu menu;
  private nxm.sys.lib.Table setTable = null;

  /** default constructor */
  public Gcontrol () {}

  public int open() {

    // Get parameters
    type = MA.getU("TYPE");
    name = MA.getU("NAME");
    String panel = MA.getU("/PANEL","PANEL");
    String handle = MA.getU("/HANDLE");

    parent = M.registry.get(panel);
    if (parent==null) parent=this;
    oneshot = (parent==this);

    if (MA.find("/WIN")) { // optional location for standalone widget
      String winString = MA.getS("/WIN");
      // check added to allow NeXtMidas.java to set panel via /win switch (Bug 2316)
      if (winString==null || winString.equals("")) parent=this;
    }
    if (MA.getState("/WAIT")) oneshot = true;
    setTable = MA.getTable("TABLE");

    GDialog gd = (parent instanceof GDialog)? (GDialog)parent : null;
    defMsg = (gd == null); // most cases want messages
    flags = 0;
    if (oneshot) {
      owner = this;
    } else {
      owner = M.macro;
      if (!MA.getState("/TEMP")) flags |= GWidget.PERM;
    }
    if (MA.getState("/HEADLESS", Shell.isHeadless())) {
      flags |= GWidget.HEADLESS;
    }

    if (parent instanceof nxm.sys.prim.panel && (flags&GWidget.PERM)==0) {
      parent = ((nxm.sys.prim.panel)parent).MW;
    }
    //If gcontrol uses panel=list switch, add to list's MW
    if (parent instanceof nxm.sys.prim.list) {
      parent = ((nxm.sys.prim.list)parent).MW;
    }
    if (type.startsWith("MENU")) {
      String title = MA.getU("TITLE","Menu");
      String list = MA.getCS("P1","Item 1,Item 2,Item 3,Item 4");
      flags = getFlags(GMenu.flagsList,flags|GMenu.FLAT);
      menu = new GMenu(null,title,list,0,flags,owner);
      menu.setColumns(MA.getL("/NC",0));
    }
    else if (type.startsWith("CHOI")) {
      String title = MA.getU("TITLE","Choice");
      String list = MA.getCS("P1","Item 1,Item 2,Item 3,Item 4");
      flags = getFlags(GMenu.flagsList,flags|GMenu.CHOICE);
      // JC4: Fix for DR 500212-4, need ability to set /NC=
      int nc = MA.getL("/NC",0);                        // JC4: Added
      if (nc==0) flags |= GMenu.ONEROW;                 // JC4: Added
      if (title.length()==0) flags |= GMenu.TOFF;       // JC4: Added
      menu = new GMenu(null,title,list,0,flags,owner);
      menu.setColumns(nc);                              // JC4: Added
    }
    else if (type.startsWith("BUTT")) {
      String title = MA.getU("TITLE","");
      String list = MA.getCS("P1","Button 1,Button 2,Button 3");
      flags = getFlags(GMenu.flagsList,flags|GMenu.BUTTON|GMenu.FLAT);
      int nc = MA.getL("/NC",0);
      if (nc==0) flags |= GMenu.ONEROW;
      if (title.length()==0) flags |= GMenu.TOFF;
      menu = new GMenu(null,title,list,0,flags,owner);
      menu.setColumns(nc);
    }
    else if (type.indexOf("VAL")==1) {
      char dt = type.charAt(0);
      String title = MA.getU("TITLE","Value");
      // Use more useful tag names, but for backward compatibility override with old Pn references.
      double defMax = 1.0;
      double defMin = 0.0;
      double defVal = 0.5;
      if (dt == 'T') {
        defMax = -1.0;
        defVal =  0.0;
      }
      double v  = MA.getD("P1",MA.getD("DEF", defVal));
      double v1 = MA.getD("P2",MA.getD("MIN", defMin));
      double v2 = MA.getD("P3",MA.getD("MAX", defMax));
      double defInc = (v2 - v1) / 20;
      if (dt == 'T') defInc = 1;
      double vd = MA.getD("P4",MA.getD("INC", defInc));
      if (!MA.find("/MSGONDRAG") && !MA.find("/NOMSGONDRAG")) flags |= GValue.MSGONDRAG;
      flags = getFlags(GValue.flagsList,flags);


      widget = (GWidget) new GValue (null,title,v,v1,v2,vd,dt,flags,owner);
      ((GValue)widget).setFormat(MA.getU("/FMT"),MA.getO("/MULT",null));
    }
    else if (type.startsWith("PROMPT")) {
      String title = MA.getU("TITLE","Input");
      // Use better tags DEF and LEN but allow overwrite with P1,P2 for backward compatibility.
      String seed  = MA.getCS("P1",MA.getCS("DEF","default"));
      int digits   = MA.getL("P2",MA.getL("LEN",10));
      flags = getFlags(GPrompt.flagsList,flags);
      widget = (GWidget) new GPrompt (null,title,seed,digits,flags,owner);
    }
    else if (type.startsWith("TEXT")) {
      String title = MA.getU("TITLE","Message");
      String text  = MA.getCS("P1","");
      int cols = MA.getL("P2",24);
      int rows = MA.getL("P3",1);
      flags = getFlags(GText.flagsList,flags);
      widget = (GWidget) new GText (null,title,text,cols,rows,flags,owner);
    }
    else if (type.startsWith("LIST")) {
      String title = MA.getU("TITLE","Title");
      String text  = MA.getCS("P1","");
      int cols = MA.getL("P2",24);
      int rows = MA.getL("P3",1);
      flags = getFlags(GList.flagsList,flags);
      widget = (GWidget) new GList (null,title,text,cols,rows,flags,owner);
    }
    else if (type.startsWith("FILE")) {
      String title = MA.getU("TITLE","FileName");
      String seed = MA.getCS("P1","");
      String filt = MA.getCS("P2","*.tmp|prm");
      String raux = MA.getCS("P3",MR.getS("AUX.WRITE"));
      int cols = MA.getL("P4",50);
      int rows = MA.getL("P5",15);
      flags = getFlags(GFiles.flagsList,flags);
      if ((flags&GWidget.PERM)!=0)
        widget = (GWidget) new GPrompt (null,title,seed,filt,raux,M,cols,rows,flags,owner);
      else
        widget = (GWidget) new GFiles (null,title,seed,filt,raux,M,cols,rows,flags,owner);
    }
    else if (type.startsWith("PROG")) {
      String title = MA.getU("TITLE","Message");
      String list  = MA.getCS("P1","Run,Pause,Exit");
      String text  = MA.getCS("P2","Job Completion Status");
      int cols = MA.getL("P3",0);
      int rows = MA.getL("P4",0);
      flags = getFlags(GProgress.flagsList,flags);
      widget = (GWidget) new GProgress (null,title,list,text,cols,rows,flags,owner);
    }
    else if (type.startsWith("LABEL")) { defMsg=false;
      String title  = MA.getCS("TITLE","Section Title");
      String active = MA.getS("P1", "Open");
      flags = getFlags(GLabel.flagsList,flags);
      widget = (GWidget) new GLabel(null,title,active,flags,owner);
    }
    else if (type.startsWith("LINE")) {
      java.awt.Color c = MColor.getColor(MA.getS("P1", MColor.toString(GLine.DEF_COLOR)));
      int thickness    = MA.getL("P2",GLine.DEF_THICKNESS);
      int percent      = MA.getL("P3",100);   // default of 100 percent of the width of the panel
      int linePos      = GLine.getLinePosValue(MA.getS("P4","LEFT"));
      flags            = getFlags(GLine.flagsList,flags);
      widget           = (GWidget) new GLine(null,name,c,thickness,percent,linePos,flags,owner);
    }
    else if (type.startsWith("ALERT")) {
      String title = MA.getU("TITLE","Alert");
      String text = MA.getCS("P1","Message Text");
      String list = MA.getCS("P2","OK");
      int def = MA.getL("P3",1);
      int timeout = MA.getL("/TIMEOUT", -1);
      flags = getFlags(GAlert.flagsList,flags);
      widget =(GWidget)new GAlert(null,title,text,list,def,flags,owner,timeout);
    }
    else if (type.startsWith("PANEL")) {
      String title = MA.getCS("TITLE","Panel");
      int pw = MA.getL("P1",MA.getL("WIDTH",200));
      int ph = MA.getL("P2",MA.getL("HEIGHT",200));
      flags = getFlags(GPanel.flagsList,flags);
      widget = (GWidget) new GPanel(null,title,pw,ph,flags,owner);
    }
    else if (type.startsWith("DIALOG")) {
      String title = MA.getCS("TITLE","Dialog");
      int pw = MA.getL("P1",200);
      int ph = MA.getL("P2",200);
      flags = getFlags(GDialog.flagsList,flags);
      String buttonsList = MA.getCS("P3","Submit,Cancel");
      String noCloseList = MA.getCS("P4",null);
      widget = (GWidget) new GDialog(null,title,pw,ph,buttonsList,noCloseList, flags,owner);
      if (handle.length()>0) M.registry.put(handle,widget);
      else M.error ("The use of DIALOG without the /HANDLE switch is no longer supported");
    }
    else if (type.startsWith("TABLE")) {
      String title = MA.getCS("TITLE","Table");
      nxm.sys.lib.Table table = MA.getTable("P1");
      nxm.sys.lib.Table config = MA.getTable("P2");
      flags = getFlags(GDialog.flagsList,flags);
      widget = (GWidget) new GDialog(null,title,table,config,flags,owner);
    }
    else if (type.startsWith("PIPE")) {
      String title = MA.getU("TITLE","NOPIPE");
      flags = getFlags(GPipeMonitor.flagsList,flags|GPipeMonitor.SLIDER);
      widget = (GWidget) new GPipeMonitor (null,title,M,flags,owner);
      ((GPipeMonitor)widget).setFormat(MA.getU("/FMT"));
    }
    else if (type.startsWith("QUERY")) {
      String title = MA.getU("TITLE","QUERY");
      Object obj = MA.getO("P1",MR);
      String list = MA.getS("P2");
      flags = getFlags(GQuery.flagsList,flags);
      widget = (GWidget) new GQuery (null,title,obj,list,flags,owner);
    }
    else if (type.startsWith("CALENDAR")) {
      flags = getFlags(GCalendar.flagsList,flags);
      widget = (GWidget) new GCalendar (null,name,flags,owner);
    }
    else if (type.startsWith("STEXT")) {
      String title  = MA.getU("TITLE", "Message");
      String stext  = MA.getCS("P1", "");
      int cols      = MA.getL("P2", 24);
      int rows      = MA.getL("P3", 1);
      flags         = getFlags(GSText.flagsList, flags);
      widget        = (GWidget) new GSText (null, title, stext, cols, rows, flags, owner);
    }
    else if (type.equals("BEAN")) {
      flags = getFlags(GWidget.flagsList, flags);
      // Get the class name provided by the first argument
      String className = MA.getCS("P1");
      try {
        // Instantiate the object via reflection
        Class<?> clazz = Class.forName(className);
        Object obj = clazz.getConstructor().newInstance(); // updated in 3.9.0 to use constructor
        if (obj instanceof GWidget) {
          String title = MA.getCS("TITLE","Bean");
          widget = (GWidget) obj;
          widget.setupBean(name, null, title, flags, owner, setTable);  // title added in 3.1.3 for Bug 2615
        }
        else M.error("The class " + className + " must extend GWidget.");
      }
      catch (ClassNotFoundException e) { M.error("The class for "+className+" could not be found.", e); }
      catch (InstantiationException e) { M.error("Check that the class "+className+" has an empty constructor.", e); }
      catch (IllegalAccessException e) { M.error("Check that the class "+className+" is public.", e); }
      // Exceptions added for the new default constructor call.
      catch (NoSuchMethodException e)  { M.error("Check that the class "+className+" has an empty constructor.", e);  }
      catch (InvocationTargetException e) { M.error("There is an issue with the constructor in the class "+className, e);}
    }
    else {
      M.warning ("Unknown widget type: " + type);
      return FINISH;
    }

    // finish stuff common to all menus
    if (menu!=null) {
      menu.setMidas(M);  // set here to prevent NPE in setValue (since 3.3.1)
      int idef = MA.getL("/IDEF");
      String sdef = MA.getCS("P2");
      if (idef>0) menu.setValue(idef);
      else menu.setValue(sdef);
      widget = (GWidget)menu;
    }

    // finish stuff common to all widgets
    widget.setLabel(name);

    if (!Installation.ApplyBugFix.GCONTROL_WTHEME_OVERRIDES_THEME) { //TODO: remove after 3.2.x
      widget.setWidgetTheme(MA.getO("/WTHEME"));    // since 2.7.0: this widget's theme only
    }
    Object themeObj = MA.getO("/THEME");
    if (themeObj !=null) widget.setTheme(themeObj); // this affects GLOBAL theme
    if (Installation.ApplyBugFix.GCONTROL_WTHEME_OVERRIDES_THEME) {
      widget.setWidgetTheme(MA.getO("/WTHEME"));    // since 3.1.1: set this widget's theme only (overriding /THEME=)
    }

    widget.setMonitor(MA.getU("/MON"));
    widget.setGroup(MA.getU("/GRP"));
    widget.setUnits(MA.getCS("/UNITS"));
    widget.fixLineHeight(MA.getL("/LH",0));
    widget.fixHeight(MA.getL("/WH",0));
    widget.fixWidth(MA.getL("/WW",0));
    widget.setFontSize(MA.getL("/WFS",0));
    widget.setMidas(M);
    if (MA.isPresent("/TOOLTIP")) {      // since 2.9.0 for Bug 2240
      String text = MA.getS("/TOOLTIP");
      widget.setToolTipText(text);
    }
    if (MA.isPresent("/NOALTTITLE")) {   // since 3.3.0 for Bug 2681
      boolean val = MA.getState("/NOALTTITLE");  // true state means No updates
      widget.setNoAltTitle(val);
    }
    if (MA.isPresent("/EVENTFILTER")) {  // since 3.3.1 for Bug 2712
      widget.eventFilter = MA.getOptionMask("/EVENTFILTER", MWindow.eventFilterList, widget.eventFilter);
    }

    // check for global defaults Table
    nxm.sys.lib.Table gcdt = MA.getTable("/GC");
    if (gcdt != null) {
      String value = gcdt.getS(widget.label);
      if (value!=null) widget.setValue(value);
    }

    // provide handle in results table
    MR.put(handle,widget);

    // run one-shot to completion
    if (oneshot) widget.addTo(parent);
    // change to reflection for instance of and method calling
    else if (WidgetPanel.isWidgetPanel(parent)) {
      // Sub-widgets of a dialog should inherit the Dialog widgetTheme if no switch is present for the individual widget
      if (gd !=null && !MA.isPresent("/WTHEME")) { widget.setWidgetTheme(gd.getWidgetTheme()); }

      // attach to the control panel. -- Done in EDT to prevent GDialog race condition (BUG 2003)
      // NXM-4064 No longer need Shell.isHeadless check that was here
      // It is important to form the table associated with panel/controls=gc
      // for snapper and other macros to work in headless mode, rather than skip
      // adding the widget
      if (EventQueue.isDispatchThread()) {
        WidgetPanel.addWidgetViaReflection(parent, widget);
      } else {
        try {
          EventQueue.invokeAndWait(new Runnable() {
            public void run() {
              WidgetPanel.addWidgetViaReflection(parent, widget);
            }} );
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

    }
    // attach to the other panel
    else widget.addTo(parent);

    // If using the 'TABLE=' options in macro ...
    if (setTable!=null) {
      if (widget != null) KeyObject.setKeys(widget,setTable,args,0);
    }

    return NORMAL;
  }

  public int process() {
    if (oneshot && widget.status!=MWindow.EXIT) return NOOP;
    return FINISH;
  }

  public int processMessage (nxm.sys.lib.Message msg) {
    if (msg.name.equals(name)) {
      //M.info ("Got message: "+msg.name);
      MR.put(name,msg.data);
    }
    else if (msg.name.equals("EXIT")) return FINISH;
    else if (msg.name.equals("REFRESH")) widget.refresh();
    else if (msg.name.equals("SHOWN")) widget.refresh();
    else M.info ("Unhandled message: "+msg.name);
    return NORMAL;
  }

  /** convert any local switches that match widget flag names
      there are 8 common flags (below), plus any widget specific flags
      "Permanent,Sticky,Fixed,NoEdit,InLine,InBand,NoMsg,Monitor"
  */
  private int getFlags (String flagsList, int flags) {
    if (!MA.getState("/MSG",defMsg)) flags |= GWidget.NOMSG; // special defaulting logic here
    String vbox = MA.getU("/INPUTBORDER",MA.getU("/VBOX"));
    if (vbox.equalsIgnoreCase("PERM")) flags |= GWidget.VBOX;
    else if (vbox.equalsIgnoreCase("MO")) flags |= GWidget.VBOXMO;
    KeyVector kv = MA.getKV();
    if (MA.nargs+1>=kv.size()) return flags;	// quick out if no local switches
    flags |= MA.getOptionMask("/FLAGS",flagsList,0);
    Parser parser = new Parser(flagsList.toUpperCase());
    String[] list = parser.getArray(0);
    for (int i=MA.nargs+1; i<kv.size(); i++) {
      String key = kv.getKey(i);
      if (key.charAt(0)!='/') continue;	// only for switches
      String ykey = key.substring(1);
      String nkey = "NO"+ykey;
      for (int mask=1,j=0; j<list.length; j++,mask<<=1) {
        if (ykey.equals(list[j]) && MA.getState(key)) flags |= mask;
        if (nkey.equals(list[j]) && !MA.getState(key)) flags |= mask;
      }
    }
    // these flags do not show up in the flagsList field
    if (MA.getState("/TITLE")) flags |= GWidget.TITLE;
    return flags;
  }

}
