package nxm.sys.lib;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
  Resource for interacting with a TCP Socket.

  @author Jeff Schoen
 */
public class TcpResource extends IOResource implements Runnable {

  /** Socket action/state flags */
  public static final int UNTANGLE=1, CLOSED=-1, REOPEN=-2;
  private DataInputStream sis;
  private DataOutputStream sos;
  private Socket socketToMe;
  private String host;
  private int port;

  /** Empty constructor for TcpResource */
  public TcpResource() { }

  /** test existence of the resource - assume exist in only for input */
  @Override
  public boolean exists () { return (dir < DIR_INOUT); }

  @Override
  public void setURL (String str) {
    super.setURL(str);
    // peel off various header formats
    if      (str.startsWith("stp://"))  str = str.substring(6);
    else if (str.startsWith("stp:/"))   str = str.substring(5);
    else if (str.startsWith("stp:"))    str = str.substring(4);
    else if (str.startsWith("http://")) str = str.substring(7); // emb = need to work with hwdriver
    int i = str.indexOf(":");
    // host = str.substring(6,i); emb = removed overhead already
    host = str.substring(0,i); // emb = changed b/c all header formats have been removed
    port = Convert.s2l(str.substring(i+1));
  }

  /** open the resource */
  @Override
  public boolean open () {
    isOpen = false;
    try {
      if (dir > DIR_INOUT) { // output only
        ServerSocket server = new ServerSocket (port);
        Socket socket = server.accept();
        sos = new DataOutputStream(socket.getOutputStream());
      } else if (dir <= DIR_INOUT) {
        // emb = problem with hwdriver we need to write then read at the same time
        Socket socket = new Socket (host,port);
        //socket.setSoTimeout(10000); // NTN - 10 second timeout on socket reads/write
        sis = new DataInputStream(socket.getInputStream());
        sos = new DataOutputStream(socket.getOutputStream());  // ntn
      }
      isOpen = true;
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    return isOpen;
  }

  /** close the resource */
  @Override
  public boolean close () {
    try {
      if (sis!=null) sis.close();
      if (sos!=null) sos.close();
    }
    catch (IOException e) { return false; }
    return true;
  }

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

  /** get currently available length of resource */
  @Override
  public long avail () {
    if (sis==null) return 0L;
    try { return sis.available(); }
    catch (IOException e) { return 0L; }
  }

  /** read data from resource */
  @Override
  public int read (byte[] buf, int boff, int bytes) {
    int status=0, off=boff;
    while (bytes>0) {
      try { status = sis.read(buf,off,bytes); }
      catch (IOException e) {status = -1;}
      if (status>0) {
        bytes-=status; off+=status;
      }
      else break;
    }
    bytes = off-boff;
    if (status<0 && bytes==0) return status;
    offset += bytes;
    return bytes;
  }

  /** write data to resource */
  @Override
  public int write (byte[] buf, int boff, int bytes) {
    try { sos.write(buf,boff,bytes); }
    catch (IOException e) { return -1; }
    offset += bytes;
    return bytes;
  }

  /** connect to multiple sockets with optional timeout and untangle
   *  @param host    address
   *  @param port    port number
   *  @param nsock   number of sockets
   *  @param timeout time out
   *  @param flags socket action and state flags
   *  @return array of sockets
   */
  public Socket[] connectSockets (String host, int port,
      int nsock, int timeout, int flags) {
    this.host = host;
    this.port = port;
    Socket[] socks = new Socket[nsock];

    try {
      for (int i=0; i<nsock; i++) {
        Thread thread = new MidasThread(M,this);
        thread.start();
        thread.join(timeout);
        if (thread.isAlive()) {
          thread.interrupt();
          Thread.sleep(500);  // wait for thread to write err message
          return null;
        }
        socks[i] = socketToMe;
      }
      if (nsock==2 && (flags&UNTANGLE)!=0) {
        byte[] bufi = new byte[16];
        byte[] bufo = new byte[16];
        socks[0].getInputStream().read(bufi);
        socks[1].getInputStream().read(bufo);
        if ((char)bufi[0] != 'S') {
          Socket stmp = socks[0];
          socks[0] = socks[1];
          socks[1] = stmp;
        }
      }
      return socks;
    }
    catch (Exception e) { throw new MidasException("Connect Sockets", e); }
  }

  /** Run method for executing the socket resource. */
  @Override
  public void run () {
    try { socketToMe = new Socket (host,port); }
    catch (Exception e) { throw new MidasException("Socket connect", e); }
  }

}
