package nxm.sys.test.junit.lib;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;

import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.FileName;
import nxm.sys.lib.FileName.FNCase;
import nxm.sys.lib.IOResource;
import nxm.sys.lib.Midas;
import nxm.sys.lib.NeXtMidas;
import nxm.sys.lib.NeXtMidas.OptionFlags;
import nxm.sys.lib.OptionTree;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;
import nxm.sys.test.junit.NMTestCase;

/** This will test the FileName class.

    @author  J. Czechowski IV / Neon Ngo
 */
public final class FileNameTest extends NMTestCase {

  /** public constructor to allow running this test case directly with JUnit. */
  public FileNameTest (String methodName) { this(methodName, Shell.getSharedMidasContext()); }

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
      @param midas      The midas context to use when running the tests.
   */
  public FileNameTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////

  public void testToURL () throws MalformedURLException {
    try {
      boolean isWindowsOS = Shell.isWindows();
      String nmroot = (isWindowsOS) ? Shell.getNmRoot().replace('\\','/') : Shell.getNmRoot();
      String origAuxDir=Shell.getAuxPath("DAT");
      // Only change aux.dat when dealing the specialty case of dat being at nxm/sys/test/dat
      // Setting aux.dat when run in nextopt does not behave as expected - NXM-3809 since NeXtMidas 4.1.1
      if (urlDat.contains("nxm/sys/test/dat")) {
        getMidas().results.put("AUX.DAT",urlDat);
      }
      String sysDatUrlString =
          IOResource.getInstance(Shell.getMidasContext(), IOResource.UNKNOWN, urlDat, 0, null).getURL();
      String sysTestUrlString =
          IOResource.getInstance(Shell.getMidasContext(), IOResource.UNKNOWN, Shell.getOptPath("SYS", "TEST"), 0, null).getURL();
      boolean isJarFile = nmroot.startsWith("jar:") || Shell.isWebStart() || nmroot.startsWith("classpath:");
      String prependFileScheme = isJarFile? "" : (isWindowsOS)? "file:/" : "file://";
      URL expectedApennyURL = isJarFile? new URI(sysDatUrlString+"apenny.prm").toURL() : new URI(prependFileScheme+nmroot+datDirSlash+"/apenny.prm").toURL();
      URL expectedHiddenURL = isJarFile? new URI(sysTestUrlString+".test_hidden").toURL() : new URI(prependFileScheme+nmroot+"nxm/sys/test/.test_hidden").toURL();
      if(Shell.isGradleBuild()) {
        expectedHiddenURL = new URI(sysTestUrlString+".test_hidden.prm").toURL();
      }
      String apennyURLstr   = expectedApennyURL.toString();
      File  apennyFile      = new File(apennyURLstr);
      String apennyFilePlusQualStr = apennyFile+"{FG=PRM}";

      boolean isNeXtOpt = Shell.isNeXtOpt();
      URL expectedFileURL      = expectedApennyURL;
      URL expectedWithDatQuals = (isNeXtOpt)? null : expectedApennyURL; // null to skip under NeXtOpt since there is no "DAT" AUX

      // Use {EXIST=none} on all URLs so we don't don't bother going to the server to check for the file's existence
      Object[] fileNames = {
          new FileName(datDir+".apenny.prm"),
          datDir+".apenny.prm",
          apennyFile,
          apennyFilePlusQualStr,
          apennyURLstr,
          apennyURLstr+"{FG=PRM}",
          "apenny.prm{AUX=DAT}",         // <--- skip under NeXtOpt since there is no "DAT" AUX
          "apenny.prm(0:128){AUX=DAT}",  // <--- skip under NeXtOpt since there is no "DAT" AUX
          "http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm{EXIST=none}",
          "http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm{FG=PRM}{EXIST=none}",
          "http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm(0:128){FG=PRM}{EXIST=none}",
          expectedHiddenURL.toString()  // since 3.7.3 for NXM-3113
      };
      URL[] expectedURLs = {
          expectedApennyURL,
          expectedApennyURL,
          expectedFileURL,
          expectedFileURL,
          expectedApennyURL,
          expectedApennyURL,
          expectedWithDatQuals, // skip under NeXtOpt since X-Midas does not supported named AUXes
          expectedWithDatQuals, // skip under NeXtOpt since X-Midas does not supported named AUXes
          new URI("http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm").toURL(),
          new URI("http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm").toURL(),
          new URI("http://localhost:9999/nxmxxx/nxm/sys/dat/apenny.prm").toURL(),
          expectedHiddenURL
      };

      for (int ii = 0; ii < fileNames.length; ii++) {
        URL expectedURL = expectedURLs[ii];
        if (expectedURL == null) continue; // skip this test case, see above for reasons
        Object testObj  = fileNames[ii];
        FileName fn     = new FileName(testObj.toString());

        URL actual1 = FileName.toURL(null,testObj, IOResource.DIR_INPUT);
        URL actual2 = fn.toURL(IOResource.DIR_INPUT);
        if (!isJarFile && !Shell.isWindows()) {
          assertEquals("FileName.toURL(null, '"+testObj+"', DIR_INPUT)", expectedURL, actual1);
          assertEquals("FileName.toURL(DIR_INPUT) with '"+testObj+"'",   expectedURL, actual2);
        }
        else {
          // On Linux running from jar some of the URLs comes back as "jar:file:////....".
          // Linux treats multiple slashes are treated as 1 slash, but it causes a problem comparing URLs
          // For this case only, stripping extra '/'s to compare
          String expected = expectedURL.toString().replaceAll("/+","/");
          assertEquals("FileName.toURL(null, '"+testObj+"', DIR_INPUT)", expected, actual1.toString().replaceAll("/+","/"));
          assertEquals("FileName.toURL(DIR_INPUT) with '"+testObj+"'",   expected, actual2.toString().replaceAll("/+","/"));
        }
      }

      if (urlDat.contains("nxm/sys/test/dat")) getMidas().results.put("AUX.DAT",origAuxDir);
    }
    catch (URISyntaxException | MalformedURLException e) {
      throw new MalformedURLException("Forming URL via URI failed:"+e.getMessage()+" "+e.getStackTrace());
    }
  }

