package nxm.sys.intr;

import nxm.sys.lib.BaseFile;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.FileUtil;
import nxm.sys.lib.IOResource;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.TextFile;

/** Provides generic file access from the macro language.

    @author Jeff Schoen / Jay Hallam / Neon Ngo
*/
public class File extends Intrinsic {

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

  @Override
  public int process () {
    // Get parameters
    String func  = MA.getU("FUNC");
    String label = MA.getU("LABEL");

    if (func.startsWith("OPEN")) {
      int flags = 0;
      if (func.indexOf("/A") > 0) { flags = flags | BaseFile.APPEND; }
      if (func.indexOf("/N") > 0) { flags = flags | BaseFile.OUTPUT; }
      if (func.indexOf("/W") > 0) { flags = flags | BaseFile.INOUT;  } // INOUT loads existing header

      BaseFile bf;
           if (func.indexOf("/D")>0) { bf = MA.getDataFile("P1"); } // Get a DataFile
      else if (func.indexOf("/T")>0) { bf = MA.getTextFile("P1"); } // Get a TextFile
      else                           { bf = MA.getFile("P1");     } // Get a file of unknown type
      bf.open(flags);
      MR.put(label,bf);
    }
    else if (func.equals("PROTECT") || func.equals("UNPROTECT")) {
      String fname = MA.getFileName("LABEL") + "";
      fname += (fname.endsWith(".cdif")) ? "{PROT=0,FORCE=TRUE}" : "{PROT=0}";
      DataFile df = new DataFile(this, fname);
      df.open(DataFile.INOUT);
      df.setProtected(func.equals("PROTECT"));
      df.close();
    }
    else if (func.startsWith("SAVE")) {       // The save function
      BaseFile bf = MA.getBaseFile("LABEL");  // input
      if (!bf.find(IOResource.DIR_INPUT)) M.error("File "+bf.getURL()+" not found for save");
      FileName fn = new FileName (bf.getURL()); fn.setExt("prm");
      bf.rename( fn.getFullName() );
    }
    else if (func.startsWith("RENAME")) {   // The rename function
      BaseFile bfi = MA.getFile("LABEL");   // input
      if (!bfi.find(IOResource.DIR_INPUT)) M.error("File "+bfi.getURL()+" not found for rename");
      BaseFile bfo = MA.getFile("P1");      // input
      boolean overWrite = (func.indexOf("/F")>0); // Force existing target file to be overwritten
      if (!overWrite && MA.getState("/F")) {
        // Deprecated since NeXtMidas 2.1.1
        M.deprecate("FILE: /F command switch is deprecated use RENAME/F.");
        overWrite = true;
      }
      if (bfo.find(IOResource.DIR_OUTPUT) && !overWrite) M.error("Use RENAME/F to overwrite existing file: "+bfo.getURL());
      bfi.rename( bfo.getURL() );
    }
    else if (func.startsWith("COPY")) {   // The copy function
      FileName inFile  = MA.getFileName("LABEL");
      FileName outFile = MA.getFileName("P1");
      int         xfer = MA.getL("/TL", BaseFile.BUFFER_SIZE); // since 3.1.2

      if ((func.indexOf("/F") < 0) && exists(outFile)) { // no overwrite
        M.error("Use COPY/F to overwrite existing file: "+outFile);
      }
      if (func.indexOf("/M") > 0) { // make dirs
        Object flags = outFile.getQualifier("FLAGS");
        if (flags == null) outFile.setQualifier("FLAGS","MKDIR");
        else               outFile.setQualifier("FLAGS",flags.toString()+"|MKDIR");
      }

      //if (func.startsWith("COPY/L")) { // Changed to line below per BUG 1774
      if (func.indexOf("/L") > 0) {
        String propMask = MA.getS("/PROPMASK", "NONE");

        if (MA.getState("/PROPAGATE", true)) {
          FileUtil.copyListFile(M, inFile, outFile, FileUtil.PROPAGATE, propMask);
        } else {
          FileUtil.copyListFile(M, inFile, outFile, null,               propMask);
        }
      } else {
        FileUtil.copyBaseFile(M, inFile, outFile, xfer);
      }
    }
    else if (func.startsWith("NAME")) {
      // Get the parameters
      String[] path = parseList(MA.getCS("P1"));
      String   root = MA.getCS("P2");
      String   ext  = MA.getCS("P3");
      FileName fn;
      if (path != null && path.length != 0) {
        // Form is "(p1,p2,p3,...) root ext" or ...
        // Parse the list of paths
        if (!FileUtil.startsWithRootDir(path[0])) {
          if (path[0].indexOf(':') > 0) {
            // A URL or drive letter on windows, no leading '/' required
          } else { //
            path[0] = FileName.LOCAL_PATH_DELIM + path[0];
          }
        }
        fn = new FileName(path, root, ext);
      } else {
        // Form is "root ext"
        fn = new FileName("", root, ext);
      }

      // Build a file name
      MR.put (label, fn.getFullName());
    }
    else if (func.startsWith("DETACH")) {
      DataFile dfi = MA.getDataFile("LABEL", "", "", DataFile.INOUT);
      String   dataFnExtension   = MA.getCS("P1", "1"); // "1" defaults to .det
      boolean  overwriteExisting = (func.indexOf("/F") > 0);
      boolean  copyDataToNewFile = (func.indexOf("/D") > 0); // i.e. file{DET=1}
      detachBLUE(dfi, dataFnExtension, overwriteExisting, copyDataToNewFile);
    }
    else {
      Object f = MR.get(label);
      TextFile tf = null;
      DataFile df = null;
      BaseFile bf = null;
           if (f instanceof TextFile) tf = (TextFile)f;
      else if (f instanceof DataFile) df = (DataFile)f;
      else if (f instanceof BaseFile) bf = (BaseFile)f;
      else M.error ("File "+label+" must be a Text|Data|BaseFile object");

      if (func.equals("READ")) {
        Object value = null;
        if (tf!=null && tf.isOpen) value = tf.read();
        if (df!=null && df.isOpen) value = df.getData(df.seek());
        String p1 = MA.getU("P1");
        if (p1.length()>0) MR.put( p1, value);
        else M.println(value.toString());  // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
      }
      else if (func.equals("NEXT")) {
        Object value = null;
        if (tf!=null && tf.isOpen) value = tf.readProper();
        String p1 = MA.getU("P1");
        if (p1.length()>0) MR.put( p1, value);
        else M.println(value.toString());  // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
      }
      else if (func.equals("WRITE")) {
        if (tf!=null && tf.isOpen) tf.writeln(MA.getS("P1"));
        if (df!=null && df.isOpen) df.setData(df.seek(),MA.getO("P1"));
      }
      else if (func.equals("CLOSE")) {
        if (tf!=null && tf.isOpen) tf.close();
        if (df!=null && df.isOpen) df.close();
        if (bf!=null && bf.isOpen) bf.close();
        MR.remove(label);
      }
      else {
        M.warning ("Unsupported function: "+func);
      }
    }
    return FINISH;
  }

