package nxm.sys.lib;

import static nxm.sys.lib.IOResource.UNKNOWN;

import java.net.URL;
import java.util.HashMap;

import nxm.sys.inc.IOResourceFactory;
import nxm.sys.inc.InternalUseOnly;

/** A new resource that will used the current Midas Context's ClassLoader to access the files
 *  in the classpath. <br>
 *  <br>
 *  NOTE: This resource type will generally provide read-only access consistent with using the
 *  Java ClassLoader to access the files. In some cases the file will be read/write due to the
 *  ClassLoader finding the file on a location within the classpath that is writable. However,
 *  this will NOT find any output-only files.
 *  @author Juan Velasquez
 *  @author J. Czechowski IV
 *  @since  NeXtMidas 3.7.2
 */
public class ClassPathResource implements IOResourceFactory {
  static final ClassPathResource INSTANCE = new ClassPathResource();
  private static final String classpathStr = System.getProperty("java.class.path");
  private static boolean moduleInfoFix = false;

  static {
    // Some jar dependencies contain module-info.class, which cause getResource(pathName)
    // to sometimes return an invalid result when using jdk9+.
    // Check the classpath against a set of jar file dependencies, and
    // if using jdk9+ and at least the minimum specified version is on the classpath, set moduleInfoFix to true
    // NXM-4036 Since NeXtMidas 4.1.4
    if (Shell.getJavaMajorVersion() > 8) {
      HashMap<String,String> map = new HashMap<>(); // jar file dependencies and their corresponding minimum version in which module-info.class exists
      // SYS
      map.put("bcel", "6.8.0");
      map.put("commons-lang3", "3.14.0");
      map.put("commons-net", "3.10.0");
      // MINIO
      map.put("commons-compress", "1.23.0");
      map.put("jackson-core", "2.13.0");
      map.put("jackson-databind", "2.13.0");
      map.put("kotlin-stdlib", "1.4.0");
      for (String depJarName : map.keySet()) {
        if (classpathStr.contains(depJarName+"-")) {
          int depJarNameIdx = classpathStr.indexOf(depJarName);
          int depJarVerIdx = classpathStr.indexOf(":", depJarNameIdx);
          String depJarFullName = (depJarVerIdx != -1) ? classpathStr.substring(depJarNameIdx,depJarVerIdx) : classpathStr.substring(depJarNameIdx);
          String depJarActVer = depJarFullName.substring(0,depJarFullName.indexOf(".jar")).split(depJarName+"-")[1];
          String depJarMinVer = map.get(depJarName);
          String [] depJarActVerArr = depJarActVer.split("\\.");
          String [] depJarMinVerArr = depJarMinVer.split("\\.");
          int depJarActVerMajorVer = Integer.parseInt(depJarActVerArr[0]);
          int depJarMinVerMajorVer = Integer.parseInt(depJarMinVerArr[0]);
          int depJarActVerMinorVer = Integer.parseInt(depJarActVerArr[1]);
          int depJarMinVerMinorVer = Integer.parseInt(depJarMinVerArr[1]);
          if (depJarActVerMajorVer > depJarMinVerMajorVer || (depJarActVerMajorVer == depJarMinVerMajorVer && depJarActVerMinorVer >= depJarMinVerMinorVer)) {
            moduleInfoFix = true;
            break;
          }
        }
      }
    }
  }

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

  @Override
  public IOResource getResourceFor (Midas M, int type, String urlstr, int dir, Table quals) {
    if (!urlstr.startsWith("classpath:")) {
      throw new MidasException("Invalid URL for ClassPathResource: "+urlstr);
    }
    int    i        = urlstr.indexOf('!');             // see if it is in the form "classpath:foo.tar!/myFile.txt"
    String filePath = (i < 0)? urlstr.substring(10)    // strip off the "classpath:"
                             : urlstr.substring(10,i); // strip off the "classpath:" and "!..."
    String suffix   = (i < 0)? "" : urlstr.substring(i);

    if (moduleInfoFix && filePath.equals("/")) return null;

    URL    fileURL  = getResource(filePath);

    if (fileURL != null) {
      // Use the Midas CoreIO to find the resource needed for the URL found on the classpath
      return IOResource.getInstance(M, UNKNOWN, fileURL+suffix, dir, quals);
    }
    return null;
  }

  /** Load a given resource. This method will try to load the resource using the following methods
   *  (in order):
   *  <pre>
   *    Thread.currentThread().getContextClassLoader()
   *    Shell.class.getClassLoader()
   *    ClassLoader.getSystemClassLoader().getClassLoader()
   *  </pre>
   *  @param pathName The name of the resource to load
   */
  @InternalUseOnly
  private URL getResource (String pathName) {
    URL url;

    // Normalize the path (replace '\' with '/' and replace '//' with '/')
    pathName = pathName.replace('\\', '/').replaceAll("/+", "/");
    if (pathName.startsWith("/")) {
      pathName = pathName.substring(1); // take off leading '/'
    }

    // Get the URL from the class loader
    url = Thread.currentThread().getContextClassLoader().getResource(pathName);
    if (url != null) return url;

    url = Shell.class.getClassLoader().getResource(pathName);
    if (url != null) return url;

    url = ClassLoader.getSystemClassLoader().getResource(pathName);
    if (url != null) return url;

    return null;
  }
}