package nxm.sys.lib;

import nxm.sys.inc.MessageHandler;

/** A class to perform message queue functions. <br>
    <br>
    Messages are directed to the target command's {@link Command#processMessage(Message)} method by
    the source's thread (this is done internal to {@link Command}). If the target command decides
    that the source's thread is not allowed to execute the message, it is placed on the target's
    message queue. This queue is worked off by the command's run method.

    @author Jeff Schoen
*/
public class MessageQueue implements MessageHandler {

  /** Midas context */
  public Midas M;
  /** owner of the Message Queue */
  public Object owner;
  private Message first,last,lastOut;
  /** total # of messages that have been put onto this queue (since creation time) */
  private int total;
  /** # of messages currently "available" in queue */
  private int avail;

  /** get the next matching                */ public static final int NEXT = 0x01;
  /** get the last matching                */ public static final int LAST = 0x02;
  /** get without removing                 */ public static final int PEEK = 0x04;
  /** latest without removing earlier ones */ public static final int KEEP = 0x08;
  /** get matching the message name field  */ public static final int NAME = 0x10;
  /** get matching the message from field  */ public static final int FROM = 0x20;
  /** pass msgt through if no msg exists   */ public static final int PASS = 0x40;

  /** Initialize a Message Queue. This is the same as <code>new MessageQueue(null,owner)</code>.
      @param owner The "owner" of the message queue.
   */
  public MessageQueue (Object owner) {
    this(null, owner);
  }

  /** Initialize a Message Queue.
      @param M     The Midas context to use.
      @param owner The "owner" of the message queue.
   */
  public MessageQueue (Midas M, Object owner) {
    this.M = M;
    this.owner = owner;
    clear();
  }

  /** Clears all messages in the queue. This discards any messages currently in the queue and makes
      no attempt to process them.
   */
  public void clear() {
    first = null;
    last = null;
    total = 0;
    avail = 0;
  }

  /** Put a message into the queue for the specified target.
      @param message The message to put onto the message queue.
      @param to      Target destination of message
      @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (Message message, Object to) {
    return put (message,to,owner);
  }

  /** Create a new message and put it into the queue for the current command (owner) with
      a <code>new Object()</code> for the value of the message's data field.
      @param name The name of the message
      @param info The value of the info field for the message
      @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (String name, int info) {
    return put( new Message(name,info,new Object(),owner,owner));
  }

  /** Create a new message and put it into the queue for the current command (owner).
     @param name The name of the message
     @param info The value of the info field for the message
     @param data The value of the data field for the message
     @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (String name, int info, Object data) {
    return put( new Message(name,info,data,owner,owner));
  }
  /** Create a new message and put it into the queue for the specified target.
      @param name The name of the message
      @param info The value of the info field for the message
      @param data The value of the data field for the message
      @param to   Target destination of message
      @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (String name, int info, Object data, Object to) {
    return put( new Message(name,info,data), to, owner);
  }

  /** Create a new message and put it into the queue for the specified target
      and source of the message.
      @param name The name of the message
      @param info The value of the info field for the message
      @param data The value of the data field for the message
      @param to   Target destination of message
      @param from The source of the message
      @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (String name, int info, Object data, Object to, Object from) {
    return put( new Message(name,info,data), to, from);
  }

  /** Create a new message and put it into the queue for the specified target
      and source of the message.
      @param name  The name of the message
      @param info  The value of the info field for the message
      @param data  The value of the data field for the message
      @param to    Target destination of message
      @param from  The source of the message
      @param quals The value of the quals field for the message
      @return 0 if the message is deliver, -1 if there was an error
      @since NeXtMidas 4.0.0 for NXM-3699
    */
  public int put (String name, int info, Object data, Object to, Object from, Object quals) {
    return put( new Message(name,info,data,quals), to, from);
  }

  /** Put a message into the queue and updating the message's target destination and source with
      the specified to and from parameters, respectively.
     @param message The message to put onto the message queue.
     @param to Target destination of message
     @param from The source of the message
     @return 0 if the message is deliver, -1 if there was an error
   */
  public int put (Message message, Object to, Object from) {
    message.to = to;
    message.from = from;

    if (to instanceof String && M!=null) {
      to = M.registry.getMessageHandler((String)to);    // check registry
      message.to = to;
    }

    if (to==owner) {                                    // dump it here
      put(message);
    }
    else if (to instanceof Command) {                   // to command
      ((Command)to).queue.put(message);
    }
    else if (to instanceof MessageQueue) {              // to queue
      ((MessageQueue)to).put(message);
    }
    else if (to instanceof MessageHandler) {            // direct
      ((MessageHandler)to).processMessage(message);
    }
    else {
      if (M!=null) M.warning("Bad MessageQueue To Address: "+to+" From: "+from);
      else Shell.warning("Bad MessageQueue To Address: "+to+" From: "+from);
      return -1;
    }
    return 0;                                           // delivered
  }

