package nxm.sys.intr;

import java.util.Arrays;
import java.util.TreeSet;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.TextFile;
import nxm.sys.lib.Util;
import nxm.sys.lib.XmlFile;

/** Handles the creation and other functions related to tables.

    @author Jay Hallam / Neon Ngo / J. Czechowski IV
 */
public class Table extends Intrinsic {

  /** These Table functions either don't need TAB= or it is used as label for
      output results, so we don't have to do <code>tab = MA.getTable("TAB");</code>.
      Since 2.7.1, that API throws a MidasException if input is an invalid table.
   */
  private static final TreeSet<String> FUNCS_TABLE_NOT_INPUT =
    new TreeSet<>(Arrays.asList("CREATE","GETDEFAULTFLAGS","GETDEFAULTMODE",
        "LOAD","LOAD/O","LOAD/O/C","LOAD/C/O","LOAD/T","LOAD/X",
        "MERGE","MERGE/L","MERGE/R",
        "SETDEFAULTFLAGS","SETDEFAULTMODE"));

  private static final String flagsList = nxm.sys.lib.Table.flagsList;

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

  @Override
  public int process () {
    nxm.sys.lib.Table tab = null;
    // Get parameters
    String func = MA.getU("FUNC","LIST");
    if (!FUNCS_TABLE_NOT_INPUT.contains(func)) {
      tab = MA.getTable("TAB"); // Since 2.7.1 this will ERROR if input is an invalid table
      if (tab==null && !func.equals("EQUALS")) M.error("TABLE: TAB= is required for FUNC="+func);
    }

    if (func.equals("LIST")) {
      nxm.sys.intr.Results.listMultipleResult(M, tab, "*", 0, true);
    }
    else if (func.equals("COPY")) {
      String label = MA.getUQ("P3");
      nxm.sys.lib.Table tabout = new nxm.sys.lib.Table(tab.toString());
      MR.put(label, tabout);
    }
    else if (func.equals("CREATE")) {
      String label = MA.getUQ("TAB");
      nxm.sys.lib.Table newtab;
      if (MA.isPresent("P3")) { newtab = MA.getTable("P3");       }
      else                    { newtab = new nxm.sys.lib.Table(); }
      MR.put(label,newtab);
    }
    else if (func.startsWith("MERGE")) {
      // New implementation as of 2.7.1
      nxm.sys.lib.Table tab2 = MA.getTable("P3");
      if (tab2 == null) M.warning("Missing table1 -> Usage: 'TABLE <out> MERGE <table1> <table2>'");
      nxm.sys.lib.Table tab3 = MA.getTable("P4");
      if (tab3 == null) M.warning("Missing table2 -> Usage: 'TABLE <out> MERGE <table1> <table2>'");

      boolean recurse = false;
      if(func.indexOf("/R") > 0) recurse = true;

      nxm.sys.lib.Table tabout = nxm.sys.lib.Table.merge(tab2, tab3, recurse);
      String label = MA.getUQ("TAB");
      MR.put(label, tabout);
    }
    else if (func.equals("EQUALS")) {
      nxm.sys.lib.Table tab2 = MA.getTable("P3");
      String resName = MA.getU("P4");
      boolean equal = true;

      if (tab == tab2) {
        // They are the same.
      } else if ((tab == null) || (tab2 == null)) {
        equal = false;
      } else if (tab.size() != tab2.size()) {
        equal = false;
      } else {
        String[] keys = tab.getKeys();
        for (int i = 0; equal && (i < keys.length); i++) {
          equal = Util.equals(tab.getO(keys[i]), tab2.getO(keys[i]));
        }
      }
      if (resName == null || resName.length()==0) {
        M.println("Table: Equal="+equal);  // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
      } else {
        MR.put(resName, equal);
      }
    }
    else if (func.equals("GETKEY")) { // since NeXtMidas 2.7.1 for Bug 1844
      String key   = MA.getU("P3");
      String label = MA.getU("P4", null);
      Object value = tab.getx(key);
      if (label!=null) MR.put(label, value);
      else nxm.sys.intr.Results.listSingleResult(M, tab, key, 0, true);
    }
    else if (func.equals("SETKEY")) { // since NeXtMidas 2.7.1 for Bug 1844
      String key   = MA.getU("P3");
      Object value = MA.getO("P4",null);
      tab.putx(key, value);
    }
    else if (func.equals("SETMODE")) {
      String tabMode = MA.getS("P3");
      tab.setMode(tabMode);
    }
    else if (func.equals("GETMODE")) {
      String modeName = tab.getModeName();
      MR.setKey(MA.getU("P3"), modeName);
      if (verbose) M.info("Table mode = "+modeName);
    }
    else if (func.equals("SETFLAGS")) {
      String tabFlags = MA.getS("P3");
      tab.setFlags(tabFlags);
    }
    else if (func.equals("GETFLAGS")) {
      String tabFlags = tab.getFlags();
      MR.setKey(MA.getU("P3"), tabFlags);
      if (verbose) M.info("Table flags = "+tabFlags);
    }
    else if (func.equals("SETDEFAULTMODE")) {
      String tabMode = MA.getS("P3");
      nxm.sys.lib.Table.setDefaultMode(tabMode);
    }
    else if (func.equals("GETDEFAULTMODE")) {
      String defModeName = nxm.sys.lib.Table.getDefaultModeName();
      MR.setKey(MA.getU("P3"), defModeName);
      if (verbose) M.info("Default table mode = "+defModeName);
    }
    else if (func.equals("SETDEFAULTFLAGS")) {
      String tabFlags = MA.getS("P3");
      nxm.sys.lib.Table.setDefaultFlags(tabFlags);
    }
    else if (func.equals("GETDEFAULTFLAGS")) {
      String defTableFlags = nxm.sys.lib.Table.getDefaultFlags();
      MR.setKey(MA.getU("P3"), defTableFlags);
      if (verbose) M.info("Default table flags = "+defTableFlags);
    }
    else if (func.equals("GETSIZE")) {
      int tabSize = tab.getSize();
      MR.setKey(MA.getU("P3"), Convert.l2o(tabSize));
      if (verbose) M.info("Table size = "+tabSize);
    }
    else if (func.equals("GETESCAPEDSTRING")) {
      MR.setKey(MA.getU("P3"), tab.getEscapedString());
    }
    else if (func.startsWith("GETKEYS")) {
      String[] keys  = tab.getKeys();
      Object   value;
      if(func.indexOf("/R") > 0) {
        for(int i = 0; i < keys.length/2; i++) {
          String temp = keys[i];
          keys[i] = keys[keys.length-i-1];
          keys[keys.length-i-1] = temp;
        }
      }
      if(func.indexOf("/S") > 0) {
        value = StringUtil.join(keys, ",");
      } else {
        value = keys;
      }
      MR.setKey(MA.getU("P3"), value);
    }
    else if (func.startsWith("GETVALUESS")) {
      // Internal Option to get Values a String[]
      Object value;
      String[] values  = tab.getValuesS();
      if(func.indexOf("/S") > 0) {
        value = StringUtil.join(values, ",");
      } else {
        value = values;
      }
      MR.setKey(MA.getU("P3"), value);
    }
    else if (func.startsWith("SORT")) {
      boolean reverse = (func.indexOf("/R") > 0);
      if (func.indexOf("/K") > 0) {
        tab.sortByKey(reverse);
      } else if (func.indexOf("/V") > 0) {
        M.warning("TABLE: Function SORT/V not implemented yet.");
      } else {
        M.error("TABLE: The SORT function requires either the /K or /V modifier.");
      }
    }
    else if (func.startsWith("LOADCFG")) {
      TextFile inFile = MA.getTextFile("P3");
      boolean  cs     = func.contains("/C");
      boolean  merge  = func.contains("/D");
      inFile.open(TextFile.FORCEABORT);
      String[] allLines = inFile.readAllLines();
      inFile.close();
      MR.put(MA.getUQ("TAB"), nxm.sys.lib.Table.fromConfigFile(allLines,cs,merge));
    }
    else if (func.equals("SAVECFG")) {
      tab = MA.getTable("TAB");
      TextFile outFile = MA.getTextFile("P3");
      outFile.open(TextFile.OUTPUT);
      for (String line : tab.toConfigFile()) outFile.writeln(line);
      outFile.close();
    }
    else if (func.startsWith("LOAD")) {
      // JC4: Can not do this since MA.getTable(..) calls Convert.o2t(..) which checks for the
      // file name to match "<something>.tbl", this may not be the case for files that come in via a URL.
      // MR.put(MA.getUQ("TAB"), MA.getTable("P3"));
      nxm.sys.lib.Table inTable;
      boolean hasFlags = MA.isPresent("/FLAGS");
      int tabFlags = MA.getOptionMask("/FLAGS", flagsList, nxm.sys.lib.Table.getDefaultFlagsInt()); // since 3.1.2: table flags to use
      if (func.indexOf("/T") > 0) {
        if (hasFlags) inTable = new nxm.sys.lib.Table(MA.getTextFile("P3"), tabFlags); // since 3.1.2
        else          inTable = new nxm.sys.lib.Table(MA.getTextFile("P3"));           // keeps legacy behavior
      } else if (func.indexOf("/X") > 0) {
        inTable = XmlFile.fastXmlFileToTable(MA.getTextFile("P3"));
      } else if (func.indexOf("/O") > 0) {
        inTable = nxm.sys.lib.Table.fromOpalFile(this, MA.getFileName("P3"), func.contains("/C"));
      } else {
        inTable = MA.getTable("P3"); // Simple case (has .tbl or .xml extension)
      }
      if (hasFlags) inTable.setFlags(tabFlags); // since 3.1.2: set table flags (NOTE: this is NOT recursive to sub-tables)

      MR.put(MA.getUQ("TAB"), inTable);
    }
    else if (func.equals("SAVE")) {
      // since 3.5.3 Bug 2876 check for flags on SAVE as well, since
      boolean hasFlags = MA.isPresent("/FLAGS");
      int tabFlags = MA.getOptionMask("/FLAGS", flagsList, nxm.sys.lib.Table.getDefaultFlagsInt());
      if (hasFlags) tab.setFlags(tabFlags); // since 3.5.3 set table flags (NOTE: this is NOT recursive to sub-tables)
      tab.toTextFile(MA.getTextFile("P3"));
    }
    else if (func.equals("KEYMOD")) {
      String regexToReplace   = MA.getU("P3");
      String replacement = MA.getU("P4");
      tab.modifyKeys(regexToReplace, replacement);
    }
    else {
      M.error("Unsupported table function: "+func);
    }

    return FINISH;
  }

  /** Imports an OPAL table into NeXtMidas.
      @param in       The input OPAL table.
      @param keepCase Ignore normal NeXtMidas rules and treat OPAL table keys as case-sensitive
                      (this will make the table difficult to use within NeXtMidas).
      @return The imported OPAL table as a NeXtMidas table.
      @since NeXtMidas 3.3.3
      @deprecated Since NeXtMidas 3.7.0 use {@link nxm.sys.lib.Table#fromOpalFile(String[], boolean)}
   */
  @Deprecated
  @InternalUseOnly
  public static nxm.sys.lib.Table loadOpalTable (TextFile in, boolean keepCase) {
    in.open(TextFile.INPUT);
    String[]     lines  = in.readAllLines();
    in.close();
    return nxm.sys.lib.Table.fromOpalFile(lines, keepCase);
  }
}