  // the following Tables are used in testGetQualifierTable()
  private static final Table EMPTY_TABLE = new Table();
  private static final Table EXP_FG_TABLE = new Table("{FG=PRM}");
  private static final Table EXP_AUX_TABLE = new Table("{AUX=RAM}");
  private static final Table EXP_AUXDAT_TABLE = new Table("{AUX=DAT}");
  private static final Table EXP_FG_TAG_TABLE = new Table("{FG=PRM,TAG=PENNY}");
  private static final Table EXP_TABLE4 = new Table("{AUX=RAM,FG=PRM,LAYER={LT=2D},TAG=PENNY}");
  private static final QualsTest[] QUAL_TESTS = { // filename, expected(trim start index, qual start index, qualifier string, qualifier table)
    //             0         1         2         3         4         5
    //             012345678901234567890123456789012345678901234567890123456789
    new QualsTest("nxm.sys.dat.apenny.prm",                                      -1, -1, null, EMPTY_TABLE),
    new QualsTest("nxm.sys.dat.apenny.prm{}",                                    -1, 22, "{}", EMPTY_TABLE),
    new QualsTest("nxm.sys.dat.apenny.prm{FG=PRM}",                              -1, 22, "{FG=PRM}", EXP_FG_TABLE),
    new QualsTest("apenny.prm",                                                  -1, -1, null, EMPTY_TABLE),
    new QualsTest("apenny.prm{AUX=RAM}",                                         -1, 10, "{AUX=RAM}", EXP_AUX_TABLE),
    new QualsTest("apenny.prm{AUX=RAM,FG=PRM,LAYER={LT=2D},TAG=PENNY}",          -1, 10, "{AUX=RAM,FG=PRM,LAYER={LT=2D},TAG=PENNY}",   EXP_TABLE4),
    new QualsTest("apenny.prm{AUX=RAM}{FG=PRM}{LAYER={LT=2D},TAG=PENNY}",        -1, 10, "{AUX=RAM}{FG=PRM}{LAYER={LT=2D},TAG=PENNY}", EXP_TABLE4),
    new QualsTest("apenny.prm(0:128){AUX=RAM,FG=PRM,LAYER={LT=2D},TAG=PENNY}",   10, 17, "{AUX=RAM,FG=PRM,LAYER={LT=2D},TAG=PENNY}",   EXP_TABLE4),
    new QualsTest("apenny.prm(0:128){AUX=RAM}{FG=PRM}{LAYER={LT=2D},TAG=PENNY}", 10, 17, "{AUX=RAM}{FG=PRM}{LAYER={LT=2D},TAG=PENNY}", EXP_TABLE4),
    new QualsTest("tar:mytar.tar{FG=TAR}!apenny.prm{FG=PRM}",                    -1, 32, "{FG=PRM}", EXP_FG_TABLE),
    new QualsTest("tar:mytar.tar{FG=TAR}!apenny.prm{FG=PRM}{TAG=PENNY}",         -1, 32, "{FG=PRM}{TAG=PENNY}", EXP_FG_TAG_TABLE),
    new QualsTest("tar:mytar.tar{FG=TAR}!apenny.prm(0:128){FG=PRM}",             32, 39, "{FG=PRM}", EXP_FG_TABLE),
    new QualsTest("tar:mytar.tar{FG=TAR}!apenny.prm(0:128){FG=PRM}{TAG=PENNY}",  32, 39, "{FG=PRM}{TAG=PENNY}", EXP_FG_TAG_TABLE),

    // Old-style X-Midas quals
    new QualsTest("apenny.prm",                                                  -1, -1, null, EMPTY_TABLE),
    new QualsTest("apenny.prm(AUX=DAT)",                                         -1, 10, "(AUX=DAT)", EXP_AUXDAT_TABLE),
    new QualsTest("apenny.prm(0:128)(AUX=DAT)",                                  10, 17, "(AUX=DAT)", EXP_AUXDAT_TABLE),
    //             0         1         2         3         4         5
    //             012345678901234567890123456789012345678901234567890123456789
  };


