package com.intellij.openapi.externalSystem.model.project;

import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.Serializable;
import java.util.*;

/**
 * @author Denis Zhdanov
 * @since 8/9/11 6:25 PM
 */
public class ContentRootData extends AbstractExternalEntityData {

  private static final long serialVersionUID = 1L;

  @NotNull private final Map<ExternalSystemSourceType, Collection<SourceRoot>> myData = ContainerUtilRt.newHashMap();

  @NotNull private final String myRootPath;

  /**
   * Creates new {@code GradleContentRootImpl} object.
   *
   * @param rootPath  path to the root directory
   */
  public ContentRootData(@NotNull ProjectSystemId owner, @NotNull String rootPath) {
    super(owner);
    myRootPath = ExternalSystemApiUtil.toCanonicalPath(rootPath);
  }

  /**
   * @param type      target dir type
   * @return          directories of the target type configured for the current content root
   */
  @NotNull
  public Collection<SourceRoot> getPaths(@NotNull ExternalSystemSourceType type) {
    final Collection<SourceRoot> result = myData.get(type);
    return result == null ? Collections.emptyList() : result;
  }

  public void storePath(@NotNull ExternalSystemSourceType type, @NotNull String path) throws IllegalArgumentException {
    storePath(type, path, null);
  }

  /**
   * Ask to remember that directory at the given path contains sources of the given type.
   *
   * @param type           target sources type
   * @param path           target source directory path
   * @param packagePrefix  target source directory package prefix
   * @throws IllegalArgumentException   if given path points to the directory that is not located
   *                                    under the {@link #getRootPath() content root}
   */
  public void storePath(@NotNull ExternalSystemSourceType type, @NotNull String path, @Nullable String packagePrefix) throws IllegalArgumentException {
    if (FileUtil.isAncestor(new File(getRootPath()), new File(path), false)) {
      Collection<SourceRoot> paths = myData.get(type);
      if (paths == null) {
        myData.put(type, paths = new TreeSet<>(SourceRootComparator.INSTANCE));
      }
      paths.add(new SourceRoot(
        ExternalSystemApiUtil.toCanonicalPath(path),
        StringUtil.nullize(packagePrefix, true)
      ));
      return;
    }
    if (!ExternalSystemSourceType.EXCLUDED.equals(type)) { // There are external systems which mark output directory as 'excluded' path.
      // We don't need to bother if it's outside a module content root then.
      throw new IllegalArgumentException(String.format(
        "Can't register given path of type '%s' because it's out of content root.%nContent root: '%s'%nGiven path: '%s'",
        type, getRootPath(), new File(path).getAbsolutePath()
      ));
    }
  }

  @NotNull
  public String getRootPath() {
    return myRootPath;
  }

  @Override
  public String toString() {
    StringBuilder buffer = new StringBuilder("content root:");
    for (Map.Entry<ExternalSystemSourceType, Collection<SourceRoot>> entry : myData.entrySet()) {
      buffer.append(entry.getKey().toString().toLowerCase(Locale.ENGLISH)).append("=").append(entry.getValue()).append("|");
    }
    if (!myData.isEmpty()) {
      buffer.setLength(buffer.length() - 1);
    }
    return buffer.toString();
  }

  public static class SourceRoot implements Serializable {
    @NotNull
    private final String myPath;

    @Nullable
    private final String myPackagePrefix;

    public SourceRoot(@NotNull String path, @Nullable String prefix) {
      myPath = path;
      myPackagePrefix = prefix;
    }

    @NotNull
    public String getPath() {
      return myPath;
    }

    @Nullable
    public String getPackagePrefix() {
      return myPackagePrefix;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof SourceRoot)) return false;
      SourceRoot root = (SourceRoot)o;
      if (myPackagePrefix != null ? !myPackagePrefix.equals(root.myPackagePrefix) : root.myPackagePrefix != null) return false;
      if (!myPath.equals(root.myPath)) return false;
      return true;
    }

    @Override
    public int hashCode() {
      int result = myPath.hashCode();
      result = 31 * result + (myPackagePrefix != null ? myPackagePrefix.hashCode() : 0);
      return result;
    }

    @Override
    public String toString() {
      StringBuilder buffer = new StringBuilder("source_root(");
      buffer.append(myPath);
      if (myPackagePrefix != null) {
        buffer.append(", ").append(myPackagePrefix);
      }
      buffer.append(")");
      return buffer.toString();
    }
  }

  private static final class SourceRootComparator implements Comparator<SourceRoot>, Serializable {
    private static final SourceRootComparator INSTANCE = new SourceRootComparator();

    @Override
    public int compare(@NotNull SourceRoot o1, @NotNull SourceRoot o2) {
      return StringUtil.naturalCompare(o1.myPath, o2.myPath);
    }
  }
}
