package nxm.sys.libg;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

import nxm.sys.inc.MessageHandler;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.Message;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;

/**
  A class to implement a graphical text prompt widget.
  @author Jeff Schoen
*/
public class GPrompt extends GWidget implements ActionListener {
  private String seed;
  private JTextField jtexti;
  private int pw;
  private int maxheight;
  private int maxwidthl;
  private int digits=10;
  private int mouseMode=0;
  private boolean edit=true;
  private boolean isSetup=false;
  private MBox bp,bt,bc;        // boxes for prompt,text,control
  private int aw;
  private int ah;
  private String raux;
  private String filt;
  private GFiles gf = null;     // BUG 2022

  /** widget flags  */
  public static final String flagsList = GWidget.flagsList+",Password,LimitChars,NoWarn";  // Password added for Bug 2223

  /** flags mask: a password field */
  public static final int PASSWORD   = 0x0100;                             // Password added for Bug 2223

  /** flags mask: limit the number of characters allowed */
  public static final int LIMITCHARS = 0x0200;                             // Password added for Bug 2223

  /** flags mask: do not display a warning if the character limit exceeded */
  public static final int NOWARN     = 0x0400;

  /**
     Construct a new GPrompt initialized with the specified text to be displayed,
     and wide enough to display the requested number of columns.
     @param parent the parent object
     @param name the name of this GPrompt
     @param seed the text to be displayed
     @param digits the number of columns
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GPrompt (Object parent, String name, String seed,
                  int digits, int flags, MessageHandler mh) {
    super("GPrompt",parent,name,flags,mh);
    open (name, seed, digits);
    addTo (parent);
  }

  /**
     Construct a new GPrompt initialized with the specified text to be displayed,
     and wide enough to display the default number of columns(10).
     @param parent the parent object
     @param name the name of this GPrompt
     @param seed the text to be displayed
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GPrompt (Object parent, String name, String seed,
      int flags, MessageHandler mh) {
    super("GPrompt",parent,name,flags,mh);
    open (name, seed, digits);
    addTo (parent);
  }

  /**
     Instantiate a new GPrompt for a filename with a popup GFiles widget when clicked.
     @param parent the parent object
     @param name the name of this List
     @param seed the default file name
     @param filt the filename filter
     @param raux the read aux list
     @param M    the Midas context reference
     @param aw the width in characters
     @param ah the height in lines
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GPrompt (Object parent, String name, String seed, String filt, String raux, Midas M,
                  int aw, int ah, int flags, MessageHandler mh) {
    super("GFile",parent,name,flags,mh);
    this.filt = filt;
    this.raux = raux;
    this.M = M;
    this.aw = aw;
    this.ah = ah;
    open (name, seed, digits);
    addTo (parent);
  }

  /** Set the widget theme for the prompt and the input text component
    @param o The theme in String, Table or object form
  */
  public void setWidgetTheme(Object o) {
    // jph - I added this method because -- Seems that the widget theme is not yet set in the
    //       open() method below, so  I override the setWidgetTheme(..) method in GWidget
    //       to affect the input text colors
    super.setWidgetTheme(o);
    if (jtexti != null) {
      jtexti.setForeground(theme.cwfg);
      jtexti.setBackground(theme.cwbg);
    }
  }

