package nxm.hwc.prim;

import nxm.sys.lib.Convert;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Native;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Results;
import nxm.sys.lib.TcpResource;
import nxm.sys.lib.Time;

/**
  NeXtMidas generic hardware controller for devices with a serial control interface.

  Protocols include RS232, USB, and Ethernet.

  @author Jeff Schoen
 */
public class hwdriver extends Primitive {

  private DataFile hwf;
  /** Array that holds info from hw command about inputs */
  private HwArg[] hwa;
  /** Structure that hold info about hw device from the hwconfig file */
  private HwDev hwd;
  private char chan='0';
  private int np;
  /** Specifies the result name to receive data */
  private String drn;
  /** Specifies the filename to receive data. since 2.7.1 */
  private String drf; // emb

  private StringBuffer form = new StringBuffer();

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

  @Override
  public int open() {

    String func = MA.getU(1);
    verbose = MA.getState("/VERBOSE");
    drn = MA.getU("/DRN");
    drf = MA.getU("/DRF");// since 2.7.1: emb added for save results to file

    // open the hardware file (via defaulted switch)
    String hwfn = MA.getCS("/HWF","ENV.HWFILE");
    hwf = new DataFile(M,hwfn);
    hwf.open();
    hwf.close();

    // open and parse device control
    if (func.equals("CONTROL") || func.equals("SET") || func.equals("GET")) {
      hw_open( MA.getS(2) );
      np = MA.numberOf()-2;
      //Number of Parameters -2= np emb
      hwa = new HwArg[np];
      //Hardware arguments = hwa emb
      for (int i=0; i<np; i++) {
        hwa[i] = new HwArg();
        hwa[i].name = MA.getKey(3+i);
        hwa[i].cmd  = (String)hwf.keywords.get(hwa[i].name);
        hwa[i].data = MA.getO(hwa[i].name);
        if (hwa[i].cmd==null) M.error ("Command "+hwa[i].name+" not found for device "+hwd.name);
        // find valid output results name
        String rn = MA.getU(hwa[i].name);
        hwa[i].resname = (rn.length()!=0 && Results.validNameLength(rn,0)==rn.length())? rn : drn;
        //hwa[i].resname = (MR.validNameLength(rn,0)==rn.length())? rn : drn;
      }
    }
    // open and parse switch connect
    else if (func.equals("CON") || func.equals("C")) {
      hw_open("SWITCH");
      np = (MA.numberOf()-1)/2;
      hwa = new HwArg[np];
      for (int i=0; i<np; i++) {
        hwa[i] = new HwArg();
        hwa[i].name = "CON";
        hwa[i].cmd  = (String)hwf.keywords.get(hwa[i].name);
        hwa[i].data  = MA.getS(i*2+2);
        hwa[i].datax = MA.getS(i*2+3);
      }
    }
    // open and parse switch disconnect
    else if (func.equals("DIS")) {
      hw_open("SWITCH");
      np = MA.numberOf()-1;
      hwa = new HwArg[np];
      for (int i=0; i<np; i++) {
        hwa[i] = new HwArg();
        hwa[i].name = "DIS";
        hwa[i].cmd  = (String)hwf.keywords.get(hwa[i].name);
        hwa[i].data  = MA.getS(i+2);
      }
    }
    else {
      M.error("Unsupported function: "+func);
    }

    return NORMAL;
  }

  @Override
  public int process() {
    for (int i=0; i<np; i++) {
      hw_send(hwa[i]);
    }
    return FINISH;
  }

  @Override
  public int close() {
    hw_close();
    return NORMAL;
  }

  private class HwArg {
    String name;      // name of arg
    String cmd;       // instruction string
    String resname;   // results name
    Object data;      // 1st value
    Object datax;     // 2nd value
    int ic,lc;        // current/last parse index
    String formed;    // last formed string
    HwDev dev = hwd;  // device
    int qlen;         // queued read length
    String term;
  }

