package nxm.sys.lib;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import nxm.sys.inc.DataTypes;
import nxm.sys.inc.RmifInterface;

/**
  Resource for implementing the Midas File Transfer (MFTP) Protocol client.

  This file is served by an RMIF server using the RMIF protocol.
  The RMIF header has an 8-byte adjunct containing the byte offset.

  <pre>General format:
    mftp://[localport/]remotehost:remoteport/filename
  e.g. (using default localport, i.e. first available port from 7070 to 7080)
    mftp://myserver:9000/test1000.prm
  </pre>
  @author Jeff Schoen
*/
public class MftpResource extends IOResource implements DataTypes {

  private DatagramSocket dsock;
  private byte[] buffi,buffo;
  private DatagramPacket dpacki,dpacko;
  private String rhost,rname;
  private int port,rport;
  private InetAddress raddr;
  private int MAXPKTBUF=65536;
  private int defaultMftpPort=7070;
  private int lastMftpPort=7080;
  private int timeOut=2;   // in seconds
  private int pktlen=8192; // in bytes
  private int pktrate=0;   // in bytes/sec
  private long length=0;
  private boolean dbg=false;

  /** Mftp Default Constructor */
  public MftpResource() { }

  public void setURL (String str) {
    super.setURL(str);
    // peel off various header formats
    if (str.startsWith("mftp://")) str = str.substring(7);
    else if (str.startsWith("mftp:/")) str = str.substring(6);
    else if (str.startsWith("mftp:")) str = str.substring(5);
    int i = str.indexOf("/");
    int j = str.indexOf(":");
    int k = str.indexOf("/",i+1);
    if (i<j) {  		// has localport
      port = Convert.s2l(str.substring(0,i));
    } else {  			// default localport
      port = -1;
      k=i; i=-1;
    }
    rhost = str.substring(i+1,j);
    rport = Convert.s2l(str.substring(j+1,k));
    rname = str.substring(k+1);
  }

  /** test existence of the resource */
  public boolean exists () {
    sendMftpPacket("{FUNC=EXIST,NAME="+rname+"}");
    String str = recvMftpPacket();
    if (str==null) return false;
    Table t = new Table(str);
    String s = t.getS("EXISTS");
    boolean exists = (s!=null) && s.equals("Y");
    return exists;
  }

  /** open the resource */
  public boolean open () {
    isOpen = false;
    sendMftpPacket("{FUNC=OPEN,NAME="+rname+"}");
    String str = recvMftpPacket();
    if (str==null) return false;
    Table t = new Table(str);
    length = (long)t.getD("LENGTH");
    isOpen = (length >= 0L);
    pktlen  = getQual("PKTLEN",pktlen);
    pktrate = getQual("PKTRATE",pktrate);
    return isOpen;
  }

  private int getQual (String name, int defval) {
    Object qo = getQualifier(name);
    return (qo!=null)? Convert.o2l(qo) : defval;
  }

  /** close the resource */
  public boolean close() {
    sendMftpPacket("{FUNC=CLOSE,NAME="+rname+"}");
    if (dsock!=null) dsock.close();
    return true;
  }

  /** get length of resource */
  public long getLength() { return length; }

  /** seek to the specified offset in socket */
  public boolean seek (long offset) {
    this.offset = offset;
    return true;
  }

  /** get currently available length of resource */
  public long avail() {
    return length-offset;
  }