  /** Opens the graphics window and initializes the prompt with the given parameters.
      @param name not used???
      @param seed the text to be displayed
      @param digits the number of columns
   */
  public void open (String name, String seed, int digits) {
    super.open();
    this.digits = digits;
    this.seed = is(LIMITCHARS)? truncateText(seed,digits,true) : seed;
    pw = stringWidth(title);
    setupTextField ();
    resize(1);
  }
  /** Setup GPrompt text field */
  public void setupTextField () {
    edit = !is(PERM) && !is(NOEDIT); // Do not allow prompt to be editable if NOEDIT is set, even when it is not attached to panel (4.1.2)
    setup();
    if (headless) {
      return; // Since NeXtMidas 3.5.0. Break out if in Headless state. BUG 2817
    }
    if (is(PASSWORD) && !isFileChooser()) jtexti = new MJPasswordField(seed, digits); // Bug 2304: don't apply GFiles flags to GPrompts
    else                                  jtexti = new MJTextField (seed,digits);
    jtexti.setFont(font);
    // Seems the widget theme is not yet set here, so I override the
    // setWidgetTheme(..) method in GWidget to affect the input text colors
    jtexti.setForeground(theme.cwfg);
    jtexti.setBackground(theme.cwbg);
    jtexti.addActionListener(this);
    if (edit) {
      panel.add(jtexti,0);
      jtexti.requestFocus();
    }
    if (is(LIMITCHARS)) jtexti.setDocument(new MPlainDocument(digits));
  }

  public void setup() {
    int h = th+ta-(edit?0:compression);
    if (is(INLINE)) bw=0; else bw=DEFBW;
    bp = new MBox (bw+tw, bw, pw, h);
    bc = new MBox (bp.x+bp.w, bw, th+tw, h);
    bt = new MBox (bc.x+bc.w, bw, tw*digits, h);
    bbox = new MBox(bt.x-3,bt.y+ta/2+2,bt.w+5,bt.h-ta/2-6);
    pos.w = bt.x+bt.w+tw+bw;
    if (fixWidth>pos.w) { pos.w=fixWidth; bt.w=pos.w-bt.x-bt.w-bw; }
    pos.h = bw+h+bw;
    layout();
    isSetup=true;
  }

  public void setTitle (String title) {
    this.title = title;
    pw = titlew = stringWidth(title);
    if (isSetup) { setup(); refresh(); }
  }

  public void resize (int mode) {
    super.resize(mode);
    bt.w = pos.w - bt.x - bw - tw;
    placeTextInput (bc,jtexti);
  }