  public void testGetQualifiersIndex () {
    for (QualsTest qt : QUAL_TESTS) {
      assertEquals("getQualifiersIndex("+qt+")", qt.qualIndex, FileName.getQualifiersIndex(qt.url));
    }
  }

  public void testGetTrimmersIndex () {
    for (QualsTest qt : QUAL_TESTS) {
      assertEquals("getTrimmersIndex("+qt+")", qt.trimIndex, FileName.getTrimmersIndex(qt.url));
    }
  }

  public void testGetQualifierString () {
    for (QualsTest qt : QUAL_TESTS) {
      assertEquals("getQualifierString("+qt+")", qt.qualString, FileName.getQualifierString(qt.url));
    }
  }

  /** @since NeXtMidas 3.1.0 */
  public void testGetQualifierTable () {
    for (QualsTest qt : QUAL_TESTS) {
      FileName fn = new FileName(qt.url);
      Table qualsTable = fn.getQualifierTable();
      assertNotNull("getQualifierTable("+qt+") is not null => "+qualsTable, qualsTable); // test for BUG 2497
      assertEquals("getQualifierTable("+qt+") contents: "+qualsTable, qt.qualTable, qualsTable);
    }
  }

  /** tested in {@link #testSetQualifiers} */
  public void testGetQualifier () {}
  /** tested in {@link #testSetQualifiers} */
  public void testSetQualifier () {}
  /** @since NeXtMidas 3.1.0 */
  public void testSetQualifiers () {
    FileName fn = new FileName("testfile.tmp");
    fn.setQualifiers("{A=first,B=Second,C=4,C=3}");
    fn.setQualifier("D", "Last"); // test setQualifier(String key, Object val) method
    assertEquals("setQualifiers(.) A tag", "first",     fn.getQualifier("A"));
    assertEquals("setQualifiers(.) B tag", "Second",    fn.getQualifier("B"));
    assertEquals("setQualifiers(.) C tag", new Data(3), fn.getQualifier("C"));
    assertEquals("setQualifiers(.) D tag", "Last",      fn.getQualifier("D"));
    assertEquals("setQualifiers(.) X tag", "defvalue",  fn.getQualifier("X","defvalue"));
  }

  /** tested in {@link #testSetDefaultQualifiers} */
  public void testSetDefaultQualifier () {}
  /** @since NeXtMidas 3.1.0 */
  public void testSetDefaultQualifiers () {
    HashMap<String, Object> map = new HashMap<>();
    FileName fn = new FileName("testfile.tmp{A=\"first\",B=\"Second\",C=3}");
    fn.setDefaultQualifier("C","4");     // this exists, so should not override
    fn.setDefaultQualifier("D", "Last"); // this qualifier should be set
    map.put("E", "Sixth");
    map.put("D", "D should not be overriden with this");
    fn.setDefaultQualifiers(map); // this qualifier should be set
    assertEquals("setQualifiers(.) A tag", "first",     fn.getQualifier("A"));
    assertEquals("setQualifiers(.) B tag", "Second",    fn.getQualifier("B"));
    assertEquals("setQualifiers(.) C tag", new Data(3), fn.getQualifier("C"));
    assertEquals("setQualifiers(.) D tag", "Last",      fn.getQualifier("D"));
    assertEquals("setQualifiers(.) E tag", "Sixth",     fn.getQualifier("E"));
  }

