package nxm.sys.libg;

import java.awt.Graphics;

import nxm.sys.inc.MessageHandler;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.Message;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Table;

/** A class to implement a dialog box consisting of GWidgets. User may set a
    list of buttons which do not close the dialog window.  When a dialog button
    is pressed a table of data containing the values of all the child widgets
    is returned. Use <code>MSG.DATA.DIALOG.VALUE</code> to get the return value.
    <BR>
    Child widgets may be configured to send messages, but by default do not.<BR>

    @author Jeff Schoen
    @author Jay Hallam
*/
public class GDialog extends GWidget implements WidgetPanel {
  protected boolean needsResize = true; // To prevent to many calls to the Event Dispatch Thread Since 3.9.0 NXM-3213
  private static String defButtonsList = "Submit,Cancel";
  private static String defNoCloseButtons = null;
  private String noCloseButtons = defNoCloseButtons;
  private int sw, rw, gh, grid;
  private int dialog;
  private Table widgets;
  /** The gmenu that is used for the dialog */
  public  GMenu gdialog;
  private int minw, minh, maxh, fullh;
  private int maxWidgets;
  private boolean rollup;
  private GWidget gwl;

  /** Construct a new GDialog using the buttonsList and noCloseButtonsList provided.
      @param parent      the parent object
      @param name        the name of this GDialog
      @param pw          the panel width
      @param ph          the panel height
      @param buttonsList the comma-delimited list of buttons that close this dialog
      @param noCloseList the comma-delimited list of buttons that do not close this dialog
      @param flags       the widget flags
      @param mh          the MessageHandler
   */
  public GDialog (Object parent, String name, int pw, int ph, String buttonsList,
                  String noCloseList, int flags, MessageHandler mh) {
    super("GDialog",parent,name,TITLE,mh);
    open(); // get text sizes
    if( parent instanceof MPlot) ((MPlot)parent).popupCount++; // Since NXM 3.8.1 This is used to increment the amount of popups contained inside of MPLot.
    init(buttonsList,noCloseList,pw,ph,flags);
    addTo(parent);
  }

  /** Construct a new GDialog for init() call by subclass after determining size
      @param parent      the parent object
      @param name        the name of this GDialog
      @param mh          the MessageHandler
   */
  public GDialog (Object parent, String name, MessageHandler mh) {
    super("GDialog",parent,name,TITLE,mh);
    open(); // get text sizes
  }

  /** Construct a new GDialog using the default buttons.
      @param parent the parent object
      @param name   the name of this GDialog
      @param pw     the panel width
      @param ph     the panel height
      @param flags  the widget flags
      @param mh     the MessageHandler
   */
  public GDialog (Object parent, String name, int pw, int ph, int flags, MessageHandler mh) {
    this(parent, name, pw, ph, defButtonsList, defNoCloseButtons, flags, mh);
  }

  /** Construct a new GDialog (the Table params are unused???)
      @param parent the parent object
      @param name   the name of this GDialog
      @param table  not used?
      @param config not used?
      @param flags  the widget flags
      @param mh     the MessageHandler
   */
  public GDialog (Object parent, String name, Table table, Table config, int flags, MessageHandler mh) {
    super("GDialog",parent,name,flags|TITLE,mh);
    open(); // get text sizes
    setDialog(defButtonsList,-1);
    addTo(parent);
  }

  protected void init (String buttonsList, String noCloseList, int pw, int ph, int flags) {
    this.flags = flags;
    if (pw<0) pw = -pw * tw;
    if (ph<0) ph = -ph * th;
    if (pw==0) pw = 300;
    if (ph==0) ph = 200;
    open(name, pw, ph);
    setDialog(buttonsList,-1);
    setNoCloseButtonsList(noCloseList);
  }

  //public void open (String name, Table values, Table config) {  }

  public void close () {
    if (gdialog != null) gdialog.close();
    if( parent instanceof MPlot) ((MPlot)parent).popupCount--; // Since NXM 3.8.1 This is used to decrement the amount of popups contained inside of MPLot.
    super.close();
  }

