// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.eclipse.model;

import com.intellij.openapi.components.ExpandMacroToPathMap;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.eclipse.*;
import org.jetbrains.jps.model.JpsElementFactory;
import org.jetbrains.jps.model.java.*;
import org.jetbrains.jps.model.library.JpsLibrary;
import org.jetbrains.jps.model.library.JpsLibraryReference;
import org.jetbrains.jps.model.library.JpsOrderRootType;
import org.jetbrains.jps.model.module.*;
import org.jetbrains.jps.model.serialization.JpsMacroExpander;
import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;

final class JpsEclipseClasspathReader extends AbstractEclipseClasspathReader<JpsModule> {
  private static final Logger LOG = Logger.getInstance(JpsEclipseClasspathReader.class);
  private final Map<String, String> myLibLevels;

  JpsEclipseClasspathReader(String rootPath,
                                   @Nullable List<String> currentRoots,
                                   @Nullable Set<String> moduleNames,
                                   Map<String, String> levels) {
    super(rootPath, currentRoots, moduleNames);
    myLibLevels = levels;
  }

  @Override
  protected String prepareValidUrlInsideJar(String url) {
    //strip path inside jar
    final String jarSeparator = "!/";
    final int localPathEndIdx = url.indexOf(jarSeparator);
    if (localPathEndIdx > -1) {
      return url.substring(0, localPathEndIdx + jarSeparator.length());
    }
    return url;
  }

