package nxm.sys.lib;

import nxm.sys.inc.IDable;
import nxm.sys.inc.Keyable;

/** Used to communicate with and between commands. NextMidas is a message
    based architecture in which each command is capable of sending and
    receiving messages from any source. Messages are delivered by calling the
    Command.processMessage method where they are handled by the receiving
    instance.  Each message has name, info, data, from, and to properties
    along with links to the next and previous Messages.

    @see nxm.sys.lib.Command
    @see nxm.sys.lib.MessageQueue

    @author Jeff Schoen
*/
public class Message extends Object implements Keyable {

  /** list of available Message properties */
  public static final String propertyList = "Name,Info,Data,From,To";

  /** X-Midas Message Type. Options include:
      <pre>
        NEXTMIDAS    - A normal NeXtMidas message (this is the default). Sent to X-Midas
                       as an /NM/ message (as configured in $NMROOT/nxm/sys/cfg/messages.cfg).
        CONFIGURED   - A "configured" X-Midas message. The message name must match one
                       found in messages.cfg, the data must be a Table with keys matching
                       messages.cfg.
        UNCONFIGURED - An "un-configured" X-Midas message. The data must be a Table and
                       must contain:
                         ARGS=  - Number of arguments.
                         TYPE=  - The X-Midas argument type specifier (e.g. "LLA").
                         VAL1=  - The 1st value.
                         VAL2=  - The 2nd value.
                          :            :
                         VALN=  - The Nth value.
        AUTO         - A "configured" or an "un-configured" X-Midas message, compatible
                       with MESSAGE in NeXtMidas 2.6.0. Users are advised NOT to use this
                       as its implementation is subject to change without notice.
      </pre>
      @since NeXtMidas 2.7.1
   */
  public static enum XmMsgType {
      /** A normal NeXtMidas message (this is the default). Sent to X-Midas
          as an /NM/ message (as configured in $NMROOT/nxm/sys/cfg/messages.cfg)
       */
      NEXTMIDAS,
      /** A "configured" X-Midas message. The message name must match one
          found in messages.cfg, the data must be a Table with keys matching
          messages.cfg
       */
      CONFIGURED,
      /** An "un-configured" X-Midas message. The data must be a Table and
          must contain:
            ARGS=  - Number of arguments
            TYPE=  - The X-Midas argument type specifier (e.g. "LLA")
            VAL1=  - The 1st value
            VAL2=  - The 2nd value
             :            :
            VALN=  - The Nth value
       */
      UNCONFIGURED,
      /** A "configured" or an "un-configured" X-Midas message, compatible
          with MESSAGE in NeXtMidas 2.6.0. Users are advised NOT to use this
          as its implementation is subject to change without notice
       */
      AUTO
    };

  /** The name of the message */
  public String name;

  /** The message info field */
  public int info;

  /** The message data */
  public Object data;

  /** The message sender ID */
  public Object from;

  /** The message reader ID */
  public Object to;

  /** Message Qualifiers (extra data that did not belong in any other field) */
  public Object quals;

  /** Do not use: This should only be used by {@link MessageQueue}. */
  public Message next,prev;

  /** X-Midas message type (see {@link XmMsgType}) */
  private XmMsgType xmMsgType = XmMsgType.NEXTMIDAS;

  /** List of info types to include in the toString method. */
  public static final String stringList = "Name,Info,Data,From,To,FID,TID,Quals";
  /** include the name of the Message */
  public static final int NAME = 0x1;
  /** include the info field of the Message */
  public static final int INFO = 0x2;
  /** include the data field of the Message */
  public static final int DATA = 0x4;
  /** include the source of the Message */
  public static final int FROM = 0x8;
  /** include the destination of the Message */
  public static final int TO = 0x10;
  /** include the source ID of the Message */
  public static final int FID = 0x20;
  /** include the destination ID of the Message */
  public static final int TID = 0x40;
  /** include the qualifiers of the Message */
  public static final int QUALS = 0x80;
  /** Information for toString */
  private static int defToStringMask = NAME|INFO|DATA|QUALS|FID|TID;
  private static String defToStringStr = Parser.mask2s(stringList,defToStringMask).toUpperCase();

  private static String[] keys = {"NAME","DATA","INFO","TO","FROM","TID","FID","QUALS"};