  /** @since NeXtMidas 2.7.4 */
  public void testRelativeTo () throws MalformedURLException {
    boolean optionCmdExists = getMidas().dictionary.isDictionaryEntry("OPTION");
    if (optionCmdExists) {
      String nmroot = Shell.getNmRoot();
      if (nmroot.equals("classpath:")) {
        // since 3.7.3, support running with new "classpath:" option, for NXM-3099
        String urlSysDatString = IOResource.getInstance(Shell.getMidasContext(), IOResource.UNKNOWN, Shell.getOptPath("SYS", "DAT"), 0, null).getURL();
        nmroot = urlSysDatString.substring(0, urlSysDatString.length()-12);
        NeXtMidas.getGlobalInstance().addOptionTreeToPathClasspath("DSP",  OptionFlags.ADD_PLUS);
      }
      String sep = java.io.File.separator ; // path separator
      String[] fileNames = {
          "nxm/sys/hlp/concepts.hlp",
          "nxm.sys.mcr.demo.mm",
          "jar:http://myserver/nxm/jars/nxm-sys.jar!/nxm/sys/test/test_include.mm", // to test fix for Bug 1463 under Web Start
          "/path/to/filename.ext",
          "jar:http://myserver/nxm/jars/nxm-sys.jar!/nxm/sys/test/",
          "jar:http://myserver/nxm/jars/nxm-sys.jar!/nxm/sys/test"
      };
      String[] names = {
          "plot.hlp",
          "exp/ffp.exp",
          "jay/exp/foot.exp",
          "include_file",         // test case for filename w/o extension (also part of test for Bug 1463)
          "fft.exp exp",          // <filename> <area> of SYS
          "commands.cnf cfg dsp", // <filename> <area> <optionTree under $NMROOT>
      };

      for (String filename : fileNames) {
        FileName fn = new FileName(filename);
        String fnExt = fn.getExt();
        boolean fnHasExt = !fnExt.equals("");
        String path = fn.getPath();

        // System.out.println("testing FN="+fn + " path="+path); // DEBUG
        for (String name : names) {
          if(name.equalsIgnoreCase("commands.cnf cfg dsp") && Shell.isGradleBuild()) continue;
          FileName relativefn = fn.relativeTo(name);
          String expected = path+name; // normal case
          if (name.equals("include_file") && fnHasExt) {
            expected = expected + "." + fnExt; // special case to add extension
          }
          else if (name.equals("fft.exp exp")) {     // special case for <file> <area> of SYS
            expected = nmroot+"nxm"+sep+"sys"+sep+"exp"+sep+"fft.exp";
          }
          else if (name.equals("commands.cnf cfg dsp")) { // special case for <file> <area> <optionTree under $NMROOT>

            String dsproot = nmroot.startsWith("jar:") ? nmroot.replaceFirst("sys", "dsp") : nmroot;
            expected = dsproot+"nxm"+sep+"dsp"+sep+"cfg"+sep+"commands.cnf";
          }

          assertTrue("relativeTo("+name+")  relfn="+relativefn.getFullName()+" ends with "+expected.replace("\\", "/"), relativefn.getFullName().endsWith(expected.replace("\\", "/")));
        } // end inner for loop
      } // end outer for loop
    }
    else {
      info("Skipping relativeTo(..) test, seem to be in LIBNXM mode without OPTION command");
    }
  }

