/*
 * Jalview - A Sequence Alignment Editor and Viewer (2.11.5.0)
 * Copyright (C) 2025 The Jalview Authors
 * 
 * This file is part of Jalview.
 * 
 * Jalview is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *  
 * Jalview is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty 
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
 * PURPOSE.  See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
 * The Jalview Authors are detailed in the 'AUTHORS' file.
 */
package jalview.util;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

@Test
public class FileUtilsTest
{
  @Test(groups = "Functional", dataProvider = "patternsAndMinNumFiles")
  public void testJavaFileGlob(String pattern, int atLeast, int atMost)
  {
    List<File> files = FileUtils.getFilesFromGlob(pattern);
    if (atLeast != -1)
    {
      Assert.assertTrue(files.size() > atLeast,
              "Did not find more than " + atLeast + " files with " + pattern
                      + " (found " + files.size() + ")");
    }
    if (atLeast != -1)
    {
      Assert.assertTrue(files.size() > atLeast,
              "Did not find more than " + atLeast + " files with " + pattern
                      + " (found " + files.size() + ")");
    }
    if (atMost != -1)
    {
      Assert.assertTrue(files.size() < atMost,
              "Did not find fewer than " + atMost + " files with " + pattern
                      + " (found " + files.size() + ")");
    }
  }

  @Test(groups = "Functional", dataProvider = "dirnamesAndBasenames")
  public void testDirnamesAndBasenames(String filename, String dirname,
          String endsWith, String basename, String notInDirname)
  {
    File file = new File(filename);
    String d = FileUtils.getDirname(file);
    String b = FileUtils.getBasename(file);
    Assert.assertEquals(b, basename);
    Assert.assertTrue(d.startsWith(dirname), "getDirname(" + file.getPath()
            + ")=" + d + " didn't start with '" + dirname + "'");
    Assert.assertTrue(d.endsWith(endsWith), "getDirname(" + file.getPath()
            + ")=" + d + " didn't end with '" + endsWith + "'");

    // ensure dirname doesn't end with basename (which means you can't use same
    // filename as dir in tests!)
    Assert.assertFalse(d.endsWith(b), "Processed dirname '" + d
            + "' ends with '" + b + "' when it shouldn't");

    if (notInDirname != null)
      Assert.assertFalse(d.contains(notInDirname), "Processed directory '"
              + d + "' contains '" + notInDirname + "' when it shouldn't");
  }

  /**
   * these need to be maintained as jalview's source base grows
   * 
   * @return
   */
  @DataProvider(name = "patternsAndMinNumFiles")
  public Object[][] patternsAndMinNumFiles()
  {
    return new Object[][] { { "src/**/*.java", 900, 100000 },
        { "src/**.java", 900, 100000 },
        { "test/**/*.java", 250, 2500 },
        { "test/**.java", 250, 2500 },
        { "help/**/*.html", 100, 1000 },
        { "test/**/F*.java", 15, 150 },
        { "test/jalview/*/F*.java", 10, 15 }, // 12 at time of writing
        { "test/jalview/**/F*.java", 18, 30 }, // 20 at time of writing
        { "test/jalview/util/F**.java", 1, 5 }, // 2 at time of writing
        { "src/jalview/b*/*.java", 14, 19 }, // 15 at time of writing
        { "src/jalview/b**/*.java", 23, 50 }, // 22 at time of writing, now 27
    };
  }

  @DataProvider(name = "dirnamesAndBasenames")
  public Object[][] dirnamesAndBasenames()
  {
    String homeDir = new File(System.getProperty("user.home")).getPath();
    return new Object[][] { // -1=startsWith, 0=equals, 1=endsWith
        { "~/hello/sailor", homeDir, "/hello", "sailor", "~" }, //
        { "./examples/uniref50.fa", "./", "examples", "uniref50", "Users" }, //
        { "./examples/uniref50.1.fa", "./", "examples", "uniref50.1",
            "Users" }, //
        { "examples/uniref50.fa", "examples", "examples", "uniref50",
            ".fa" }, //
    };
  }

  @Test(groups = "Functional", dataProvider = "convertWildcardsToPathData")
  public void convertWildcardsToPathTest(String value, String wildcard,
          String dirname, String basename, String path)
  {

    Assert.assertEquals(
            FileUtils.convertWildcardsToPath(value, wildcard, dirname,
                    basename),
            path, "Conversion of wildcard output path is not right.");

  }

