package nxm.sys.libg;

import java.awt.Graphics;

import nxm.sys.inc.Filter;
import nxm.sys.inc.MessageHandler;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.Format;
import nxm.sys.lib.Message;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Pipe;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Time;

/**
     A class to implement a graphical Pipe Monitor widget.
     <br><br>
     <table>
       <caption style="visibility:hidden">""</caption>
       <tr> <td><i>Mouse actions:</i></td> </tr>
       <tr> <td>Left click over Pipe Name:</td><td>enters the  Pipe Selection Menu</td> </tr>
       <tr> <td>Middle Click over Pipe Name:</td><td>enters the Configuration Menu</td> </tr>
       <tr> <td>Left click over Slider Box:</td><td>toggles Rate Display (MBytes/sec)</td>  </tr>
       <tr> <td>Middle Click over Slider Box:</td><td>toggles display of the Slider Box</td> </tr>
       <tr> <td>Right Click over the Pipe Monitor:</td><td>displays Status Provider</td> </tr>
       <tr></tr>
       <tr> <td><i>Flags:</i></td> </tr>
       <tr> <td>Slider:</td><td>toggles the Slider Box on/off <td> </tr>
       <tr> <td>ShowRate:</td><td>toggles the display of text to show MBytes per second </td> </tr>
     </table>
     <br>
   @author Troy Deel / Neon Ngo
 */
public class GPipeMonitor extends GWidget {

  /** list of valid flags for a pipe monitor */
  public static final String flagsList = GWidget.flagsList+",Slider,ShowRate";

  /** flags mask: show slider bar with read/write pointers */
  public static final int SLIDER = 0x0100;

  /** flags mask: show numeric value of data rate in display */
  public static final int SHOWRATE=0x0200;

  /** Special constant value for no pipe selected. Since NeXtMidas 3.3.1. */
  private static final String NOPIPE = "NOPIPE";

  private Midas M;
  private double pread,pwrite,prate,ptime;
  private int pw,iw,ip;
  private String prompt,format,sval;
  private MBox bp,bt,bc,br; // boxes for prompt,text,control,pipe readout
  private int digits; // digit select logic
  private int promptWidth=0, mouseMode=0;
  private Pipe pipe;
  private Format nf;
  private String[] pipeNames;

  /**
     Construct a new GPipeMonitor and initialize parameters.
     @param parent the parent object
     @param name the name of this pipe monitor
     @param M the Midas context
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GPipeMonitor (Object parent, String name, Midas M,
      int flags, MessageHandler mh) {
    super("GPipeMonitor",parent,name,flags,mh);
    open (name,M);
    addTo (parent);
  }

  /**
     Construct a new GPipeMonitor, initialize parameters, and set up
     to display data for the given Pipe.
     @param parent the parent Object
     @param pipe the Pipe to be monitored
     @param flags the widget flags
     @param mh the MessageHandler
   */
  public GPipeMonitor (Object parent, Pipe pipe,
      int flags, MessageHandler mh) {
    super("GPipeMonitor", parent, pipe.getName(), flags, mh);
    open (pipe);
    addTo (parent);
  }

  /**
     Open the graphics display for this pipe monitor using the given
     Pipe's name for the prompt name.
     @param pipe the Pipe to be monitored
   */
  public void open (Pipe pipe) {
    super.open();
    prompt = pipe.getName();
    promptWidth = stringWidth(prompt);
    nf = new Format();
    setFormat("##0.0#####");
    setup();
  }

  /**
     Open the graphics display for this pipe monitor with the given
     prompt name and Midas context.
     @param name the prompt name
     @param M the Midas context
   */
  public void open (String name, Midas M) {
    super.open();
    this.M = M;
    prompt = name;
    if (prompt.equals("")||prompt.equals("VALUE")) prompt = NOPIPE;
    getPipeNames();
    nf = new Format();
    setFormat("##0.0#####");
    setup();
  }

  /**
     If the SHOWRATE flag is set for this pipe monitor, the rate
     will be displayed using the default decimal format pattern (##0.0##) unless
     this method is called. This method changes the formatting using the
     given format pattern string.
     @param numFormat the decimal format pattern string
     (See the {@link Format} class for valid pattern Strings.)
   */
  public void setFormat (String numFormat) {
    if (numFormat==null || numFormat.length()==0) return;
    this.format = numFormat;
    digits = numFormat.length();
    nf.setFormat(format);
    setup();
  }

  private void getPipeNames () {
    promptWidth = stringWidth(NOPIPE);
    pipeNames = M.pipes.getKeys(PipeFilter.INSTANCE);
    ip=-1;
    for (int i=0; i<pipeNames.length; i++) {
      promptWidth = Math.max(promptWidth,stringWidth(pipeNames[i]));
      if (prompt.equals(pipeNames[i])) ip=i;
    }
  }