  /** read data from resource */
  public int read (byte[] buf, int boff, int bytes) {
    long foffset = offset;
    long loffset = offset+bytes;
    while (offset<loffset) {
      long noffset = offset;
      String cmd = "{FUNC=READ,NAME="+rname+",OFFSET="+offset+",LENGTH="+bytes+",PKTLEN="+pktlen+"}";
      if (pktrate>0) cmd += "{RATE="+pktrate+"}";
      sendMftpPacket(cmd);
      String str = recvMftpPacket();
      if (str==null) return -1;
      Table t = new Table(str);
      bytes = t.getL("LENGTH");
      while (offset<loffset) {
        int status = 0;
        try {
          dpacki.setLength(buffi.length);
          dsock.receive(dpacki);
          status = dpacki.getLength();
        }
        catch (java.net.SocketTimeoutException e) {
          if (offset > noffset) {
            Shell.warning("Mftp Packet TimeOut. Retrying ... ");
            bytes = (int)(loffset-offset);
            break;
          }
        }
        catch (IOException e) { Shell.warning(e.toString()); return -1; }
        if (dbg) System.out.println("Get MFTP data packet Stat="+status+" Bytes="+bytes+" Func="+RmifInterface.functionName(buffi[0])+" offset="+offset+" loffset="+loffset);
        if (status<8 || buffi[RmifInterface.FUNC]!=RmifInterface.DBUF || buffi[RmifInterface.ADJ]!=8) continue;
        long poffset = (long)Convert.unpackD(buffi,8);
        if (poffset > offset) {
          Shell.warning("Mftp Packet OutOfOrder. Retrying ... ");
          bytes = (int)(loffset-offset);
          break;
        }
        else if (poffset>=foffset && poffset<loffset) {
          int tbytes = status-16;
          System.arraycopy(buffi,16, buf,boff+(int)(poffset-foffset), tbytes);
          offset = poffset + tbytes;
        }
      }
    }
    return (int)(offset-foffset);
  }

  /** write data to resource */
  public int write (byte[] buf, int boff, int bytes) {
    sendMftpPacket("{FUNC=WRITE,NAME="+rname+",OFFSET="+offset+",LENGTH="+bytes+"}");
    // hmm do we want to support server writes ?
    // we would need a channel on the server RMIF
    offset += bytes;
    return bytes;
  }

  private void getMftpSocket() {
    timeOut = getQual("PKTTO",timeOut);
    int p1=defaultMftpPort, p2=lastMftpPort;
    if (port>0) p1 = p2 = port;
    for (port=p1; port<=p2; port++) try {
      dsock = new DatagramSocket(port);
      dsock.setSoTimeout(timeOut*1000); // in milliseconds
      dsock.setReceiveBufferSize(1048576);
      // allocate a packet buffer for output
      buffo  = new byte[8+MAXPKTBUF];
      dpacko = new DatagramPacket(buffo,buffo.length);
      raddr = InetAddress.getByName(rhost);
      dpacko.setAddress(raddr);
      dpacko.setPort(rport);
      // allocate a packet buffer for input
      buffi  = new byte[8+MAXPKTBUF];
      dpacki = new DatagramPacket(buffi,buffi.length);
      return;
    }
    catch (java.net.BindException e) { }
    catch (Exception e) { Shell.warning(e.toString()); }
    System.out.println("No Mftp ports available from "+p1+" to "+p2);
  }

  private void sendMftpPacket (String str) {
    if (dbg) System.out.println("Send MFTP packet "+str+" on "+port);
    if (dsock==null) getMftpSocket();
    buffo[RmifInterface.FUNC]=RmifInterface.MFTP;
    buffo[RmifInterface.FLAG]=0;
    buffo[RmifInterface.INFO]=0;
    buffo[RmifInterface.REP ]=EEEI;
    buffo[RmifInterface.SEQ ]=-1;
    buffo[RmifInterface.TRY ]=0;
    buffo[RmifInterface.RPT ]=0;
    buffo[RmifInterface.ADJ ]=0;
    byte[] bstr = str.getBytes();
    System.arraycopy(bstr,0, buffo,8, bstr.length);
    dpacko.setLength(8+bstr.length);
    try { dsock.send(dpacko); }
    catch (IOException e) { Shell.warning("Err sending MFTP message"); }
  }

  private String recvMftpPacket() {
    if (dsock==null) getMftpSocket();
    int status=0;
    while (status<8 || buffi[RmifInterface.FUNC]!=RmifInterface.MFTP) {
      try {
        dpacki.setLength(buffi.length);
        dsock.receive(dpacki);
        status = dpacki.getLength();
      }
      catch (IOException e) { Shell.warning(e.toString()); return null; }
    }
    String str = new String(buffi,8,status-8);
//System.out.println("Recv MFTP packet str="+str);
    return str;
  }

}