  @DataProvider(name = "convertWildcardsToPathData")
  public Object[][] convertWildcardsToPathData()
  {
    return new Object[][] {
        /*
         * cmdline args
         * Arg (null if only testing headless)
         * String value if there is one (null otherwise)
         * boolean value if String value is null
         * expected value of isHeadless()
         */
        /*
        */
        { "*/*", "*", "{dirname}", "{basename}", "{dirname}/{basename}" },
        { "*/", "*", "{dirname}", "{basename}", "{dirname}/" },
        { "/*", "*", "{dirname}", "{basename}", "/{basename}" },
        { "*", "*", "{dirname}", "{basename}", "{basename}" },
        { "tmp/output/*/file-*.stk", "*", "{dirname}", "{basename}",
            "tmp/output/{dirname}/file-{basename}.stk" },
        { "/*.file", "*", "{dirname}", "{basename}", "/{basename}.file" },
        { "*/file.stk", "*", "{dirname}", "{basename}",
            "{dirname}/file.stk" },
        { "tmp/abc*def/file.stk", "*", "{dirname}", "{basename}",
            "tmp/abc{dirname}def/file.stk" },
        { "a*b/c*d", "*", "{dirname}", "{basename}",
            "a{dirname}b/c{basename}d" },
        { "a*b/c", "*", "{dirname}", "{basename}", "a{dirname}b/c" },
        { "a/b*c", "*", "{dirname}", "{basename}", "a/b{basename}c" },
        { "a*b", "*", "{dirname}", "{basename}", "a{basename}b" },
        { "aSTARb/cSTARd", "STAR", "BEFORE", "AFTER", "aBEFOREb/cAFTERd" },
        { "aSTARb/c", "STAR", "BEFORE", "AFTER", "aBEFOREb/c" },
        { "a/bSTARc", "STAR", "BEFORE", "AFTER", "a/bAFTERc" },
        { "aSTARb", "STAR", "BEFORE", "AFTER", "aAFTERb" },
        //
    };
  }

  @Test(
    groups = "Functional",
    dataProvider = "stringFilenamesBaseAndExtensionsData")
  public void stringGetBaseAndExtensionTest(String filename,
          String extension, String base)
  {
    String thisBase = FileUtils.getBase(filename);
    Assert.assertEquals(thisBase, base,
            "base part of path and filename not as expected");
    String thisExtension = FileUtils.getExtension(filename);
    Assert.assertEquals(thisExtension, extension,
            "extension part of filename not as expected");
  }

  @DataProvider(name = "stringFilenamesBaseAndExtensionsData")
  public Object[][] stringFilenamesBaseAndExtensionsData()
  {
    return new Object[][] {
        /*
         * String full URL or path
         * String base the above but without the extension if there is one
         * String extension the filename extension if there is one
         */
        /*
        */
        { "/examples/uniref50.fa", "fa", "/examples/uniref50." },
        { "/examples/uniref50", null, "/examples/uniref50" },
        { "/examples/.uniref50", null, "/examples/.uniref50" },
        { "/exampl.es/uniref50", null, "/exampl.es/uniref50" },
        { "/examples/uniref50.", "", "/examples/uniref50." },
        { "examples/uniref50.fa", "fa", "examples/uniref50." },
        { "examples/uniref50", null, "examples/uniref50" },
        { "examples/.uniref50", null, "examples/.uniref50" },
        { "exampl.es/uniref50", null, "exampl.es/uniref50" },
        { "examples/uniref50.", "", "examples/uniref50." },
        { "https://www.jalview.org:443/examples/uniref50.fa", "fa",
            "https://www.jalview.org:443/examples/uniref50." },
        { "https://www.jalview.org:443/examples/uniref50", null,
            "https://www.jalview.org:443/examples/uniref50" },
        { "https://www.jalview.org:443/examples/.uniref50", null,
            "https://www.jalview.org:443/examples/.uniref50" },
        { "https://www.jalview.org:443/exampl.es/uniref50", null,
            "https://www.jalview.org:443/exampl.es/uniref50" },
        { "https://www.jalview.org:443/examples/uniref50.", "",
            "https://www.jalview.org:443/examples/uniref50." },
        /*
        */
        //
    };
  }

  public static final String root0 = "test/files/tempTestDir";