  /** Indicates if a given file exists. */
  private boolean exists(FileName fn) {
    return M.io.findResource(fn,IOResource.DIR_INPUT).exists();
  }

  /** Parse "(,,,,,)" or "text" path list string into an array of path strings
   *  @param in "(,,,,,)" or "text" path list strin
   *  @return an array of path string
   */
  public String[] parseList (String in) {
    String plist[] = null;
    if ((in == null) || in.isEmpty()) {
      // ignore
    }
    else {
      if (in.indexOf('(') >= 0) {
        if (in.indexOf(')') >= 0 ) {
          String pathDelimString = in.substring(1,in.length()-1);
          Parser p = new Parser (pathDelimString);
          plist = new String[p.elements()];
          int i = 1;
          while (p.more()) {
            plist[i-1] = p.get(i);
            i++;
          }
        } else {
          M.warning("invalid parentheses");
        }
      } else {
        plist = new String[1];
        plist[0] = in;
      }
    }
    return plist;
  }

  /** Detach header and/or data from a BLUE file.
      @param df        BLUE file to create a detached header from
      @param detachExt Detached data file extension ("1" results to .det) without dot.
                       NOTE: only the first four chars are used. Cannot be null.
      @param overwrite if there is an existing .det, overwrite it, otherwise error out
      @param copyData  if true, copy data from specified file (this becomes the header file)
                       into .det (data) file (at byte offset 0). otherwise the file
                       is renamed to the .det file and a new header file is created.
      @throws MidasException if df does not exist OR
              overwrite is false and there is existing .det file
      @since NeXtMidas 2.9.1 for BUG 2182 FILE: Add DETACH and DETACH/D functions
   */
  private void detachBLUE (DataFile df, String detachExt, boolean overwrite, boolean copyData) {
    df.open(); // first open file (to read headers from it)
    if (df.getDetached() != 0) {
      M.warning("Header is already detached for "+df.getURL());
    }
    else {
      df.setDetached(detachExt);
      String detfilename = df.getDetachName();
      if (!overwrite && M.io.exists(detfilename)) {
        df.setOutput(false); // trick df.close() NOT to write updated header
        df.close();
        M.error("Use DETACH/F modifier to overwrite existing file: "+detfilename);
      }

      String   inurl    = df.getURL();
      DataFile dfHeader = new DataFile(M, inurl, df, DataFile.HEADERONLY);
      if (copyData) {
        BaseFile bfData = new BaseFile(M, detfilename);

        FileUtil.copyBaseFile(M, df, bfData);
        dfHeader.setDataStart(0.0);
        bfData.close();
        if (verbose) M.info("Data section copied to: " + detfilename);
      }
      else { // rename to .det
        int renameStatus  = df.rename(detfilename); // rename() does a close()
        if (renameStatus == DataFile.E_HCB_OK || renameStatus == DataFile.E_PKT_OK) {
          if (verbose) M.info("Data renamed to: " + detfilename+" dataStart="+df.getDataStart());
        } else {
          M.error("Renaming " + inurl +" to " + detfilename + " - Deatch aborted");
        }
      }

      // create BLUE header file
//    dfHeader.setDetached(detachExt); // NTN DO NOT call before open(), otherwise DataStart gets reset to zero
      dfHeader.open();
      dfHeader.setDetached(detachExt);
      dfHeader.setSize(df.getSize()); // set size, otherwise close() -> update(), resets size to wsize (which is zero)
      dfHeader.close();
      if (verbose) M.info("Header detached to: "+ inurl);
    }
    df.close();
  }
}
