package nxm.ucl.prim;

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

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;
import nxm.ucl.lib.httpgetdata;

/** Does GET or POST from/to a URL. See explain file for more details and
    example usages.
   @author MSZ / Neon Ngo
   @since NeXtMidas 2.5.5
 */
public class httpget extends Primitive implements Chainable {
  private httpgetdata hg   = null;
  private boolean     done = false; // Indicates if the primitive is done.
  private ExecutorService threadPool = null; // Thread Pool Executor

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

  public int open () {
    try {
      String muser = MA.getS("USER","");
      String mpass = MA.getS("PASS","");
      int  timeout = MA.getL("TIMEOUT", 0);      // both open & read timeout
      boolean warn = MA.getState("/WARN", true); // since 3.1.1 for BUG 2557
      hg = new httpgetdata(timeout);
      hg.setShowWarn(warn);                      // since 3.1.1 for BUG 2557
      if (muser.length()>0) hg.setUserPass(muser,mpass);

      String label    = MA.getU("LABEL");
      String url      = MA.getCS("URL").trim();
      String postdata = MA.getCS("POSTDATA",null);

      if (url.length()>0) { // URL specified, run in one-shot mode
        String response = submitRequest(url, postdata, label, timeout, timeout);
        if (response!=null) {
          if (label==null || label.length()==0) M.println("HTTP RESPONSE = "+response);
          // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
        }
        return FINISH;
      }

      // create thread pool (requires Java 5+) if size > 0 for handling HTTP
      // submission requests concurrently - Bug 1993
      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 (POST request when postdata is specified).
      @param url         URL of request
      @param postdata    Data to submit in a POST request (set to null for GET request).
      @return Response for submitted HTTP GET/POST request, null on error
      @since NeXtMidas 2.8.0
   */
  public String submitRequest (String url, String postdata) {
    return submitRequest(url, postdata, null, hg.getOpenTimeout(), hg.getReadTimeout(), null, null, null);
  }

  /** Submit a HTTP GET request (POST request when postdata is specified).
      @param url         URL of request
      @param postdata    Data to submit in a POST request (set to null for GET request).
      @param label       Results label to store response into.
      @return Response for submitted HTTP GET/POST request, null on error
      @since NeXtMidas 2.8.0
   */
  public String submitRequest (String url, String postdata, String label) {
    return submitRequest(url, postdata, label, hg.getOpenTimeout(), hg.getReadTimeout(), null, null, null);
  }

  /** Submit a HTTP GET request (POST request when postdata is specified).
      @param url         URL of request
      @param postdata    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.
      @return Response for submitted HTTP GET/POST request, null on error
      @since NeXtMidas 2.7.0
   */
  public String submitRequest (String url, String postdata, String label, int openTimeout, int readTimeout) {
    return submitRequest(url, postdata, label, openTimeout, readTimeout, null, null, null);
  }

  /** Submit a HTTP GET request (POST request when postdata is specified).
      @param url         URL of request
      @param postdata    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.
      @return Response for submitted HTTP GET/POST request, null on error
      @since NeXtMidas 2.7.3
   */
  private String submitRequest (String url, String postdata, String label, int openTimeout, int readTimeout, String msgName, Object userData, Object msgTo) {
    String value = null;
    Table quals = new Table(); // since 3.1.1 for BUG 2557
    quals.put("URL", url);
    quals.put("LABEL", label);
    quals.put("OPENTIMEOUT", openTimeout);
    quals.put("READTIMEOUT", readTimeout);
    quals.put("USERDATA", userData);
    // end since 3.1.1 for BUG 2557

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

    if (msgName != null && msgName.length()>0) {
      // MQ.put(msgName, 0, value, msgTo); // send message with response data to msgTo (prior to 3.1.1)
      Message responseMsg = new Message(msgName, 0, value, quals);
      MQ.put(responseMsg, msgTo); // send message with response to msgTo (since 3.1.1 for BUG 2557)
    }
    return value;
  }

  public synchronized int processMessage (Message msg) {
    try {
      if (verbose) M.info("httpget: Processing a message: "+msg.name +" data="+msg.data);
      if (msg.name.equals("HTTP_REQUEST")) {     // since 2.7.3 for BUG 1993
        Table reqTable = Convert.o2t(msg.data);
        if (reqTable != null) {
          String url      = reqTable.getS("URL","").trim();
          String postdata = reqTable.getS("POSTDATA", null);
          String label    = reqTable.getS("LABEL");
          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); // since 3.1.1 for BUG 2557

          // if have thread pool, then use it for submitting HTTP submissions/requests
          if (threadPool!=null) {
            threadPool.execute(new SubmitTask(url, postdata, label, openTimeout, readTimeout, msgName, userData, msgTo));
          } else {
            submitRequest(url, postdata, label, openTimeout, readTimeout, msgName, userData, msgTo);
          }
        }
      } 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 postdata;
    String label;
    int openTimeout;
    int readTimeout;
    String msgname;
    Object userData; // since 3.1.1 for BUG 2557
    Object msgTo;

    public SubmitTask (String url, String postdata, String label, int openTimeout, int readTimeout, String msgname, Object userData, Object msgTo) {
      super();
      this.url = url;
      this.postdata = postdata;
      this.label = label;
      this.openTimeout = openTimeout;
      this.readTimeout = readTimeout;
      this.msgname = msgname;
      this.userData = userData; // since 3.1.1 for BUG 2557
      this.msgTo = msgTo;
    }

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