  /** @since NeXtMidas 3.1.2 */
  public void testGetBasename () {
    //                        path                            basename      root      extension
    String[][] testcases = {{"myfile.prm",                    "myfile.prm",  "myfile", "prm"},
                            {"/home/smith/myfile",            "myfile"    ,  "myfile", ""   },
                            {"/home/smith/myfile.prm",        "myfile.prm",  "myfile", "prm"},
                            {"\\data\\myfile.tmp",            "myfile.tmp",  "myfile", "tmp"},
                            {"home/smith/MyFile.abc",         "MyFile.abc",  "MyFile", "abc"},
                            {"file:///home/smith/myfile.txt", "myfile.txt",  "myfile", "txt"},
                            {"http://server/data/myfile.tbl", "myfile.tbl",  "myfile", "tbl"},
                            {"nxm.sys.test.MyFile.xyz",       "MyFile.xyz",  "MyFile", "xyz"},
                            {"nxm.sys.test.sub.myFILE.dat",   "myFILE.dat",  "myFILE", "dat"},
                            {"/home/smith/mydir/",            ""          , ""      , ""   },

                            { "nxm.opt.area.fname.ext",       "fname.ext" , "fname" , "ext"},

                            {"/Home/Smith/myfile.prm",        "myfile.prm", "myfile", "prm"}, // Mixed-case dir
                            {"/home/smith/MyFile.prm",        "MyFile.prm", "MyFile", "prm"}, // Mixed-case name
                            {"/home/smith/myfile.Prm",        "myfile.Prm", "myfile", "Prm"}, // Mixed-case ext
                            {"/Home/Smith/MyFile.Prm",        "MyFile.Prm", "MyFile", "Prm"}, // Mixed-case all

                            {"/HOME/SMITH/myfile.prm",        "myfile.prm", "myfile", "prm"}, // Upper-case dir
                            {"/home/smith/MYFILE.prm",        "MYFILE.prm", "MYFILE", "prm"}, // Upper-case name
                            {"/home/smith/myfile.PRM",        "myfile.PRM", "myfile", "PRM"}, // Upper-case ext
                            {"/HOME/SMITH/MYFILE.PRM",        "MYFILE.PRM", "MYFILE", "PRM"}, // Upper-case all
                            // Since 3.5.1
                            { "tar:file:nxm.sys.test.mytar.tar!myfile.prm", "myfile.prm",  "myfile", "prm"},
                            { "nxm.sys.test.mytar.tar!dir1/myfile.prm",     "myfile.prm",  "myfile", "prm"},
                            { "/home/smith/mytar.tar!/dir2/myfile.prm",     "myfile.prm",  "myfile", "prm"},
                            { "myjar.jar!/dir/myfile.class",  "myfile.class","myfile", "class"},
                           };
    for (String[] testcase : testcases) {
      String path         = testcase[0];
      String expBasename  = testcase[1];
      String expRoot      = testcase[2];
      String expExtension = testcase[3];
      assertEquals("getBasename(String)", expBasename , FileName.getBasename(path));
      assertEquals("getRoot(String)",     expRoot     , FileName.getRoot(path));
      assertEquals("getExt(String)",      expExtension, FileName.getExt(path));

      for (FNCase fnCase : FNCase.values()) {
        String _expBasename  = expBasename;
        String _expRoot      = expRoot;
        String _expExtension = expExtension;
        switch (fnCase) {
          case LowerCase:
            _expBasename  = _expBasename.toLowerCase();
            _expRoot      = _expRoot.toLowerCase();
            _expExtension = _expExtension.toLowerCase();
            break;
          case KeepCase:
            break;
          case AutoCase:
            if (_expRoot.toUpperCase().equals(_expRoot)) {
              _expRoot      = _expRoot.toLowerCase();
            }
            if (_expExtension.toUpperCase().equals(_expExtension)) {
              _expExtension = _expExtension.toLowerCase();
            }
            _expBasename  = (_expExtension.isEmpty())? _expRoot : _expRoot + "." + _expExtension;
            break;
        }
        FileName fn = new FileName(path+"{FNCASE="+fnCase+"}");
        assertEquals("getBasename() with {FNCASE="+fnCase+"}", _expBasename , fn.getBasename());
        assertEquals(    "getRoot() with {FNCASE="+fnCase+"}", _expRoot     , fn.getRoot());
        assertEquals(     "getExt() with {FNCASE="+fnCase+"}", _expExtension, fn.getExt());

        fn = new FileName(path,fnCase);
        assertEquals("getBasename()", _expBasename , fn.getBasename());
        assertEquals("getRoot()",     _expRoot     , fn.getRoot());
        assertEquals("getExt()",      _expExtension, fn.getExt());
      }
    }
  }

  /** tested in {@link #testGetBasename()} */
  public void testGetRoot () {}
  /** tested in {@link #testGetBasename()} */
  public void testGetExt () {}