  /** Initialize a new instance that has no "to", "from", or "quals" values and default "xmMsgType" value.
      This is the same as <code>this(name, info, data, null, null, null, null)</code>.
      @param name The name of the Message.
      @param info The value of the info field.
      @param data The value of the data field.
      @see #Message(String, int, Object, Object, Object, Object, XmMsgType)
   */
  public Message (String name, int info, Object data) {
    this(name, info, data, null, null, null, null);
  }

  /** Initialize a new instance that has no "to", "from", or "quals" values.
      This is the same as <code>this(name, info, data, null, null, null, xmMsgType)</code>.
      @param name The name of the Message.
      @param info The value of the info field.
      @param data The value of the data field.
      @param xmMsgType X-Midas message type (one of {@link XmMsgType}).
                       Defaults to {@link XmMsgType#NEXTMIDAS} if null.
      @see #Message(String, int, Object, Object, Object, Object, XmMsgType)
      @since NeXtMidas 2.9.1
   */
  public Message (String name, int info, Object data, XmMsgType xmMsgType) {
    this(name, info, data, null, null, null, xmMsgType);
  }

  /** Initialize a new instance that has no "to" or "from" values and default "xmMsgType" value.
      This is the same as <code>this(name, info, data, null, null, quals, null)</code>.
      @param name  The name of the Message.
      @param info  The value of the info field.
      @param data  The value of the data field.
      @param quals The value of the quals field
      @see #Message(String, int, Object, Object, Object, Object, XmMsgType)
  */
  public Message (String name, int info, Object data, Object quals) {
    this(name, info, data, null, null, quals, null);
  }

  /** Initialize a new instance that has no "quals" value and default "xmMsgType" value.
      This is the same as <code>this(name, info, data, to, from, null, null)</code>.
      @param name  The name of the Message.
      @param info  The value of the info field.
      @param data  The value of the data field.
      @param to    The destination of the message
      @param from  The source of the message.
      @see #Message(String, int, Object, Object, Object, Object, XmMsgType)
  */
  public Message (String name, int info, Object data, Object to, Object from) {
    this(name, info, data, to, from, null, null);
  }

  /** Initialize a new instance that has its properties set to the given parameters
      and default "xmMsgType" value.
      @param name The name of the Message.
      @param info The value of the info field.
      @param data The value of the data field.
      @param to   The destination of the message
      @param from The source of the message.
      @param quals The qualifiers for the Message
      @see #Message(String, int, Object, Object, Object, Object, XmMsgType)
  */
  public Message (String name, int info, Object data, Object to, Object from, Object quals) {
    this(name, info, data, to, from, quals, null);
  }

  /** Initialize a new instance that has its properties set to the given parameters.
      @param name  The name of the Message.
      @param info  The value of the info field.
      @param data  The value of the data field.
      @param to    The destination of the message
      @param from  The source of the message.
      @param quals The qualifiers for the Message
      @param xmMsgType X-Midas message type (one of {@link XmMsgType}).
                       Defaults to {@link XmMsgType#NEXTMIDAS} if null.
      @since NeXtMidas 2.9.1
   */
  public Message (String name, int info, Object data, Object to, Object from, Object quals, XmMsgType xmMsgType) {
    this.name  = name;
    this.info  = info;
    this.data  = data;
    this.to    = to;
    this.from  = from;
    this.quals = quals;
    if (xmMsgType != null) this.xmMsgType = xmMsgType;
  }

  /**  Get the message data as a string. This is the same as <code>getData().toString()</code>.
   *   @return the message data as a string */
  public String getS() {
    return getData().toString();
  }

  /** Get the name of the message.
   * @return the name of the message */
  public String getName() { return name; }
  /** Get the message info.
   *  @return the message info */
  public int    getInfo() { return info; }
  /** Get the message data.
   *  @return the message data */
  public Object getData() { return data; }
  /** Get the object reference for the message source (who is it from).
   *  @return who it is from */
  public Object getFrom() { return from; }
  /** Get the object reference for the message destination (who is it to).
   *  @return who it is to */
  public Object getTo()   { return to; }
  /** Get the object reference for the message quals
   *  @return the Object reference for the message quals */
  public Object getQuals() { return quals; }

