package nxm.sys.intr;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.Command;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.Message.XmMsgType;
import nxm.sys.lib.MessageQueue;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Time;

/**
  Send and get messages from the macro language.

  @author Jeff Schoen
*/
public class Message extends Intrinsic {

  /** nxm.sys.prim.rmif class */
  private static Class<?>  rmifPrimClass = null;
  /** nxm.sys.prim.plot class */
  private static Class<?>  plotPrimClass = null;
  /** static nxm.sys.prim.plot.processMessage(plot plotObj, Message msg, boolean direct) method*/
  private static Method    plotStaticProcessMessage = null;

  static {
    // added in 4.0.0 to allow a thin NeXtMidas without Primitives and libg - NXM
    try {
      rmifPrimClass = Class.forName("nxm.sys.prim.rmif");
      plotPrimClass = Class.forName("nxm.sys.prim.plot");
      plotStaticProcessMessage = plotPrimClass.getDeclaredMethod("processMessage", plotPrimClass, nxm.sys.lib.Message.class , boolean.class);
    }
    catch (Exception ex){
      Shell.getMidasContext().info("intr.Message seems to be operating in LIBNXM mode without Primitives");
    }
  }

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

  public int process() {
    nxm.sys.lib.Message msg;
    nxm.sys.lib.Message msgt;

    // Get parameters
    String func = MA.getU("FUNC");
    Object from = (M.macro != null)? M.macro : this;

    if (func.startsWith("SEND")) {
      boolean sync   = (func.indexOf('W',4) > 0);
      boolean direct = (func.indexOf('X',4) > 0);
      boolean replace= (func.indexOf('R',4) > 0);    // since 3.3.0 for BUG 98
      msgt = send(sync, direct, replace, from);
      if (msgt != null) {                            // since 3.3.0 for BUG 98
        MR.put(MA.getU("MSG"),msgt);
      }
    }
    else if (func.equals("GET")) {
      msg = MQ.get();
      MR.put(MA.getU("MSG"),msg);
    }
    else if (func.startsWith("GET")) {
      int flags = 0;
      int timeout = 0;
      msgt = new nxm.sys.lib.Message("NULL",0,null);
      if (func.indexOf('N')>0) {
        msgt.name = MA.getS("NAME");
        flags |= MessageQueue.NAME;
      }
      if (func.indexOf('F')>0) {
        msgt.from = M.registry.get(MA.getS("FROM"));
        flags |= MessageQueue.FROM;
      }
      if (func.indexOf('W')>0) {
        timeout = (int)(1000*MA.getD("/WAIT"));
      }
      msg = MQ.get(flags,msgt,timeout);
      if (msg==null) { msg=msgt; msg.name="NULL"; }
      MR.put(MA.getU("MSG"),msg);
    }
    else if (func.startsWith("PASS")) {
      String id = MA.getS("ID");
      msg = MA.getMessage("MSG");
      if (msg == null) M.error("Message not found in results table");
      if (MQ.put(msg,id,messageHandler) < 0) {
        M.warning("Command "+id+" not found in registry");
      }
    }
    else if (func.startsWith("PRUNE")) {
      msg = MA.getMessage("MSG");
      if (msg==null) M.warning("Trying to prune a null message");
      msgt = MQ.prune(msg);
      if (msgt!=msg) MR.put(MA.getU("MSG"),msgt);
    }
    else if (func.startsWith("QUERY")) {
      M.println("MQ owner = "+MQ.owner);   // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
      M.println("MQ avail = "+MQ.avail());
    }
    else if (func.startsWith("SYNC")) {
      String id = MA.getS("ID");
      Command command = M.registry.getCommand(id,0);
      MQ.put("NULL",0,null,command,from);
      while (command.queue.avail()>0) Time.sleep(0.01);
    }
    else if (func.startsWith("REPLACE")) { // since 3.3.0 for BUG 98
      msg = getMessage(from);
      msgt = MQ.replace(msg);

      if (msgt!=msg) MR.put(MA.getU("MSG"),msgt);
    }
    else {
      M.error ("Unsupported function: "+func);
    }

    return FINISH;
  }

  /** Sends a message. */
  nxm.sys.lib.Message send (boolean sync, boolean direct, Object from) { // package-private used by Message and Sendto
    return send(sync, direct, false, from); // false for replace
  }