  @Override
  protected void addNamedLibrary(JpsModule rootModel,
                                 Collection<? super String> unknownLibraries,
                                 boolean exported,
                                 String name,
                                 boolean applicationLevel) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("loading " + rootModel.getName() + ": adding " + (applicationLevel ? "application" : "project") + " library '" + name + "'");
    }
    JpsElementFactory factory = JpsElementFactory.getInstance();
    JpsLibraryReference libraryReference;
    final String level = myLibLevels.get(name);
    libraryReference = level != null ? factory.createLibraryReference(name, JpsLibraryTableSerializer.createLibraryTableReference(level))
                                     : factory.createLibraryReference(name, factory.createGlobalReference());
    final JpsLibraryDependency dependency = rootModel.getDependenciesList().addLibraryDependency(libraryReference);
    setLibraryEntryExported(dependency, exported);
  }

  @Override
  protected void addInvalidModuleEntry(JpsModule rootModel, boolean exported, String moduleName) {
    final JpsElementFactory elementFactory = JpsElementFactory.getInstance();
    final JpsDependenciesList dependenciesList = rootModel.getDependenciesList();
    final JpsModuleDependency dependency = dependenciesList.addModuleDependency(elementFactory.createModuleReference(moduleName));
    final JpsJavaDependencyExtension extension = getService().getOrCreateDependencyExtension(dependency);
    extension.setExported(exported);
  }

  @Override
  protected void setUpModuleJdk(JpsModule rootModel,
                                Collection<? super String> unknownJdks,
                                EclipseModuleManager eclipseModuleManager,
                                String jdkName) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("loading " + rootModel.getName() + ": set module jdk " + jdkName);
    }
    rootModel.getDependenciesList().addSdkDependency(JpsJavaSdkType.INSTANCE);
  }

  @Override
  protected void addSourceFolder(JpsModule rootModel, String srcUrl, boolean testFolder) {
    rootModel.addSourceRoot(srcUrl, testFolder ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE);
  }

  @Override
  protected void addSourceFolderToCurrentContentRoot(JpsModule rootModel, String srcUrl, boolean testFolder) {
    rootModel.addSourceRoot(srcUrl, testFolder ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE);
  }

  @Override
  protected void addJUnitDefaultLib(JpsModule rootModel, String junitName, ExpandMacroToPathMap macroMap) {
    final @NonNls String ideaHome = macroMap.substitute("$APPLICATION_HOME_DIR$", SystemInfo.isFileSystemCaseSensitive);
    final FilenameFilter junitFilter = (dir, name) -> name.startsWith("junit");
    File[] junitJars = new File(ideaHome, "lib").listFiles(junitFilter);
    if (junitJars == null || junitJars.length == 0) {
      junitJars = new File(new File(ideaHome, "community"), "lib").listFiles(junitFilter);
    }
    if (junitJars != null && junitJars.length > 0) {
      final boolean isJUnit4 = junitName.contains("4");
      File junitJar = null;
      for (File jar : junitJars) {
        final boolean isCurrentJarV4 = jar.getName().contains("4");
        if (isCurrentJarV4 && isJUnit4 || !isCurrentJarV4 && !isJUnit4) {
          junitJar = jar;
          break;
        }
      }
      if (junitJar != null) {
        final JpsLibrary jpsLibrary = rootModel.addModuleLibrary(junitName, JpsJavaLibraryType.INSTANCE);
        jpsLibrary.addRoot(pathToUrl(junitJar.getPath()), JpsOrderRootType.COMPILED);
        final JpsDependenciesList dependenciesList = rootModel.getDependenciesList();
        dependenciesList.addLibraryDependency(jpsLibrary);
      }
    }
  }

  @Override
  protected void addModuleLibrary(JpsModule rootModel,
                                  Element element,
                                  boolean exported,
                                  String libName,
                                  String url,
                                  String srcUrl,
                                  String nativeRoot,
                                  ExpandMacroToPathMap macroMap) {
    final JpsLibrary jpsLibrary = rootModel.addModuleLibrary(libName, JpsJavaLibraryType.INSTANCE);
    final JpsDependenciesList dependenciesList = rootModel.getDependenciesList();
    final JpsLibraryDependency dependency = dependenciesList.addLibraryDependency(jpsLibrary);
    url = StringUtil.trimStart(url, "file://");
    final String linked = expandLinkedResourcesPath(url, macroMap);
    if (linked != null) {
      url = pathToUrl(linked);
    }
    else {
      url = expandEclipsePath2Url(rootModel, url);
    }
    LOG.debug("loading " + rootModel.getName() + ": adding module library " + libName + ": " + url);
    jpsLibrary.addRoot(url, JpsOrderRootType.COMPILED);

    setLibraryEntryExported(dependency, exported);
  }

  @Override
  protected String expandEclipsePath2Url(JpsModule rootModel, String path) {

    String url = null;
    if (new File(path).exists()) {  //absolute path
      url = pathToUrl(path);
    }
    else {
      final String relativePath = new File(myRootPath, path).getPath(); //inside current project
      final File file = new File(relativePath);
      if (file.exists()) {
        url = pathToUrl(relativePath);
      }
      else if (path.startsWith("/")) { //relative to other project
        final String moduleName = EPathCommonUtil.getRelativeModuleName(path);
        final String relativeToRootPath = EPathCommonUtil.getRelativeToModulePath(path);
        url = EPathCommonUtil.expandEclipseRelative2ContentRoots(myCurrentRoots, moduleName, relativeToRootPath);
      }
    }
    if (url == null) {
      url = pathToUrl(path);
    }

    return prepareValidUrlInsideJar(url);
  }

  @Override
  protected Set<String> getDefinedCons() {
    return Collections.emptySet();
  }


  @Override
  protected int rearrange(JpsModule rootModel) {
    return 0;
  }

  public void readClasspath(JpsModule model,
                            final String testPattern,
                            Element classpathElement, JpsMacroExpander expander) throws IOException {
    LOG.debug("start loading classpath for " + model.getName());
    final HashSet<String> libs = new HashSet<>();
    for (Element o : classpathElement.getChildren(EclipseXml.CLASSPATHENTRY_TAG)) {
      try {
        readClasspathEntry(model, new ArrayList<>(), new ArrayList<>(), new HashSet<>(),
                           testPattern, o, 0, null, expander.getExpandMacroMap(), libs);
      }
      catch (ConversionException e) {
        throw new IOException(e);
      }
    }
    boolean foundSdkDependency = false;
    JpsDependenciesList dependenciesList = model.getDependenciesList();
    for (JpsDependencyElement element : dependenciesList.getDependencies()) {
      if (element instanceof JpsSdkDependency) {
        foundSdkDependency = true;
        break;
      }
    }
    if (!foundSdkDependency) {
      dependenciesList.addSdkDependency(JpsJavaSdkType.INSTANCE);
    }
    if (LOG.isDebugEnabled()) {
      String name = model.getName();
      LOG.debug("finished loading classpath for " + name + " (" + dependenciesList.getDependencies().size() + " items):");
      for (JpsDependencyElement element : dependenciesList.getDependencies()) {
        LOG.debug(" [" + name + "]:" + element.toString());
      }
    }
  }

  private @Nullable String expandLinkedResourcesPath(final String path, ExpandMacroToPathMap expander) {
    final EclipseProjectFinder.LinkedResource linkedResource = EclipseProjectFinder.findLinkedResource(myRootPath, path);
    if (linkedResource != null) {
      if (linkedResource.containsPathVariable()) {
        final String toPathVariableFormat =
          getVariableRelatedPath(linkedResource.getVariableName(), linkedResource.getRelativeToVariablePath());
        return expander.substitute(toPathVariableFormat, SystemInfo.isFileSystemCaseSensitive);
      }
      return linkedResource.getLocation();
    }
    return null;
  }

  @Override
  public void setupOutput(JpsModule rootModel, final String path) {
    final JpsJavaModuleExtension extension = getService().getOrCreateModuleExtension(rootModel);
    String outputUrl = pathToUrl(path);
    extension.setOutputUrl(outputUrl);
    extension.setTestOutputUrl(outputUrl);
    //extension.setInheritOutput(false);

    rootModel.getDependenciesList().addModuleSourceDependency();
  }

  private static void setLibraryEntryExported(final JpsDependencyElement dependency, boolean exported) {
    final JpsJavaDependencyExtension extension = getService().getOrCreateDependencyExtension(dependency);
    extension.setExported(exported);
  }

  private static JpsJavaExtensionService getService() {
    return JpsJavaExtensionService.getInstance();
  }
}