  public void setup() {
    bw=DEFBW;
    dialog = th+ta+bw;
    titleh = th+ta;
    sw=bw*2;
    rw=15;
    grid=5;
    pos.w = sw+pw+sw;
    pos.h = titleh+ph+dialog;
    positionDialog();
    layout();
  }

  private void positionDialog() {
    if (gdialog==null) return;
    gdialog.pos.x = (pos.w-gdialog.pos.w)/2;
    gdialog.pos.y = pos.h-dialog+(dialog-gdialog.pos.h)/2;
    gdialog.setBounds(gdialog.pos);
  }

  public void resize (int mode) {
    super.resize(mode);

    // This is being removed because it is causing issues with the sizing of the GDialog over a smaller window.
    // It was added for NXM-2755. It is being removed for the graphics minimal fix NXM-3213.
    // Jeff needs original behavior except when everything will not fit, so making alternate fix (jtm) in 3.9.3 under NXM-3557
//    if ((parent instanceof MWindow) {
//      MWindow mw = (MWindow)parent;
//      if (pos.h > mw.pos.h) pos.h = mw.pos.h;
//    }
    if ((parent instanceof MWindow)) {
      MWindow mw = (MWindow)parent;
      if (pos.h > mw.pos.h) pos.h = Math.max(mw.pos.h, minDialogHeight());
    }
    pw = pos.w-sw-sw;
    ph = pos.h-titleh-dialog;
    if (rollup) return;
    positionDialog();
    if (widgets==null || widgets.size()<=1) return;
    Table.Iterator ti = widgets.iterator();
    GWidget gwl = gdialog;
    for (int i=1; i<ti.size; i++) {
      GWidget gw = (GWidget)ti.values[i];
      // stretch the last widget to fill the dialog ?
      if (i==ti.size-1 && gw.isStretchable()) gw.pos.h = pos.h-dialog-gw.pos.y;
      int w=pw, h=gw.pos.h;
      // handle side-by-side widget placement
      if (gw.is(CONCAT)) w = pw/2;
      if (gwl.is(CONCAT)) { gw.pos.x = sw + pw/2; w = pw - pw/2; }
      gw.fixWidth(w);
      if (w!=gw.pos.w || h!=gw.pos.h) { gw.pos.w=w; gw.pos.h=h; } // gw.setup(); }
      gw.setBounds(gw.pos);
      gwl = gw;
    }
  }

  /** Make sure dialog is big enough to hold widgets, even if it needs to expand
   *  beyond parent height.  Since there is no scroll bar, this is the only way to
   *  make sure everything is able to be viewed.
   *  @return minimum size needed to hold title, dialog, and widgets
   *  @since NeXtMidas 3.9.3 for NXM-3557
   */
  private int minDialogHeight () {
    int minHeight = gdialog.is(CONCAT) ? titleh : titleh + gdialog.pos.h;
    if (widgets==null || widgets.size()<=1) return minHeight;
    Table.Iterator ti = widgets.iterator();
    GWidget gwl = gdialog;
    for (int i=1; i<ti.size; i++) {
      GWidget gw = (GWidget)ti.values[i];
      int h=gw.pos.h;
      // handle side-by-side widget placement
      if (!gw.is(CONCAT)) minHeight += gw.pos.h;
    }
    return minHeight;
  }

  /** Change this widget theme and make sure any internal widget follow.
    @param obj Theme in pbject, string or Table form
  */
  public void setWidgetTheme(Object obj) {
    super.setWidgetTheme(obj);
    if (gdialog !=null) gdialog.setWidgetTheme(obj);
  }

  /**
   *  @deprecated Since NeXtMidas 3.9.0 use {@link #paintComponent(Graphics)}
   */
  @Deprecated
  public void paint() {
	refresh();
  }