  /** Sends a message. */
  nxm.sys.lib.Message send (boolean sync, boolean direct, boolean replace, Object from) { // package-private
    nxm.sys.lib.Message replacedMsg = null;
    String id = MA.getS("ID");
    boolean msgDebug   = MA.find("/MSGDEBUG");
    String  debugLevel = MA.getS("/MSGDEBUG", "DEFAULT");
    if (debugLevel.equals("")) debugLevel = "DEFAULT";

    nxm.sys.lib.Message msg = getMessage(from);

    Object to;
    if (id.equals("REPLY")) {
      if (MA.find("NAME"))  msg.name   = MA.getS("NAME");
      if (MA.find("INFO"))  msg.info   = MA.getL("INFO",0);
      if (MA.find("DATA"))  msg.data   = MA.getO("DATA");
      if (MA.find("QUALS")) msg.quals  = MA.getO("QUALS");
      to = msg.from;
      from = msg.to;
    }
    else {
      if (id.indexOf(".")>0) to = MA.getO("ID"); // Allows sending msgs to another macro or primitive in submacro or parent
      else to = M.registry.getMessageHandler(id);
    }

    int status = -1;
    if (to == null) {
      status = -1;
    }
    else if (direct) {
      msg.from = from;
      msg.to = to;
      if (msg.to instanceof MessageHandler) {
        if (msgDebug) M.info(getName()+" sending direct "+msg.toString(debugLevel));
        if ((plotPrimClass != null) && (plotPrimClass.isInstance(msg.to))) // 4.0.0. for NXM-3671
          try {
            status = (int) plotStaticProcessMessage.invoke(null,msg.to,msg, direct);
          } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            getMidas().warning("intr.Message unable to send message to plot "+ex);
          }
        else status = ((MessageHandler)msg.to).processMessage(msg);
      }
    }
    else {
      if (msgDebug) M.info(getName()+" sending "+msg.toString(debugLevel));
      if (replace) {
        replacedMsg = MQ.replace(msg); // since 3.3.0 for BUG 98
      }
      if (replacedMsg == null) {       // no replacement msg found, so send msg
        status = MQ.put(msg,to,from);
      } else {
        status = 0;
      }
    }
    if ((to != null) && (rmifPrimClass != null) && rmifPrimClass.isInstance(to) && !sync) { // 4.0.0 updated to use reflection
      // JC4: I think the next two lines contains an error, either the warning is wrong or
      //      the sync=true should be part of the if block (which is missing { and }).
      if (msg.name.equals("OPEN") || msg.name.equals("ADDR"))
        M.warning("Forcing use of MESSAGE SENDW for Rmif OPEN and ADDR messages");
      sync=true;
    }

    if (status<0) {
      M.warning("Unable to send Msg="+msg.name+" to Id="+id + " status="+status+" to="+to + " " + msg.toString(debugLevel));
    }
    else if (sync) {
      if (to instanceof Command) { // 2011-07-25 for BUG 2179 to prevent ClassCastException ERROR when sending a message to a non-Command (like Rmif.Remote)
        int timeout = (int)(1000*MA.getD("/WAIT",1.0));
        Command command = (Command)to;
        status = MQ.put ("NULL",0,null,command,from);
        while (timeout>0 && command.queue.avail()>0) { Time.sleep(0.010); timeout-=10; }
      }
      else { // 2011-07-25 Warning for BUG 2179 ClassCastException ERROR when sending a message to a non-Command (like Rmif.Remote)
        M.warning("Forcing use of MESSAGE SEND instead of SENDW for messages not destined for a Command. to="+to);
      }
    }
    return replacedMsg;
  }

  /** Gets message from input args.
      First precedence goes to "MSG" argument, otherwise use
      "NAME", "INFO", "DATA", "QUALS", etc. arguments to construct the message
      @param from The source of the message.
   */
  nxm.sys.lib.Message getMessage (Object from) {
    nxm.sys.lib.Message msg = MA.getMessage("MSG");
    if (msg == null) {
      String    msg_name  = MA.getS("NAME");
      int       msg_info  = MA.getL("INFO", 0);
      Object    msg_data  = MA.getO("DATA");
      Object    msg_quals = MA.getO("QUALS");
      XmMsgType msg_type  = MA.getSelection("/XMTYPE", XmMsgType.AUTO);
      msg = new nxm.sys.lib.Message(msg_name, msg_info, msg_data, null, from, msg_quals);
      msg.setXmMessageType(msg_type);
    }
    return msg;
  }
}
