/*
 * Copyright 2003-2011 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.util;

import jetbrains.mps.vfs.path.Path;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.annotations.Internal;
import org.jetbrains.mps.annotations.Singleton;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * Responsible for different predefined paths in the distribution layout
 */
@Singleton
public final class PathManager {
  private static final Logger LOG = LogManager.getLogger(PathManager.class);

  private static final String FILE = "file";
  public static final String JAR = "jar";
  private static final String JAR_DELIMITER = "!";
  public static final String DOT_JAR = ".jar";

  private static final String PROTOCOL_DELIMITER = ":";
  private static final String PLUGINS_PATH = "plugins";
  private static final String PROPERTIES_FILE_NAME = "idea.properties";

  private static String ourHomePath;
  private static String ourIdeaPath;

  public static final FilenameFilter JAR_FILE_FILTER = (dir, name) -> name.endsWith(DOT_JAR);

  private PathManager() {
  }

  public static String getHomePath() {
    if (ourHomePath != null) {
      return ourHomePath;
    }

    String rootPath = getContainingJar(PathManager.class);

    File root = new File(rootPath);
    root = root.getAbsoluteFile();

    if (rootPath.endsWith(DOT_JAR)) {
      // {mps_home}/lib
      root = root.getParentFile();
      if (root != null) {
        // {mps_home}
        root = root.getParentFile();
      }
    } else {
      while ((!isMpsDir(root)) && (root.getParentFile() != null)) {
        root = root.getParentFile();
      }
    }

    ourHomePath = root.getAbsolutePath();
    if (ourHomePath.equals("/")) {
      throw new IllegalStateException("cannot detect MPS location");
    }
    return ourHomePath;
  }

  /**
   * Defines whether we are starting from sources not from distribution
   */
  @Internal
  public static boolean isFromSources() {
    return !getContainingJar(PathManager.class).endsWith(Path.DOT_JAR);
  }

  private static String getContainingJar(Class aClass) {
    return getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
  }

  public static String getIdeaPath() {
    if (ourIdeaPath != null) {
      return ourIdeaPath;
    }

    // {idea_home}/lib/jdom.jar
    String rootPath = getContainingJar(Document.class);
    if (rootPath == null) {
      ourIdeaPath = getHomePath();
      return ourIdeaPath;
    }
    File root = new File(rootPath);
    root = root.getAbsoluteFile();

    // {idea_home}/lib
    root = root.getParentFile();
    if (root != null) {
      // {idea_home}
      root = root.getParentFile();
    }

    if (root == null) {
      ourIdeaPath = getHomePath();
    } else {
      ourIdeaPath = root.getAbsolutePath();
    }

    return ourIdeaPath;
  }

  public static String[] getHomePaths() {
    if (getHomePath().equals(getIdeaPath())) {
      return new String[]{getHomePath()};
    }
    return new String[]{getHomePath(), getIdeaPath()};
  }

  public static Collection<String> getBootstrapPaths() {
    Collection<String> paths = getBootstrapPathsFromLibFolder();
    if (new File(getCorePath()).exists()) {
      paths.add(getCorePath());
    }
    if (new File(getEditorPath()).exists()) {
      paths.add(getEditorPath());
    }
    return Collections.unmodifiableCollection(paths);
  }

  @NotNull
  private static Collection<String> getBootstrapPathsFromLibFolder() {
    List<String> paths = new ArrayList<String>();
    File libDir = new File(getLibPath());
    if (libDir.exists() && libDir.isDirectory()) {
      for (File jar : libDir.listFiles(JAR_FILE_FILTER)) {
        paths.add(jar.getAbsolutePath());
      }
    }
    return paths;
  }

  private static String getLibPath() {
    return getHomePath() + File.separator + "lib";
  }

  public static String getLanguagesPath() {
    return getHomePath() + File.separator + "languages";
  }

  public static String getWorkbenchPath() {
    return getHomePath() + File.separator + "workbench";
  }

  public static String getCorePath() {
    return getHomePath() + File.separator + "core";
  }

  public static String getEditorPath() {
    return getHomePath() + File.separator + "editor";
  }

  private static boolean isMpsDir(File file) {
    return new File(file, "bin" + File.separator + PROPERTIES_FILE_NAME).exists();
  }

  /**
   * Attempts to detect classpath entry which contains given resource
   */
  public static String getResourceRoot(Class context, String path) {
    URL url = context.getResource(path);
    if (url == null) {
      url = ClassLoader.getSystemResource(path.substring(1));
    }
    if (url == null) {
      return null;
    }
    return extractRoot(url, path);
  }

  /**
   * Attempts to extract classpath entry part from passed URL.
   */
  private static String extractRoot(URL resourceURL, String resourcePath) {
    if (!(resourcePath.startsWith("/") || resourcePath.startsWith("\\"))) {
      LOG.error("precondition failed for" + resourcePath);
      return null;
    }
    String protocol = resourceURL.getProtocol();
    String resultPath = null;

    if (FILE.equals(protocol)) {
      String path = resourceURL.getFile();
      String testPath = path.replace('\\', '/').toLowerCase();
      String testResourcePath = resourcePath.replace('\\', '/').toLowerCase();
      if (testPath.endsWith(testResourcePath)) {
        resultPath = path.substring(0, path.length() - resourcePath.length());
      }
    } else if (JAR.equals(protocol)) {
      String fullPath = resourceURL.getFile();
      int delimiter = fullPath.indexOf(JAR_DELIMITER);
      if (delimiter >= 0) {
        String archivePath = fullPath.substring(0, delimiter);
        if (archivePath.startsWith(FILE + PROTOCOL_DELIMITER)) {
          resultPath = archivePath.substring(FILE.length() + PROTOCOL_DELIMITER.length());
        }
      }
    }

    if (resultPath != null && resultPath.endsWith(File.separator)) {
      resultPath = resultPath.substring(0, resultPath.length() - 1);
    }

    resultPath = resultPath != null ? StringUtil.replace(resultPath, "%20", " ") : null;
    return resultPath;
  }

  public static String getPreInstalledPluginsPath() {
    return getHomePath() + File.separator + PLUGINS_PATH;
  }

  public static String getUserDir() {
    return System.getProperty("user.dir");
  }
}