  /** @since NeXtMidas 3.3.1 */
  public void testGetCloneOf () {
    // Note the first 8 test cases demonstrate an issue with NXM 3.3.1rc1 where
    // qualifiers would report as "{}" rather than null and/or a cloneOf() call
    // would cause a NPE.
    String[] testcases = {
      // FILE NAME                   FULL NAME                 TRIMMERS  QUALIFIERS
      "/data/midas/myfile.tmp",      "/data/midas/myfile.tmp", null,     null,
      "myfile.tmp{AUX=DAT}",         "myfile.tmp",             null,     "{AUX=DAT}",
      "myfile{AUX=RAM}",             "myfile",                 null,     "{AUX=RAM}",
      "ram:myfile",                  "ram:myfile",             null,     null,
      "/data/midas/myfile.tmp(2:4)", "/data/midas/myfile.tmp", "(2:4)",  null,
      "myfile.tmp(2:4){AUX=DAT}",    "myfile.tmp",             "(2:4)",  "{AUX=DAT}",
      "myfile(2:4){AUX=RAM}",        "myfile",                 "(2:4)",  "{AUX=RAM}",
      "ram:myfile(2:4)",             "ram:myfile",             "(2:4)",  null,
    };

    for (int i = 0; i < testcases.length; i+=4) {
      String   fileName       = testcases[i];
      String   fullName       = testcases[i+1];
      String   trimmers       = testcases[i+2];
      Table    qualTbl        = Convert.o2t(testcases[i+3]);

      if (qualTbl != null) qualTbl.setMode(Table.SLHM);

      Table    qualifierTable = (qualTbl == null)? new Table() : qualTbl;
      String   qualifiers     = (qualTbl == null)? null        : qualTbl.toString();
      FileName fn             = new FileName(fileName, FNCase.KeepCase);
      FileName f2             = fn.cloneOf();

      assertEquals("cloneOf() with "+fileName+": initial full name",      fullName,       fn.getFullName());
      assertEquals("cloneOf() with "+fileName+": initial trimmers",       trimmers,       fn.getTrimmers());
      assertEquals("cloneOf() with "+fileName+": initial qualifiers",     qualifiers,     fn.getQualifiers());
      assertEquals("cloneOf() with "+fileName+": initial qualifierTable", qualifierTable, fn.getQualifierTable());

      assertEquals("cloneOf() with "+fileName+": cloneOf full name",      fullName,       f2.getFullName());
      assertEquals("cloneOf() with "+fileName+": cloneOf trimmers",       trimmers,       f2.getTrimmers());
      assertEquals("cloneOf() with "+fileName+": cloneOf qualifiers",     qualifiers,     f2.getQualifiers());
      assertEquals("cloneOf() with "+fileName+": cloneOf qualifierTable", qualifierTable, f2.getQualifierTable());
    }
  }

  /** @since NeXtMidas 3.3.4 */
  public void testFileName () {
    FileName fnLC = new FileName("NXM.SYS.CFG.HANDLERS.TBL", null);
    FileName fnUC = new FileName("nxm.sys.cfg.handlers.tbl", null);
    assertEquals("all lower and all UPPER case nxm dot syntax are equal", fnLC, fnUC);

    // test for BUG 2768
    final Midas midas = getMidasContext();
    final String optName = "testopt987654321";
    final String nxmdotFilenameLC = "nxm."+optName+".docs.filename.tbl";
    final String nxmdotFilenameUC = nxmdotFilenameLC.toUpperCase();
    OptionTree.defineOptionTree(midas, optName, "/MixedCase/path/");
    try {
      FileName fn1 = new FileName(nxmdotFilenameLC, null);
      FileName fn2 = new FileName(nxmdotFilenameUC, null);
      assertEquals("UPPER CASE nxm dot syntax to a Mixed Case option tree path", fn1, fn2);
    } finally {
      boolean cleaned = OptionTree.removeOptionTree(midas, optName);
      if (!cleaned) {
        midas.warning("Unable to remove test option tree definition for " +optName);
      }
    }
    // end test for BUG 2768
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  private static final class QualsTest {
    /** input filename URL */    final String url;
    /** expected trim index */   final int    trimIndex;
    /** expected qauls index */  final int    qualIndex;
    /** expected quals String */ final String qualString;
    /** expected quals Table */  final Table  qualTable;

    public QualsTest (String url, int trimIndex, int qualIndex, String qualString, Table qualTable) {
      this.url        = url;
      this.trimIndex  = trimIndex;
      this.qualIndex  = qualIndex;
      this.qualString = qualString;
      this.qualTable  = qualTable;
    }

    public String toString () { return url; }
  } // end inner class QualsTest

}