  private class HwDev {
    String alias;     // name of device
    String name;      // name of device
    int timeout;      // current timeout value
    String term;      // terminators
    int fd;           // raw device handle
    TcpResource tcp;
  }

  private void hwd_open (HwDev hwd, String devName) {
    if (devName.startsWith("SOCKET:")) {
      hwd.tcp = new TcpResource();
      hwd.tcp.setContext(M);
      String dev = devName.substring(7);
      dev = dev.replace("_", ":"); // emb = put string in host:socket format for TcpResource
      //hwd.tcp.setURL(devName.substring(7));
      hwd.tcp.setURL(dev);
      boolean isOpen = hwd.tcp.open();
      if (!isOpen) M.error("Device not opened");
      hwd.fd = 1;
    }
    else {
      hwd.fd = Native.fopen(devName,2);
    }
  }

  private void hwd_close (HwDev hwd) {
    if (hwd.tcp!=null) hwd.tcp.close();
    else Native.fclose(hwd.fd);
  }

  private int hwd_read (HwDev hwd, byte[] buffer, int off, int len) {
    int status,bcount;
    if (hwd.tcp!=null) {
      bcount = (int)hwd.tcp.avail(); // added to fix hang when reading data emb
      status = hwd.tcp.read(buffer, off, bcount);
    }
    //if (hwd.tcp!=null) status = hwd.tcp.read(buffer,off,len);
    else status = Native.freadb(hwd.fd,buffer,off,len);
    return status;
  }

  private int hwd_write (HwDev hwd, byte[] buffer, int off, int len) {
    int status;
    if (hwd.tcp!=null) status = hwd.tcp.write(buffer,off,len);
    else status = Native.fwriteb(hwd.fd,buffer,off,len);
    return status;
  }

  private void hw_open (String alias) {
    hwf.keywords.setScope("TAG=ALIAS"); // to find alias
    String aliasString = (String)hwf.keywords.get(alias);
    if (aliasString==null) throw new MidasException("Alias "+alias+" not found in HW Config file");
    if (verbose) M.info("HwOpen "+aliasString);
    int i = aliasString.indexOf(',');
    int j = aliasString.indexOf('-');
    if (j>0 && j<i) {
      // This if statement is executed if there is a - in the alias name then takes the first
      // character following the -... = debug emb
      i=j; chan=aliasString.charAt(j+1);
    }
    String device = aliasString.substring(0,i); // emb = get device name
    if (hwf.keywords.setScope("TAG="+device)!=1) // to find commands
      M.error("Device "+device+" not found in HW Config file");
    hwd = new HwDev(); // Put info about the device into this structure
    hwd.alias = alias;
    hwd.name = device;
    hwd.timeout = 2;
    hwd.term = "";
    // emb = set terminators for write to device
    if (aliasString.indexOf(",CRLF")>0) hwd.term="\r\n";
    else if (aliasString.indexOf(",CRCR")>0) hwd.term="\r\r";
    else if (aliasString.indexOf(",CR")>0) hwd.term="\r";
    else if (aliasString.indexOf(",LF")>0) hwd.term="\n";
    String devType = Parser.get(aliasString,2);
    String devName = Parser.get(aliasString,3);
    hwd_open(hwd,devName);
    if (hwd.fd<=0) M.error("Opening device Type="+devType+" URL="+devName);
  }

  private void hw_close() {
    if (verbose) M.info("HwClose "+hwd.name+" on "+hwd.fd);
    if (hwd.fd<=0) return;
    hwd_close(hwd);
  }

  private void hw_write (HwArg ha) {
    String string = ha.formed;
    if (hwd.fd<=0) return;
    string += hwd.term;
    byte[] buffer = string.getBytes();
    int status = hwd_write(hwd,buffer,0,buffer.length);
  }