  /**
   *  @deprecated Since NeXtMidas 3.9.0 to avoid graphical timing issues
   *  use {@link #paintComponent(Graphics)} (for NXM-3213)
   */
  @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);

    int bf=SB_DEF; if (is(INLINE)) bf=SB_FLAT;
    shadowbox(g,0,0,pos.w,pos.h,bf);
    int ty = pos.h/2 + ta/2;
    setColor(g,theme.cwti);
    setTextClip(g, panel, title, bt.x);  // Bug 2473: keep text within shadowbox
    g.drawString(title,bp.x,ty);
    setColor(g,theme.cwfg);
    if (!edit) {
      String tmpstr = (jtexti instanceof MJPasswordField)? getFillChars() : seed;  // do not display the plain text if password
      setTextClip(g, panel, tmpstr, bt.x);  // Bug 2473: keep text within shadowbox
      g.drawString(tmpstr,bt.x,ty);
    }
    setValueBox(bt.x-3,bt.y+ta/2,bt.w+5,bt.h-ta/2-4);
    drawValueBox(g,1);
    drawArrows(g);
  }

  /** Get a String of characters to display in a password field instead of the actual text.
      The fill String will be as long as the actual text, though, and be made up of the
      MJPasswordField echo char.
      @return A String of the MJPasswordField echo characters as long as the actual text.
      @since NeXtMidas 2.9.0
   */
  private String getFillChars () {
    if (!(jtexti instanceof MJPasswordField)) return seed;  // not a password field
    return StringUtil.padRight("", seed.length(), ((MJPasswordField)jtexti).getEchoChar());
  }

  private void drawArrows (Graphics g) {
    drawDiamond(g, bc.x+bc.w/2, bc.y+bc.h/2, tw*2/3, mouseMode);
  }

  public void actionPerformed (ActionEvent e) {
    String text;
    text = jtexti.getText();
    panel.remove(jtexti);
    panel.requestFocus();
    seed = text;
    edit = false;
    setup();
    sendMessage();
    if (!is(PERM)) close();
   }

  /**
     Sets the value for this GPrompt and sends a widget message.
     @param text the new value
   */
  public void setAction (String text) {
    setActionType(); // check whether the action was triggered by a mouse click
    setValue(text);
    sendMessage();
  }

  public void setValue (String value) {
    if (is(LIMITCHARS)) value = truncateText(value,digits, true);
    seed = value;
    refresh();
  }

  public Object getValue() {
    return seed;
  }

  /** Does a substring of the text if necessary but also provides a warning message back
      to the user if desired. Used in conjunction with {@link #LIMITCHARS}
      @param text The text to (possibly) truncate
      @param feedback Whether or not to send a message to the terminal if the text was truncated
      @return Truncated text
      @since NeXtMidas 2.9.0
   */
  private String truncateText (String text, int limit, boolean feedback) {
    if (isFileChooser()) return text;  // Bug 2304: Don't apply GPrompt flags to a GFiles
    String result;
    if (text.length() <= limit) return text;
    else result = text.substring(0,limit);
    if (feedback) Shell.info("GPrompt: Input text '"+result+"' truncated due to limit of "+limit+" characters");
    return result;
  }

  private void sendMessage() {
    if (is(MONITOR)) M.results.put(monitorName,seed);
    sendWidgetMessage (label,actionType,seed);
  }

  /**
     Poll for change in monitor.
     @return <table><caption style="visibility:hidden">""</caption><tr><td><code>true</code></td><td>&nbsp;-&nbsp;if there is
     a monitor for this GPrompt and the value has changed</td></tr>
     <tr><td><code>false</code></td><td>&nbsp;-&nbsp;otherwise</td></tr></table>
   */
  public boolean poll() {
    if (monitorName==null || !is(MONITOR)) return false;
    String newval = M.results.getS(monitorName);
    if (newval==null || newval.equals(seed) ) return false;
//    setAction(newval);
    setValue(newval);
    sendWidgetMessage (label,CHANGED_BY_POLL,newval);
    return true;
  }

  public String getLegend() {
    if (mouseMode==0) return "";
    else if (mouseMode==4) return "(MM=Configure)";
    else return "(LM=Edit,RM=Edit)";
  }

  /** Process event messages for this widget locally. */
  @Override
  public int processEventMessage (String name, int info, Object data) {
    if (name.equals("POINTER")) {
      int mm;
      if (info<0 || is(NOEDIT)) mm=0;
      else if (px>bc.x+bc.w) mm=8;
      else mm=4;
      if (mm!=mouseMode) {
        mouseMode=mm;
        refresh(bc);
        if (info==0) info=1;
      }
      if (is(VBOXMO) && Math.abs(info)==9) refreshValue();
      formatReadOut(info);
    }
    if (!name.equals("BUTTON")) return -1;
    if (info==3 && !is(PERM)){
      sendMessage();
      close();
    }
    else if (info==1 && px>bc.x && isFileChooser()) {
      gf = new GFiles (parent,"GFILE",seed,filt,raux,M,aw,ah,(flags&~PERM),this);
    }
    else if (info==2 && px < bc.x) {
      configure();
    }
    else if (info>0 && px>bt.x && !is(NOEDIT) && !edit) {
      edit=true;
      setup();
      jtexti.setText(seed);
      panel.add(jtexti,0);
      placeTextInput (bc,jtexti);
      jtexti.requestFocus();
    }
    return 1;
  }

  /** Is this prompt a file chooser prompt type (GFile)
   *  @return true if this prompt is a file chooser prompt type (GFile) */
  public boolean isFileChooser() {
    return (filt !=null && raux != null);
  }

  /** process  message */
  public int processMessage (Message msg) {
    if (status==EXIT);
    else if (msg.name.equals("GPROMPT.CFG")) {
      flags = msg.info;
      setup(); refresh();
    }
    else if (msg.name.equals("GFILE")) {
      filt = gf.getFilter();
      raux = gf.getAuxList();
      setAction((String)msg.data);
      if (!is(PERM)) close();
    }
    else return super.processMessage(msg);
    return -1;
  }

  /** set the read aux list
      @param aux the new read aux list
   */
  public void setAux (String aux) {
    this.raux = aux;
  }
  /** get the current read aux list
      @return the current read aux list
   */
  public String getAux () {
    return raux;
  }
  /** set the filename filter
      @param filt the new filename filter
   */
  public void setFilter (String filt) {
    this.filt = filt;
  }
  /** get the current filename filter
      @return the current filename filter
   */
  public String getFilter () {
    return filt;
  }
  /** get the current text to be displayed
      @return the current text to be displayed
   */
  public String getSeed () {
    return seed;
  }
  /** get the current number of columns
      @return the current number of columns
   */
  public int getDigits () {
    return digits;
  }

  /**
     Returns a string representation of this GPrompt's value.
     @return the string representation of this GPrompt's value
   */
  public String toString() {
    return toList((String)getValue(),"");
  }

  /** Sets the color of the selected text in the GPrompt
      @param c The color to set to
      @since NeXtMidas 2.9.0
   */
  public void setSelectedTextColor (Color c) {
    jtexti.setSelectedTextColor(c);
  }

  /** Gets the color of the selected text in the GPrompt
      @return The color
      @since neXtMidas 2.9.0
    */
  public Color getSelectedTextColor () {
    return jtexti.getSelectedTextColor();
  }

  /** Sets the background selection color of the text field
      @param c The Color to set
      @since NeXtMidas 2.9.0
    */
  public void setSelectionColor (Color c) {
    jtexti.setSelectionColor(c);
  }

  /** Gets the background color of the text field selection
      @return The color
      @since NeXtMidas 2.9.0
    */
  public Color getSelectionColor () {
    return jtexti.getSelectionColor();
  }

  /** Sets the color of the selected text
      @param color The color to set to
      @since NeXtMidas 2.9.0
   */
  public void setSelectedTextColor (String color) {
    setSelectedTextColor(MColor.getColor(color));
  }

  /** Gets the color of the selected text
      @return The color
      @since neXtMidas 2.9.0
   */
  public String getSelectedTextColorString () {
    return MColor.toString(getSelectedTextColor());
  }

  /** Sets the background selection color of the selection
      @param c The Color to set
      @since NeXtMidas 2.9.0
   */
  public void setSelectionColor (String c) {
    setSelectionColor(MColor.getColor(c));
  }

  /** Gets the background color of the selection
      @return The color
      @since NeXtMidas 2.9.0
   */
  public String getSelectionColorString () {
    return MColor.toString(getSelectionColor());
  }

  /////////////////////// NESTED CLASS ////////////////////////////////////
  // TODO: If this is useful outside of just prompts, may need to move out of GPrompt

  /** A class to enable actions on the input of a Text Field, such as to limit the
      number of characters
   */
  public class MPlainDocument extends PlainDocument {
    /** NOTE: this should be changed if the structure/fields of this class changes. */
    private static final long serialVersionUID = 2012021000311L; // for Serializable interface

    /** prompt character limit */
    private int limit;

    /** MPlainDocument Constructor
        @param limit prompt character limit
     */
    public MPlainDocument (int limit) {
      super();
      this.limit = limit;
    }

    public void insertString (int offset, String str, AttributeSet attr)
        throws BadLocationException {
      if (str == null) return;

      if ((getLength() + str.length()) <= limit) {
        super.insertString(offset, str, attr);
      }
      else  if ((flags&NOWARN)==0) M.warning("GPrompt: Text not accepted. Character limit of "+digits+ " has been reached.");
    }
  } // end inner class MPlainDocument


}
