package nxm.sys.libxm;

import java.util.ArrayList;
import java.util.Arrays;

import nxm.sys.inc.Filter;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;
import nxm.sys.lib.XmUtil;


/** A drop-in replacement for the normal NeXtMidas AUX table that maps the X-Midas AUX list.<br>
    <span style="color:red">Do NOT use this class directly, this class is for use by the SYS option
    tree ONLY and is subject to change without notice!</span> <br>
    <br>
    In addition to the X-Midas AUX entries (each with a numeric name) the following entries are added:
    <pre>
      WRITE     - The current write AUX (like the normal NeXtMidas AUX.WRITE)
      READ      - The current read AUX (like the normal NeXtMidas AUX.READ)
      XMPIPE    - The mapping of the X-Midas pipes (like the NeXtMidas AUX.RAM)
      CWD       - Alias for AUX.0
      HOME      - Alias for AUX.98
      RAM       - NeXtMidas RAM AUX (used for pipes/in-memory files)
      HOMEPATH  - Same as the X-Midas HOMEPATH (Mc.home_path)
      _TRACKCWD_- (Since 3.1.0) Denotes whether or not to keep the HOMEPATH in sync with the CWD
    </pre>
    The READ and WRITE paths are kept the same as X-Midas except that XMPIPE is always added to it.
    CWD and HOME are added (mapping to 0 and 98 respectively, if such are present) since many
    NeXtMidas commands use those names. HOMEPATH is also used extensively by NeXtMidas the same
    way Mc.home_path is used by X-Midas, however it can not be added to the read or write AUX when
    X-Midas is being used, it exists only as an entry in the AUX table.

    @author  prp
    @author  J. Czechowski IV
 */
@InternalUseOnly
public class XmAuxTable extends Table {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2012012500311L; // for Serializable interface

  /** The default keys on the AUX table. */
  private final String[] DEFAULT_KEYS = { "WRITE","READ","RAM","XMPIPE","HOME" };

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

  public int      size()     { return (getKeys().length);    }
  public Iterator iterator() { return new AuxIterator(this); }

  public String getString(String key) { return String.valueOf(getKey(key)); }

  public Object get (String key) {
    return getKey(key);
  }

/* TODO: This might not be needed... remove?
  @Override
  public Object put (String key, Object value, int attr, Object ref) {
    Object prevValue;
    if (key.startsWith("_") && key.endsWith("_")){
      prevValue = super.put(key,value,attr,ref); // special AUX entries  (Added in 3.1.1 for Bug 2481 & Bug 2482)
    }
    else prevValue = setKey(key, value);
    return prevValue; // since 3.3.3 for bug 2699
  }
*/

  @Override
  public Object putx(String key, Object value, int attr) {
    Object prevValue;
    if (key.startsWith("_") && key.endsWith("_")){
      prevValue = super.putx(key,value,attr); // special AUX entries  (Added in 3.1.1 for Bug 2481 & Bug 2482)
    }
    else prevValue = setKey(key, value);
    return prevValue; // since 3.3.3 for bug 2699
  }

  @Override
  public Object getKey (String key) {
    Object value = null;
    key = key.toUpperCase();

    if (key.equals("READ")) {
      // code update in 3.1.3 for BUG 2608
      final StringBuilder raux = new StringBuilder("XMPIPE|RAM");
      final int numActiveReadAuxes = Mc.getReadAuxs();
      for (int ii = 0; ii < numActiveReadAuxes; ii++) {
        raux.append("|").append(XmUtil.getAuxName(Mc.getReadAuxAt(ii)));
      }
      value = raux.toString();
    } else if (key.equals("WRITE")) {
      int num = Mc.getWriteAux();
      value = XmUtil.getAuxName(num);
    } else if (key.equals("XMPIPE")) {
      value = "xmpipe:";
    } else if (key.equals("RAM")) {
      value = "ram:";
    } else if (key.equals("HOME")) {
      value = Mc.getLoginPath();
    } else if (key.startsWith("_") && key.endsWith("_")){
      value = super.getKey(key); // special AUX entries  (Added in 3.1.1 for Bug 2481 & 2482)
    } else {
      int num = XmUtil.getAuxNumber(key);
      if (num < 0) {
        num = -num;
      }
      if (num == 98) {
        value = Mc.getHomePath();
      }
      else if (XMidas.valid_aux(num) > 0) {
        value = XMidas.form_path(num, Mc.getReadId());
      }
      else {
        throw new MidasException("Illegal AUX specification " + key);
      }
    }
    return value;
  }