  public void setup () {
    if (is(INLINE)) bw=0; else bw=DEFBW;
    int h = th+ta-compression;
    int w = tw*digits;
    pw = promptWidth;
    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+h/2-th/2, w, th);
    pos.w = bt.x+bt.w+tw+bw;
    pos.h = h+bw+bw;
    if (fixWidth>pos.w) {
      pos.w = fixWidth;
      bt.w = pos.w-bt.x-bw-tw;
    }
    if (M!=null && M.pipes!=null) {
      Object npipe = M.pipes.get(prompt);
      if (npipe == null || npipe instanceof Pipe) { // since 3.3.1: prevent ClassCastException (Bug 2716)
        pipe = (Pipe) npipe;
      }
    }
    layout();
  }

  @Override
  public void resize (int mode) {
    super.resize(mode);
    bt.w = pos.w-bt.x-bw-tw;
  }

  /**
   * @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) {
    // Significant reworking of this code that used to be in paint to avoid
    // graphical timing issues (in 3.9.0 for NXM-3213)
    super.paintComponent(g);

    int ty = pos.h/2 + ta/2;
    shadowbox(g,0,0,pos.w,pos.h,SB_FLAT,SB_DEF);
    g.setColor(theme.cwfg);
    g.drawString(prompt,bp.x,ty);
    drawDiamond(g, bc.x+bc.w/2, bc.y+bc.h/2+1, tw*2/3, mouseMode); // draw arrows - updated to use g in 3.8.1 for NXM-3213

    // Refresh the graphics display of this pipe monitor's data...
    if (is(SLIDER)) { // DrawData incorporated into this method in 3.9.0 for NXM-3213
      shadowbox(g,bt.x,bt.y,bt.w,bt.h,SB_DEF);
      int writeX=1,readX=0;
      double factor = 1.0;
      if (pipe!=null) {
        factor = (double)(bt.w-4)/pipe.getSize();
        writeX = (int) ((pwrite % pipe.getSize()) * factor);
        readX  = (int) ((pread  % pipe.getSize()) * factor);
      }
      // This is the pipe monitor draw area
      g.setColor (theme.cwfh);
      //  Set and draw slider read/write pointers
      if (writeX > readX){
        iw = writeX-readX+1;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
      }
      else if (writeX < readX) {
        iw = bt.w-4-readX;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
        iw = writeX;
        g.fillRect(bt.x+2,bt.y+2,iw,bt.h-4);
      }
      else if (pwrite-pread == pipe.getSize()) {
        iw = bt.w-4;
        g.fillRect(bt.x+2,bt.y+2,iw,bt.h-4);
      }
      else {
        iw = writeX-readX+1;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
      }
    }
    else {
      g.setColor(theme.cwbg);
      g.fillRect(bt.x,bt.y,bt.w,bt.h);
    }
    if (is(SHOWRATE) && !NOPIPE.equals(prompt)){
      sval = nf.form(prate);
      g.setColor (theme.cwfg);
      g.drawString(sval,bt.x+2,bt.y+ta);
    }
  }


  /** Refresh the graphics display of this pipe monitor's data.
   *  @deprecated Since NeXtMidas 3.9.0 to avoid graphical timing issues
   *  method incorporated into {@link #paintComponent(Graphics)} (for NXM-3213)
   */
  @Deprecated
  public synchronized void drawData () {
    if (is(SLIDER)) {
      shadowbox(bt.x,bt.y,bt.w,bt.h,SB_DEF);
      int writeX=1,readX=0;
      double factor = 1.0;
      if (pipe!=null) {
        factor = (double)(bt.w-4)/pipe.getSize();
        writeX = (int) ((pwrite % pipe.getSize()) * factor);
        readX  = (int) ((pread  % pipe.getSize()) * factor);
      }
      // This is the pipe monitor draw area
      g.setColor (theme.cwfh);
      //  Set and draw slider read/write pointers
      if (writeX > readX){
        iw = writeX-readX+1;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
      }
      else if (writeX < readX) {
        iw = bt.w-4-readX;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
        iw = writeX;
        g.fillRect(bt.x+2,bt.y+2,iw,bt.h-4);
      }
      else if (pwrite-pread == pipe.getSize()) {
        iw = bt.w-4;
        g.fillRect(bt.x+2,bt.y+2,iw,bt.h-4);
      }
      else {
        iw = writeX-readX+1;
        g.fillRect(bt.x+2+readX,bt.y+2,iw,bt.h-4);
      }
    }
    else {
      g.setColor(theme.cwbg);
      g.fillRect(bt.x,bt.y,bt.w,bt.h);
    }
    g.setColor (theme.cwfg);
    if (is(SHOWRATE) && !NOPIPE.equals(prompt)){
      sval = nf.form(prate);
      g.drawString(sval,bt.x+2,bt.y+ta);
    }
  }

  private void sendMessage () {
    //sendWidgetMessage (label,0,getValueObject());
  }

  public String getLegend () {
    if (mouseMode==4) return "(LM=SelectPipe,MM=Configure)";
    else return "(LM=ShowDetails,MM=ToggleSlider,RM=ToggleRate)";
  }

  /** Process event messages for this widget locally.
   *  @return 1 after processing BUTTON messages, -1 otherwise
   */
  @Override
  public int processEventMessage (String name, int info, Object data) {
    if (name.equals("POINTER")) {
      int mm; if (info<0) mm=0;
       else if (px<bc.x) mm=4;
       else if (px<bc.x+bc.x+bc.w) mm=(py<pos.h/2)?1:2;
       else mm=8;
      if (mm!=mouseMode) {
        mouseMode=mm;
        refresh(bc);
        if (info==0) info=1;
      }
      formatReadOut(info);
    }
    if (!name.equals("BUTTON")) return -1;
    int button = info;
    // The name field
    if (button>0 && px < bc.x) {
      if (button==1 && M!=null) {
        getPipeNames();
        String pipeList = NOPIPE + StringUtil.toStringBuilder(pipeNames, ",", null, false); // convert to comma separated list
        // since 3.3.1: ip+2 to pre-select current item (+1 to skip NOPIPE AND +1 to convert 0-base to 1-base)
        new GMenu(parent, "Select Pipe", pipeList, ip+2, 0, this);
      }
      if (button==2) configure();
      if (button==3 && !is(PERM)) close();
    }
    // The diamond toggle
    else if (button>0 &&  px < bc.x+bc.w) {
      if (pipeNames.length==0) getPipeNames();
      if (pipeNames.length==0) return 1;
      if (button==1 && py>=bc.h/2) ip++;
      if (button==1 && py<bc.h/2) ip--;
      if (button==3) ip--;
      if (ip<0) ip = pipeNames.length-1;
      ip = ip % pipeNames.length;
      prompt = pipeNames[ip];
      setup();
      refresh();
    }
    // The data region
    else if (button>0 && px>bt.x-4 && px<pos.w) {
      if (button==1 && pipe!=null) new GQuery (MW,prompt,pipe,0,this);
      if (button==2) flags ^= SLIDER;
      if (button==3) flags ^= SHOWRATE;
      refresh();
    }
    return 1;
  }

  /**
     Process configuration and pipe selection messages.
     @param msg {@inheritDoc}
     @return always 1
   */
  public int processMessage (Message msg) {
    if (msg.name.equals("GPIPEMONITOR.CFG")) {
      setFlags(msg.info);
    } else if (msg.name.equals("SELECT PIPE")) {
      setValue( (String)msg.data );
    }
    return 1;
  }


  public void setValue (String value) {
    if (prompt.equals(value)) return;   // nothing to do
    if (value.equals(NOPIPE)) {  // special case for no pipe selected / desired
      prompt = value;
      pipe = null;
    }
    else {
      for (int i=0; i<pipeNames.length; i++) {
        if (value.equals(pipeNames[i])) {
          prompt = value;
          ip = i;
          break;
        }
      }
    }
    setup();  // needed to stop drawing when no pipe or pipe not active at change
  }

  public Object getValue () {
    return prompt;
  }

  /**
     Poll for change in pipe data.
     @return <code>true</code> - if the pipe display needs to be refreshed<br>
     <code>false</code> - if the pipe is either 1) null or 2) non-null
     and data display is current
   */
  public boolean poll () {
    if (pipe==null) return false;
    double write = pipe.getInByte();
    double read = pipe.getOutByte();
    double time = Time.current();
    if (time-ptime>1) {
      prate = pipe.getRate() * 1e-6;
      ptime = time;
      Object npipe = M.pipes.get(prompt);
      if (npipe!=null && npipe!=pipe && npipe instanceof Pipe) { // since 3.3.1: prevent ClassCastException (Bug 2716)
        pipe = (Pipe) npipe;
      }
    }
    else if (write==pwrite && read==pread) return false;
    pwrite = write;
    pread = read;
    refresh (bt);
    return true;
  }

  /** Filter to only allow instances of {@link nxm.sys.lib.Pipe}.
      @since NeXtMidas 3.3.1
   */
  static enum PipeFilter implements Filter {
    INSTANCE; // enum singleton pattern
    @Override
    public boolean accept (Object entry) {
      return (entry instanceof Pipe);
    }
  }
}