  /** Get the ID for the message source (who is it from). <i>Note that there is no
      <code>setFromID(..)</code> method since this is set via {@link #setFrom}.</i>
      @return The ID for the message source or "NOID" if unknown.
      @since NeXtMidas 2.7.0
   */
  public String getFromID() { return getID(from); }
  /** Get the ID for the message destination (who is it to). <i>Note that there is no
      <code>setToID(..)</code> method since this is set via {@link #setTo}.</i>
      @return The ID for the message source or "NOID" if unknown.
      @since NeXtMidas 2.7.0
   */
  public String getToID() { return getID(to); }

  /** Set the name of the message.
   * @param value the name of the message */
  public void setName(String value) { name=value; }
  /** Set the message info.
   * @param value the message info */
  public void setInfo(int value) { info=value; }
  /** Set the message data.
   * @param value the message data */
  public void setData(Object value) { data=value; }
  /** Set the object reference for the message source (who is it from)
   *  @param value object reference for the message source (who is it from) */
  public void setFrom(Object value) { from=value; }
  /** Set the object reference for the message destination (who is it to)
   *  @param value object reference for the message destination */
  public void setTo(Object value)   { to=value; }
  /** Set the object reference for the message qualifiers (extra data)
      @param value object reference for the message qualifiers (extra data)*/
  public void setQuals(Object value) { quals=value; }

  /** Brief string describing the message. */
  @Override
  public String toString() {
    return  toString("-DATA");
  }

  /** Brief string describing the message
   *  @param list options in String form
   *  @return String String version integer mask for the given list
   */
  public String toString (String list) {
    return toString(Parser.mask(stringList,list,defToStringMask));
  }

  /** Brief string describing the message
     @param mask Mask of values (see {@link #stringList})
     @return String of desired data
     @since NeXtMidas 2.7.1
   */
  public String toString (int mask) {
    String list = Parser.mask2s(stringList,mask).toUpperCase();
//System.out.println("Msg got toString: " +list);
    StringBuilder str = new StringBuilder();
    str.append("Message:");   // preamble
     for (int index=0; index < keys.length; index++) {
       String key = keys[index];
       if ((list.indexOf(key))>=0) {
         str.append(" "+keys[index] + "=" + getKey(key));
       }
     }

    return str.toString();
  }

  @Override
  public String[] getKeys() { return keys; }

  @Override
  public Object getKey (String key) {
    Object value = null;
         if (key.equals("NAME"))  value = getName();
    else if (key.equals("FID"))   value = getFromID();
    else if (key.equals("DATA"))  value = getData();
    else if (key.equals("TID"))   value = getToID();
    else if (key.equals("INFO"))  value = Integer.valueOf(getInfo());
    else if (key.equals("TO"))    value = getTo();
    else if (key.equals("FROM"))  value = getFrom();
    else if (key.equals("QUALS")) value = getQuals();
    return value;
  }

  @Override
  public Object setKey (String key, Object value) {
         if (key.equals("NAME"))  setName(Convert.o2s(value));
    else if (key.equals("INFO"))  setInfo(Convert.o2l(value));
    else if (key.equals("DATA"))  setData(value);
    else if (key.equals("TO"))    setTo(value);
    else if (key.equals("FROM"))  setFrom(value);
    else if (key.equals("QUALS")) setQuals(value);
    else return null;
    return value;
  }

  /** @deprecated Since NeXtMidas 2.7.0: Use {@link #getFromID()} or {@link #getToID()}.
   *  @param obj Object from which to get id
   *  @return id
   */
  @Deprecated public String getID (Object obj) {
    if (obj instanceof IDable) return ((IDable)obj).getID();
    else return "NOID";
  }

  /** Set the X-Midas message type. This setting controls how a message is translated when being
      sent to X-Midas. This setting is only applicable if the message is being sent to X-Midas.
      @param xmt One of the specified {@link XmMsgType} types.
      @since NeXtMidas 2.7.1
   */
  public void setXmMessageType (XmMsgType xmt) {
    xmMsgType = xmt;
  }

  /** Get the X-Midas message type. This setting controls how a message is translated when being
      sent to X-Midas. This setting is only applicable if the message is being sent to X-Midas.
      @return One of the specified {@link XmMsgType} types.
      @since NeXtMidas 2.7.1
   */
  public XmMsgType getXmMessageType () {
    return xmMsgType;
  }

}