  /** Implements the {@link MessageHandler} interface. This is the same as using <code>put(msg)</code>.
      @param msg {@inheritDoc}
      @return {@inheritDoc}
      @since NeXtMidas 2.7.1
   */
  public int processMessage (Message msg) {
    return put(msg);
  }

  /** Put a message into this queue.
      @param message The message.
      @return The number of available messages.
   */
  public synchronized int put (Message message) {
    total++;
    message.prev = last;
    message.next = null;
    if (last!=null) last.next = message;
    last = message;
    if (first==null) first=last;
    return (++avail);
  }

  /** Get the next message from the queue.
      @return The next message on the queue or null if the queue is empty.
   */
  public synchronized Message get () {
    if (first==null) return null;
    Message msg = first;
    first = first.next;
    if (first==null) last=null;
    avail--;
//    msg.prev=null;
//    msg.next=null;
    lastOut = msg;
    return msg;
  }

  /** perform a modified search for a message in the queue
   *  @param flags   what part of the message to match (from, to, ...)
   *  @param msgt    value for match
   *  @param timeout when to timeout
   *  @return Message found
   */
  public Message get (int flags, Message msgt, int timeout) { // TODO: Jeff why is this method NOT synchronized to protect from race condition?
    Message msg, msgk=null;
    do {
      for (msg=first; msg!=null; msg=msg.next) {
        if ((flags&NAME)!=0 && !msg.name.equals(msgt.name)) continue;
        if ((flags&FROM)!=0 && msg.from!=msgt.from) continue;
        msgk = msg;
        if ((flags&PEEK)==0) remove(msg);  // if Not peek
        if ((flags&LAST)==0) return(msg);  // if Not last
      }
      if (timeout!=0) Time.sleep(0.010);
      if (timeout>0) timeout = Math.max(0,timeout-10);
    } while (timeout!=0);
    if (msgk==null && (flags&PASS)!=0) msgk=msgt;
    lastOut = msgk;
    return msgk;
  }

  /** remove named message from the queue
   *  @param name name of message to remove frow queue
   *  @return number of messages removed
   */
  public synchronized int remove (String name) {
    int count=0; Message msg;
    for (msg=first; msg!=null; msg=msg.next) {
      if (msg.name.equals(name)) { remove(msg); count++; }
    }
    return count;
  }

  /** remove message from the queue
   *  @param msg message to remove from queue */
  public synchronized void remove (Message msg) {
    if (msg.prev != null) msg.prev.next = msg.next;
    if (msg.next != null) msg.next.prev = msg.prev;
    if (msg==first) first = msg.next;
    if (msg==last ) last  = msg.prev;
    avail--;
  }

  /** get the latest matching message on top of queue
   *  @param match message to match
   *  @return latest matching message on top of queue
   */
  public synchronized Message prune (Message match) {
    Message msg;
    if (match==null) return null;
    for (msg=first; msg!=null; msg=msg.next) {
      if (!msg.name.equals(match.name)) break;
      if (msg.from != match.from) break;
      match = msg; remove(msg);
    }
    return match;
  }

  /** Replace the latest matching message on top of queue with specified message.
      The message.name must be equal and the message.from must reference the same
      object for it to match and replace an existing message.
      @param message message to match
      @return replaced message, null if none
      @since NeXtMidas 3.3.0
   */
  public synchronized Message replace (Message message) {
    if (message==null) return null;

    Message msg = null;
    for (msg=first; msg!=null; msg=msg.next) {
      if (!message.name.equals(msg.name)) continue; // name not equal
      if (message.from != msg.from) continue;       // from not same object
      // found matching message to replace with specified message
      if (msg.prev != null) msg.prev.next = message;
      if (msg.next != null) msg.next.prev = message;
      if (msg == first) first = message;
      if (msg == last ) last  = message;
      message.prev = msg.prev;
      message.next = msg.next;
      break;
    }
    return msg;
  }

  /** return number of messages currently in queue
   *  @return number of messages currently in queue. */
  public int avail () { return avail; }

  /** Get number of messages currently in queue.
   *  @return number of messages currently in queue. */
  public int getAvail () { return avail; }

  /** Get Last Message that was retrieved from the queue.
   *  @return Last Message that was retrieved from the queue. */
  public Message getLastOut () { return lastOut; }

  /** Get "total" messages that have been put onto this queue (since creation time).
   *  @return "total" messages that have been put onto this queue */
  public int getTotal () { return total; }

  public String toString () { return "Message Queue:  Avail="+avail+" Total="+total; }

}
