package nxm.net.prim;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import nxm.net.lib.HttpRequester;
import nxm.sys.inc.Chainable;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Message;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Table;

/** Executes an http request.  The default type is GET.
    @author Jeanette McCall
    @since NeXtMidas 3.9.2
 */
public class httprequest extends Primitive implements Chainable {
  private HttpRequester hg   = null;
  private boolean     done = false; // Indicates if the primitive is done.
  private ExecutorService threadPool = null; // Thread Pool Executor

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

  public int open () {
    try {
      String url         = MA.getCS("URL").trim();
      String user        = MA.getS("USER");
      String pass        = MA.getS("PASS");
      int  timeout       = MA.getL("TIMEOUT", HttpRequester.DEFAULT_TIMEOUT);
      int  readTimeout   = MA.getL("READTIMEOUT", timeout);
      int  openTimeout   = MA.getL("OPENTIMEOUT", timeout);
      String label       = MA.getU("LABEL");
      String body        = MA.getCS("BODY",null);
      String requestType = MA.getCS("REQUEST",null);
      String contentType = MA.getCS("CONTENTTYPE",null);
      boolean anyHost    = MA.getState("ANYHOST");
      boolean warn       = MA.getState("/WARN", true);
      hg = new HttpRequester(M);
      hg.setShowWarn(warn);
      if (user.length()>0) hg.setUserPass(user,pass);
      hg.setReadTimeout(readTimeout);
      hg.setOpenTimeout(openTimeout);

      if (url.length()>0) { // URL specified, run in one-shot mode
        hg.setAnyHost(anyHost);
        hg.setContentType(contentType);
        String response = submitRequest(url, body, label, openTimeout, readTimeout, requestType);
        if (response!=null) {
          if (label==null || label.length()==0) M.println("HTTP RESPONSE = "+response);
        }
        return FINISH;
      }
      if (contentType!=null ) M.warning("CONTENTTYPE is ignored becuase there is no URL provided");
      if (anyHost) M.warning("ANYHOST is ignored becuase there is no URL provided");
      if (body!=null ) M.warning("BODY is ignored becuase there is no URL provided");
      if (requestType!=null ) M.warning("REQUEST is ignored becuase there is no URL provided");
      if (label != null && label.length()>0) M.warning("LABEL is ignored becuase there is no URL provided");
      int nThreads = MA.getL("THREADPOOLSIZE", 0); // default to zero for none
      if (nThreads > 0) { threadPool = Executors.newFixedThreadPool(nThreads); }

    } catch (Exception e) {
      M.printStackTrace("Exception in httpget.open()!", e);
    }
    return NORMAL;
  }

  /** Submit a HTTP GET request
      @param url         URL of request
      @return Response for submitted HTTP GET/POST request, null on error
   */
  public String submitRequest (String url) {
    return submitRequest(url, null, null, hg.getOpenTimeout(), hg.getReadTimeout(), null, null, null, HttpRequester.GET);
  }

  /** Submit a HTTP GET request
      @param url         URL of request
      @param label       Results label to store response into.
      @param openTimeout Timeout in milliseconds of request for opening connection.
      @param readTimeout Timeout in milliseconds of request for reading response data.
      @return Response for submitted HTTP GET/POST request, null on error
   */
  public String submitRequest (String url, String label, int openTimeout, int readTimeout) {
    return submitRequest(url, null, label, openTimeout, readTimeout, null, null, null, null);
  }

  /** Submit a HTTP REQUEST
      @param url         URL of request
      @param body        Data to submit in a POST request (set to null for GET request).
      @param label       Results label to store response into.
      @param openTimeout Timeout in milliseconds of request for opening connection.
      @param readTimeout Timeout in milliseconds of request for reading response data.
      @param requestType The type of request - GET/POST/PUT/DELETE
      @return Response for submitted HTTP GET/POST request, null on error
  */
  public String submitRequest (String url, String body, String label, int openTimeout, int readTimeout, String requestType) {
    return submitRequest(url, body, label, openTimeout, readTimeout, null, null, null, requestType);
  }