  @Override
  public Object setKey (String key, Object value) {
    Object returnValue = value;
    String str = value.toString().toUpperCase();

    key = key.toUpperCase();
    if (key.equals("READ")) {
      if (str == null) {
        Shell.getSharedMidasContext().warning("Illegal AUX specification " + value);
      } else {
        // code updated in 3.1.3 for BUG 2608
        int    nelem = Math.min(Mc.L_AUXS, StringUtil.nelements(str, '|')); // Number of possible AUXs
        int    naux  = 0;                                              // Number of valid AUXs

        for (int i = 0; i < nelem; i++) {
          String v = StringUtil.getWordByElement(str, i + 1, "|");
          int num = XmUtil.getAuxNumber(v);

          if (v.equals("HOME")) {
            throw new MidasException("Can not add HOME to the PATH when using X-Midas.");
          }
          else if (v.equals("XMPIPE")) {
            // Ignore it.
          }
          else if (v.equals("RAM")) {
            // Ignore it.
          }
          else if (num > XmUtil.MAX_AUX_NUM) {
            throw new MidasException("The READ AUX can only include the numeric AUX available in "
                                   + "X-Midas, given "+v+".");
          }
          else if (XMidas.valid_aux(num) > 0) {
            Mc.setReadAuxAt(naux, (short)num);
            naux++;
          }
          else  {
            throw new MidasException("Bad Aux Value ("+v+")");
          }
        } // end for loop
        Mc.setReadAuxs(naux);
      }
    } else if (key.equals("WRITE")) {
      int num = XmUtil.getAuxNumber(value.toString());
      if (num > XmUtil.MAX_AUX_NUM) {
        throw new MidasException("The WRITE AUX must be set to one of the numeric AUX available in "
                               + "X-Midas, given "+value+".");
      } else if (XMidas.valid_aux(Math.abs(num)) > 0) {
        Mc.setWriteAux(num);
        returnValue = num;
      } else {
        throw new MidasException("Illegal AUX specification " + value);
      }
    } else if (key.equals("HOME")) {
        Mc.setLoginPath(value.toString());

    } else if (key.equals("RAM") || key.equals("XMPIPE")) {
      Shell.getSharedMidasContext().error("Can not modify AUX."+key+".");

    }
    else if (key.startsWith("_") && key.endsWith("_")) {
      super.setKey(key, value);  // special case internal use (Added in 3.1.1 for Bugs 2481 & 2482)
    }
    else {
      int num = XmUtil.getAuxNumber(key);

      if (num == 98) {
        Mc.setHomePath(value.toString());
      }
      else if (XMidas.define_aux(num, value.toString()) < 0) {
        throw new MidasException("Could not define aux "+key+"="+value);
      }
    }

    return returnValue;
  }

  @Override
  public String[] getKeys (Filter valueFilter) {
    // TODO: Fix filtering
    ArrayList<String> auxList = XMidas.create_aux_list();
    ArrayList<String> keyList = new ArrayList<>(Arrays.asList(DEFAULT_KEYS)); // NeXtMidas-Only named values
    ArrayList<String> intList = new ArrayList<>();                            // X-Midas AUX values

    /* Add the individual AUX numbers from X-Midas if they are defined. */
    for (int i = 0; i < auxList.size(); i++) {
      if (auxList.get(i) != null) {
        intList.add(Integer.toString(i));
      }
    }

    keyList.addAll(intList);
    return keyList.toArray(new String[0]);
  }

  /** Super class's containsKey(..) is not does not work for this class. Fix for BUG 1042.
      @param key The key to look for (not null). The dot syntax can be used to drill
                 down into keys for any sub-Table (i.e. not object's properties/methods).
      @return <code>true</code> if Table contains this key, <code>false</code> otherwise.
      @since NeXtMidas 2.4.2
   */
  @Override
  public boolean containsKey (String key) {
    String[] keys  = getKeys();
    boolean  found = false;

    for (int i = 0; !found && (i < keys.length); i++) {
      found = keys[i].equals(key);
    }
    return found;
  }

  /** An iterator used by the {@link Table} class. */
  public class AuxIterator extends Iterator {
    /** AuxIterator Constructor
     *  @param table X-Midas Auxiliary Table to be referenced
     */
    public AuxIterator(XmAuxTable table) {
      super(table, table.getKeys(), null);
    }

    @Override
    public void remove () {
      key = key.toUpperCase();
      if (key.equals("READ")) {
        Shell.getSharedMidasContext().warning("Cannot remove the READ aux entry");
      } else if (key.equals("WRITE")) {
        Shell.getSharedMidasContext().warning("Cannot remove the WRITE aux entry");
      } else if (key.equals(XmPipeResource.AUX_NAME)) {
        Shell.getSharedMidasContext().warning("Cannot remove the "+key+" aux entry");
      } else {
        Shell.getSharedMidasContext().warning("Cannot remove aux entries yet");
      }
    }
  }
}