  private String hw_read (HwArg ha, int maxlen) {
    if (hwd.fd<=0) return null;
    int got,status;
    byte[] buffer = new byte[maxlen];
    // timeout loop is in a 10ms poll loop, so mult sec by 100
    int timeout = hwd.timeout*100;
    // check for last character of terminator if enabled in query
    byte term = (ha.term!=null)? (byte)ha.term.charAt(ha.term.length()-1) : -1;
    for (got=0; got<maxlen; ) {
      status = hwd_read(hwd,buffer,got,maxlen-got);
      if (status<0) break;
      got += status;
      if (--timeout < 0) break;
      if (term>0 && status>0 && buffer[got-1]==term) break;
      if (status==0) Time.sleep(0.010);
    }
    if (got<=0) return null;
    String string = new String(buffer,0,got);
    if (ha.term!=null) { // trim off terminator here
      int i = string.indexOf(ha.term);
      if (i>=0) string = string.substring(0,i);
    }
    if (verbose) M.info("HwRead="+string);
    return string;
  }

  /** Purge read buffer?? */
  private void hw_purge() {
    if (hwd.fd<=0) return;
    else {
      byte[] buffer = new byte[8];
      // read any bytes off the queue
      while (hwd_read(hwd,buffer,0,buffer.length) > 0);
    }
  }

  private void hw_send (HwArg ha) {
    // loop through the instruction string
    ha.lc = ha.cmd.indexOf("//");
    if (ha.lc<0) ha.lc = ha.cmd.length();
    // emb = if ha.lc < 0 then there is no comment, set the length of the string
    for (ha.ic=0; ha.ic<ha.lc;) {
      char c = ha.cmd.charAt(ha.ic++);
      if (c==',' || c==' ');
      else if (c=='F') hw_form(ha);     // form string
      else if (c=='Q') hw_queue(ha);    // queue a read
      else if (c=='W') hw_write(ha);    // write string
      else if (c=='P') hw_pause(ha);    // pause time
      else if (c=='D') hw_decode(ha);   // decode string
      else if (c=='V') hw_validate(ha); // validate string
      else if (c=='T') hw_timeout(ha);  // set timeout
      else if (c=='(') hw_repeat(ha);   // start of repeat group
      else M.error("Malformed HWD string: "+ha.cmd.substring(0,ha.lc));
    }
  }

  private void hw_form (HwArg ha) {
    form.setLength(0);
    if (ha.cmd.charAt(ha.ic)!='{') return;
    for (++ha.ic; ha.ic<ha.lc ;) {
      char c = ha.cmd.charAt(ha.ic++);
      if (c=='}') break;
      else if (c=='^') hw_formCaret(ha);
      else if (c=='%') hw_formPrintf(ha);
      else if (c=='\\') hw_formTerm(ha);
      else form.append(c);
    }
    ha.formed = form.toString();
  }

  private void hw_queue (HwArg ha) {
    int n = hw_geti(ha);
    ha.term=null;
    for (; ha.cmd.charAt(ha.ic)=='e';ha.ic++) { // add echo
      n += ha.formed.length();
    }
    if (ha.cmd.charAt(ha.ic)=='t') {            // add default terminator
      ha.ic++;
      ha.term = ha.dev.term;
      n += ha.term.length();
      //Adds num of bytes in the terminator to the lenght of the buffer  emb
    }
    if (ha.cmd.charAt(ha.ic)=='s'){             // add specified terminator
      ha.ic++;
      ha.term = ""+ha.cmd.charAt(ha.ic++);
      n += ha.term.length();
    }
    ha.qlen = n;
    hw_purge();                                 // purge old data
    if (verbose) M.info("Queue read of "+n+" bytes");
  }

  private void hw_pause (HwArg ha) {
    int d = hw_geti(ha);
    if (ha.cmd.charAt(ha.ic)=='m') ha.ic++;     // in milliseconds
    else d *= 1000;                             // in seconds
    Time.usleep(d*1000);
  }