  @ProvisionalUseOnly("Treat this as protected and should only be called by subclass paintComponent methods")
  @Override
  public void paintComponent (Graphics g) {
    super.paintComponent(g);
    if (g==null || bw==0) return;
    int x=0, y=0, w=pos.w, h=pos.h;
    int dh = titleh;
    int dd = dialog;
    // draw shading
    int j1=(int)(dh*.15);
    for (int i=1; i<dh; i++) {
      if (i<j1) g.setColor(MColor.getColorMix(theme.cwbg,theme.cwbs,(double)i/j1));
      else      g.setColor(MColor.getColorMix(theme.cwbs,theme.cwms,(double)(i-j1)/(dh-j1-1)));
      g.drawLine(x,y+i,x+w,y+i);
      if (!rollup) g.drawLine(x,h-i,x+w,h-i);
    }
    g.drawLine(x,y,w,y);
    // draw title
    g.setColor(theme.getWidgetTitle());
    g.drawString(title,w/2-titlew/2,titleh/2+ta/2);
    // draw open/close arrow
    int aw=tw*2/3, yo=rollup?-aw:aw;
    shadowarrow(g,pos.w-3*aw,titleh/2+yo,aw,rollup?'d':'u'); // Updated to use g in 3.9.0 for NXM-3213
    if (rollup) return;
    // fill body
    g.setColor(theme.cwms);
    g.fillRect(x,y+dh,w,h-2*dh+1);
    // round edges
    //g.drawLine (x,y,w,y);
    g.drawLine(x,h,w,h);
    g.drawLine(w-1,y,w-1,y+h);
    g.setColor(theme.cwbg);
    g.drawLine(x,y,x,y+h);
    g.drawLine(w-1,y,w-1,y+h);
    g.setColor(theme.cwbs);
    g.drawLine(x+sw,y+dh-1,w-sw-2,y+dh-1);
    g.drawLine(x+sw-1,y+dh,x+sw-1,h-dd-1);
    // draw resize bar
    g.setColor(theme.cwts);
    for (int i=1; i<rw; i+=4) g.drawLine (w-bw-i,h-bw,w-bw,h-bw-i);
    Table.Iterator ti = widgets.iterator();
    for (int i=0; i<ti.size; i++) {
      GWidget gw = (GWidget)ti.values[i];
      if (gw.status!=FRONT) gw.display(FRONT);
    }
  }

  /** Creates a dialog button widget which is a child of this GWidget.
     @param list the button list for this dialog
     @param item the button to display in depressed position
     (must be non-zero, usually -1, for a dialog).
  */
  public void setDialog (String list, int item) {
    if (widgets==null) widgets = new Table();
    int mflags = GMenu.ONEROW|GMenu.BUTTON|GMenu.TOFF|GMenu.NODEPRESS;
    gdialog = new GMenu(null,"DIALOG",list,item,FIX|INLINE|PERM|mflags,this);
    GWidget gw = (GWidget)gdialog;
    minw = Math.max(minw,gw.pos.w);
    if (minw>pw) pw=minw;
    gw.parent = gw.MW = this;
    gw.setWidgetPanel(this);
    widgets.put("DIALOG",gdialog);
    setup();
    addOn(gw.panel);
    gwl = gw;
  }

  public Object addWidget (GWidget gw) {
    if (widgets==null) widgets = new Table();
    minw = Math.max(minw,gw.pos.w+(gwl.is(CONCAT)?gwl.pos.w:0));
    gw.parent = gw.MW = this;
    gw.setFlag(FIX|INLINE|PERM,true);
    gw.setWidgetPanel(this);
    int w=pw, h=gw.pos.h;
    gw.pos.x = sw;
    gw.pos.y = gh+titleh;
    if (gw.is(CONCAT)) w = pw/2;
    else gh += h;
    if (gwl.is(CONCAT)) { gw.pos.x = sw + pw/2; w = pw/2; }
    minh = Math.max(minh,gh);
    if (gh>=ph) { ph=gh; setup(); refresh(); }
    else if (widgets.size()==maxWidgets) h = ph-gw.pos.y;
    if (w!=gw.pos.w || h!=gw.pos.h) { gw.pos.w=w; gw.pos.h=h; } // gw.setup(); }
    gw.setBounds(gw.pos);
    //addOn(gw.panel);   // pre- gwidget Swing conversion
    gw.MW.panel.add(gw.panel);  // JWB - done this way to keep it off the layeredPane
    if (status==FRONT) gw.display(FRONT);
    widgets.put(gw.label,gw);
    gwl = gw;
    // Need to layout() and resize(1) dialog to account for additional widget (Bug 1325)
    // Since 3.9.0 - prevent resizing every time adding menu to dialog.
    if(needsResize) {
      layout();
      resize(1);
    }

    return gw;
  }