  @Test(
    groups = "Functional",
    dataProvider = "getMatchingVersionedFilesData")
  public void getMatchingVersionedFilesTest(String[] filesToMake,
          String[] templates, String[] roots, String[] versionWhitelist,
          String[] versionBlacklist, String separator, boolean exists,
          String[] expectedFilePaths)
  {
    Set<File> createdFiles = new HashSet<>();
    // create the files
    for (String n : filesToMake)
    {
      if (n != null)
      {
        File f = new File(root0 + File.separator + n);
        f.getParentFile().mkdirs();
        try
        {
          f.createNewFile();
          createdFiles.add(f);
        } catch (IOException e)
        {
          Assert.fail("Couldn't create file '" + f.getPath() + "'", e);
        }
      }
    }

    Set<File> matchedFiles = new HashSet<>();
    matchedFiles.addAll(FileUtils.getMatchingVersionedFiles(templates,
            roots, versionWhitelist, versionBlacklist, separator, exists));

    Set<String> matchedFilenames = new HashSet<>();
    StringBuilder mSB = new StringBuilder();
    for (File f : matchedFiles)
    {
      matchedFilenames.add(f.getPath());
      if (mSB.length() > 0)
      {
        mSB.append(", ");
      }
      mSB.append(f.getPath());
    }

    StringBuilder eSB = new StringBuilder();
    Set<String> expectedFilenames = new HashSet<>();
    for (int i = 0; i < expectedFilePaths.length; i++)
    {
      String path = root0 + File.separator + expectedFilePaths[i];
      expectedFilenames.add(path);
      if (eSB.length() > 0)
      {
        eSB.append(", ");
      }
      eSB.append(path);
    }

    Assert.assertEquals(matchedFilenames, expectedFilenames,
            "Did not find the right set of filenames."
                    + "\nMatched files : " + mSB.toString() + " ."
                    + "\nExpected files: " + eSB.toString() + " .\n");

    // delete created files
    for (File f : createdFiles)
    {
      f.delete();
    }
    rmEmptyDirs(root0);
  }

  private static final boolean rmEmptyDirs(String path)
  {
    if (path.startsWith("/") || path.contains(".."))
    {
      System.err.println("Not running rmDirs on '" + path
              + "'. Considered a bit dangerous.");
      return false;
    }
    File f = new File(path);
    if (!f.isDirectory())
    {
      return false;
    }
    for (File c : f.listFiles())
    {
      if (!rmEmptyDirs(c.getPath()))
      {
        return false;
      }
    }
    f.delete();
    return true;
  }

  @DataProvider(name = "getMatchingVersionedFilesData")
  public Object[][] getMatchingVersionedFilesData()
  {
    String file1 = "dir/Applications/File-1/bin/file.exe";
    String file2 = "dir/Applications/File-2/bin/file.exe";
    String file3 = "user/Applications/File-3/bin/file.exe";
    String file4a = "dir/Applications/File-4/bin/file.exe";
    String file4b = "user/Applications/File-4/bin/file.exe";
    String filea = "dir/Applications/File/bin/file.exe";
    String fileb = "user/Applications/File/bin/file.exe";
    return new Object[][] {
        /*
         * String[] filesToMake
         * String[] templates
         * String[] roots
         * String[] versionWhitelist
         * String[] versionBlacklist
         * String separator
         * boolean exists
         * String[] expectedFilePaths
         */
        /*
        */
        // empty whitelist and blacklist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            new String[] {}, new String[] {}, "-", true, new String[]
            { file1, file2, file3 } },
        // no whitelist or blacklist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            null, null, "-", true, new String[] {} },
        // no blacklist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            new String[]
            { "1", "3" }, null, "-", true, new String[] { file1, file3 } },
        // no whitelist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            null, new String[]
            { "1", "3" }, "-", true, new String[] { file2 } },
        // whitelist (but only if exists) and blacklist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            new String[]
            { "4" }, new String[] { "1", "3" }, "-", true,
            new String[]
            { file2 } },
        // whitelist (if exists or not) and blacklist
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            new String[]
            { "4" }, new String[] { "1", "3" }, "-", false,
            new String[]
            { file2, file4a, file4b, filea, fileb } },
        // whitelist (if exists or not) and blacklist but no separator so no
        // unversioned paths
        { new String[]
        { file1, file2, file3 }, new String[] { "%s/File-%s/bin/file.exe" },
            new String[]
            { root0 + "/dir/Applications", root0 + "/user/Applications" },
            new String[]
            { "4" }, new String[] { "1", "3" }, null, false,
            new String[]
            { file2, file4a, file4b } },
        /*
        */
        //
    };
  }
}