  private void hw_decode (HwArg ha) {
    String data = hw_read(ha,ha.qlen);
    if (ha.cmd.charAt(ha.ic)=='{') {            // with formatting
      int id=0;                                 // index in data string
      int loopStart=-1, loopCount=0;
      form.setLength(0);                        // string buffer for result
      for (++ha.ic; ha.ic<ha.lc ;) {
        char c = ha.cmd.charAt(ha.ic++);
        if (c=='}') break;
        else if (c=='y') id=hw_decodeYankSkip(ha,data,id,true);
        else if (c=='x') id=hw_decodeYankSkip(ha,data,id,false);
        else if (c=='i') form.append(ha.cmd.charAt(ha.ic++));
        // loop form is (...)NN - nesting not supported yet
        else if (c=='(') { loopStart=ha.ic; loopCount=0; }
        else if (c==')') { loopCount++; if (hw_geti(ha)-loopCount>0) ha.ic=loopStart; }
        else M.error("Malformed Decode command in: "+ha.cmd);
      }
      data = form.toString();
    }
    if (ha.resname.length()>0) {
      MR.getPrevious().put(ha.resname, data);   // HWDRIVER called from macro, so set
                                                //   result in previous result table
      M.info(data);
    }
    else M.println("HW Alias="+hwd.alias+" "+ha.name+"="+data); // since 3.7.1 using M.println instead of MT.writeln to allow logging - NXM-1813
  }

  private int hw_decodeYankSkip (HwArg ha, String data, int id, boolean yank) {
    int len = hw_geti(ha);      // character count
    int id1 = id, id2 = data.length();
    if (len>0) id = id1+len;    // yank/skip of specified characters
    else {                      // not a number, must be t or T for yank skip to ?
      char c = ha.cmd.charAt(ha.ic++);
      if (c!='t' && c!='T') M.error("Malformed Yank/Skip in: "+ha.cmd);
      char e = ha.cmd.charAt(ha.ic++); // character to match
      if (e=='#') // match any number
        for (; id<id2 && (data.charAt(id)<'0' || data.charAt(id)>'9'); id++);
      else        // match exact character
        for (; id<id2 && data.charAt(id)!=e; id++);
      if (c=='T') id++; // inclusive, add matching character
    }
    if (yank) form.append(data.substring(id1,id));
    return id;
  }

  private void hw_validate (HwArg ha) {
    String data = hw_read(ha,ha.qlen);
    hw_form(ha);
    // now validate
  }

  private void hw_timeout (HwArg ha) {
    ha.dev.timeout = hw_geti(ha);
  }

  private void hw_repeat (HwArg ha) {
    ha.ic--; // back to opening paren
  }

  private int hw_geti (HwArg ha) {
    // Format the ASCII char c to an integer value  emb
    int value=0; char c=ha.cmd.charAt(ha.ic);
    for (; c>='0' && c<='9'; c=ha.cmd.charAt(++ha.ic)){
      value = value*10 + (int)c - (int)'0';
    }
    return value;
  }

  private void hw_formCaret (HwArg ha) {
    char c=ha.cmd.charAt(ha.ic++);
    switch (c) {
      case 'f': form.append(Convert.o2f(ha.data)); break;
      case 'd': form.append(Convert.o2l(ha.data)); break;
      case 's': form.append(Convert.o2s(ha.data)); break;
      case 'x': form.append(Convert.l2x(Convert.o2l(ha.data))); break;
      case 'c': form.append(chan); break;
      case 'n': form.append(0); break;
      case 'e': form.append(ha.formed); break;
      case 't': form.append(ha.dev.term); break;
      case 'm': form.append(Parser.get(Convert.o2s(ha.datax),Convert.o2l(ha.data))); break;
      case '*': for (int i=0,n=hw_geti(ha); i<n || i==0; i++) form.append('*'); break;
      case 'v':
    }
  }

  private void hw_formPrintf (HwArg ha) {
    M.error("HWD printf format require JDK1.5 which is not standard yet");
  }

  private void hw_formTerm (HwArg ha) {
    char c=ha.cmd.charAt(ha.ic++);
    switch (c) {
      case 'r': form.append('\r'); break;
      case 'n': form.append('\n'); break;
      default : M.error("Terminator code="+c+" not recognized");
    }
  }
}