  /* Abstract WidgetPanel method that must be here */
  public void layoutWidget (GWidget obj) {}

  /** Get the child widgets, if any.
     @return the table of optional child widgets. Null if there are none.
  */
  public Table getWidgets() { return widgets; }

  private boolean resizing;
  protected int processEventMessage (String name, int info, Object data) {
    if (name.equals("DRAG")) {
      MBox mb = (MBox)data;
      if (resizing) {
        int w = Math.max(minw+sw*2,mb.x+mb.w+rw);
        int h = Math.max(minh+titleh+dialog,mb.y+mb.h+rw);
        if (Math.abs(w-pos.w)>grid || Math.abs(h-pos.h)>grid) {
          pos.w = w; pos.h = h;
          resize(1); refresh();
        }
      }
      else if (mb.y > pos.h-dialog/2 && mb.x > pos.w-dialog/2) {
        resizing = true;
      }
      else if (dragging==1) {
        if (Math.abs(mb.w)>grid || Math.abs(mb.h)>grid) {
          pos.x += mb.w; pos.y += mb.h;
          setBounds(pos);
          dragging = 0;
        }
      }
      return(1);
    }
    if (name.equals("DRAGBOX") && resizing) {
      resizing = false;
      return(1);
    }
    if (name.equals("BUTTON") && info==1 && py<titleh) {
      rollup = !rollup;
      if (rollup) { fullh=pos.h; pos.h=titleh; }
      else { pos.h=fullh; }
      resize(1);
      refresh();
      return(1);
    }
    return -1;
  }


  /** Process DIALOG messages
      @param msg {@inheritDoc}
      @return 1
   */
  public int processMessage (Message msg) {
    if (msg.name.equals("DIALOG")) {
      String action = (String)msg.data;
      // Send message for all buttons except CANCEL
      if (!action.equalsIgnoreCase("CANCEL")) {
        Table msgTab = getWidgets();
        int info = gdialog.getItemIndex(action); // put one-base index of selected button into info for BUG 2490
        sendWidgetMessage(label, info, msgTab);
      }
      if (!is(PERM) && isCloseButton(action)) {
        close();
      }
    }
    return 1;
  }

  /** Get the buttons on the DIALOG window
   *  @return the buttons on the DIALOG window as a comma-delimited list. */
  public String getButtonsList () {
    return gdialog.getItems();
  }

  /** Get the buttons on the DIALOG window which do not force a close when pressed
   *  @return the buttons on the DIALOG window (as a comma-delimited list) which do not force a close when pressed. */
  public String getNoCloseButtonsList () {
    return noCloseButtons;
  }

  /** Is the button pressed a button that will close the dialog box?
      @param buttonName the name of the button to check
      @return <CODE>true</CODE> - if the button will close the dialog <CODE>false</CODE> - otherwise
   */
  public boolean isCloseButton (String buttonName) {
    boolean isClose = true;
    if (noCloseButtons != null) {
      isClose = (Parser.find(noCloseButtons,buttonName,-1) < 0);
    }
    return isClose;
  }

  /** Set the buttons on the DIALOG window
      @param list the comma-delimited list of button names
   */
  public void setButtonsList (String list) {
    gdialog.setItems(list);
  }

  /** Set the buttons on the DIALOG window which do not force a close when pressed.
      @param buttons the comma-delimited list of names of buttons.
   */
  public void setNoCloseButtonsList (String buttons) {
    noCloseButtons = buttons;
  }

  /** Set the maximum number of widgets
   *  @param n the maximum number of widgets
   */
  public void setMaxWidgets (int n) { maxWidgets = n; }
}