  /** Submit a HTTP REQUEST
      @param url         URL of request
      @param body        Data to submit in a POST request (set to null for GET request).
      @param label       Results label to store response into.
      @param openTimeout Timeout in milliseconds of request for opening connection.
      @param readTimeout Timeout in milliseconds of request for reading response data.
      @param msgName     Name of message to send (if not null and not blank)
      @param msgTo       Destination of where to send message to
      @param userData    User specific passthrough data (e.g. so that they can keep track of requests)
                         that gets included in the response message.
      @param requestType null means use defaults, otherwise use this request type (PUT, DELETE, POST, or GET)
      @return Response for submitted HTTP GET/POST request, null on error
   */
  private String submitRequest (String url, String body, String label, int openTimeout, int readTimeout, String msgName,
      Object userData, Object msgTo, String requestType) {
    String value = null;
    Table quals = new Table();
    quals.put("URL", url);
    quals.put("LABEL", label);
    quals.put("OPENTIMEOUT", openTimeout);
    quals.put("READTIMEOUT", readTimeout);
    quals.put("USERDATA", userData);
    quals.put("REQUEST", requestType);

    if (url!=null && url.length()>0) {   // if URL is specified
      value = hg.doRequest(url, body, openTimeout, readTimeout, requestType);
      if (label!=null && label.length()>0) MR.put(label, value);
    }

    if (msgName != null && msgName.length()>0) {
      Message responseMsg = new Message(msgName, 0, value, quals);
      MQ.put(responseMsg, msgTo);
    }
    return value;
  }

  public synchronized int processMessage (Message msg) {
    try {
      if (verbose) M.info("httprequest: Processing a message: "+msg.name +" data="+msg.data);
      if (msg.name.equals("HTTP_REQUEST")) {
        Table reqTable = Convert.o2t(msg.data);
        if (reqTable != null) {
          String url         = reqTable.getS("URL","").trim();
          String body        = reqTable.getS("BODY", null);
          String label       = reqTable.getS("LABEL");
          String requestType = reqTable.getS("REQUEST");
          int openTimeout = hg.getOpenTimeout(); // open & read timeouts defaults to
          int readTimeout = hg.getReadTimeout(); // current settings in httpgetdata library
          if (reqTable.containsKey("TIMEOUT")) {
            openTimeout = readTimeout = reqTable.getL("TIMEOUT"); // both open & read timeout
          }
          if (reqTable.containsKey("OPENTIMEOUT")) { // OPENTIMEOUT overrides TIMEOUT
            openTimeout = reqTable.getL("OPENTIMEOUT", openTimeout);
          }
          if (reqTable.containsKey("READTIMEOUT")) { // READTIMEOUT overrides TIMEOUT
            readTimeout = reqTable.getL("READTIMEOUT", readTimeout);
          }
          String msgName  = reqTable.getS("MSGNAME", null);
          Object msgTo    = msg.getFrom();
          Object userData = reqTable.getO("USERDATA", null);
          hg.setContentType(reqTable.getS("CONTENTTYPE"));
          hg.setAnyHost(reqTable.getZ("ANYHOST"));
          // if have thread pool, then use it for submitting HTTP submissions/requests
          if (threadPool!=null) {
            threadPool.execute(new SubmitTask(url, body, label, openTimeout, readTimeout, msgName, userData, msgTo, requestType));
          } else {
            submitRequest(url, body, label, openTimeout, readTimeout, msgName, userData, msgTo, requestType);
          }
        }
      } else if (msg.name.equals("EXIT")) {
        done = true;
        return FINISH;
      } else {
        super.processMessage(msg);
      }

    } catch (Exception e) {
      M.printStackTrace("Exception in httpget.processMessage()!", e);
    }
    return NORMAL;
  }

  public int process () {
    int status = (done)? FINISH : NOOP;
    return status;
  }

  public int close () {
    if (threadPool != null) {
      // shutdown thread pool
      threadPool.shutdown();
      try {
        threadPool.awaitTermination(1000, TimeUnit.MILLISECONDS); // wait up to 1 second
      } catch (InterruptedException ie) {
        // print warning and continue
        M.warning("Interrupted while waiting for Thread Pool to shutdown. "+ie);
      }
    }
    return NORMAL;
  }

  // chainable interface
  public Object getNextLink () { return hg; }
  public Object getPrevLink () { return null; }

  /** Runnable Task to submit HTTP request using Thread Pool (ExecuteService) */
  class SubmitTask implements Runnable {
    String url;
    String body;
    String label;
    int openTimeout;
    int readTimeout;
    String msgname;
    Object userData;
    Object msgTo;
    String requestType;

    public SubmitTask (String url, String body, String label, int openTimeout, int readTimeout, String msgname, Object userData, Object msgTo) {
      super();
      this.url = url;
      this.body = body;
      this.label = label;
      this.openTimeout = openTimeout;
      this.readTimeout = readTimeout;
      this.msgname = msgname;
      this.userData = userData;
      this.msgTo = msgTo;
    }

    public SubmitTask (String url, String body, String label, int openTimeout, int readTimeout, String msgname, Object userData, Object msgTo, String requestType) {
      super();
      this.url = url;
      this.body = body;
      this.label = label;
      this.openTimeout = openTimeout;
      this.readTimeout = readTimeout;
      this.msgname = msgname;
      this.userData = userData;
      this.msgTo = msgTo;
      this.requestType = requestType;
    }

    public void run () {
      submitRequest(url, body, label, openTimeout, readTimeout, msgname, userData, msgTo, requestType);
    }
  }
}
