/**
 * Copyright (C) 2005 - 2017  Eric Van Dewoestine
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.eclim.plugin.jdt.util;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclim.logging.Logger;

import org.eclim.plugin.core.util.ProjectUtils;

import org.eclipse.core.resources.IProject;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;

/**
 * Utility methods for working with the java project's classpath.
 *
 * @author Eric Van Dewoestine
 */
public class ClasspathUtils
{
  private static final Logger logger = Logger.getLogger(ClasspathUtils.class);

  /**
   * Gets an array of paths representing the project's classpath.
   *
   * @param project The java project instance.
   * @return Array of paths.
   */
  public static String[] getClasspath(IJavaProject project)
  {
    HashSet<IJavaProject> visited = new HashSet<IJavaProject>();
    ArrayList<String> paths = new ArrayList<String>();
    collect(project, paths, visited, true);
    return paths.toArray(new String[paths.size()]);
  }

  /**
   * Gets an array of paths representing the project's source paths.
   *
   * @param project The java project instance.
   * @return Array of paths.
   */
  public static String[] getSrcPaths(IJavaProject project)
  {
    ArrayList<String> paths = new ArrayList<String>();
    IClasspathEntry[] entries = null;
    try{
      entries = project.getResolvedClasspath(true);
    }catch(CoreException ce){
      throw new RuntimeException(ce);
    }
    for (IClasspathEntry entry : entries){
      if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE){
        paths.add(ProjectUtils.getFilePath(
              project.getProject(), entry.getPath().toOSString()));
      }
    }

    return paths.toArray(new String[paths.size()]);
  }

  /**
   * Recursively collects classpath entries from the current and dependent
   * projects.
   */
  private static void collect(
      IJavaProject javaProject,
      List<String> paths,
      Set<IJavaProject> visited,
      boolean isFirstProject)
  {
    if(visited.contains(javaProject)){
      return;
    }
    visited.add(javaProject);

    try{
      IPath out = javaProject.getOutputLocation();
      paths.add(
          ProjectUtils.getFilePath(
            javaProject.getProject(),
            out.addTrailingSeparator().toOSString()));
    }catch(JavaModelException ignore){
      // ignore... just signals that no output dir was configured.
    }

    IProject project = javaProject.getProject();
    String name = project.getName();

    IClasspathEntry[] entries = null;
    try {
      entries = javaProject.getResolvedClasspath(true);
    }catch(JavaModelException jme){
      // this may or may not be a problem.
      logger.warn(
          "Unable to retrieve resolved classpath for project: " + name, jme);
      return;
    }

    final List<IJavaProject> nextProjects = new ArrayList<IJavaProject>();
    for(IClasspathEntry entry : entries) {
      switch (entry.getEntryKind()) {
        case IClasspathEntry.CPE_LIBRARY :
        case IClasspathEntry.CPE_CONTAINER :
        case IClasspathEntry.CPE_VARIABLE :
          String path = entry.getPath().toOSString().replace('\\', '/');
          if(path.startsWith("/" + name + "/")){
            path = ProjectUtils.getFilePath(project, path);
          }
          paths.add(path);
          break;
        case IClasspathEntry.CPE_PROJECT :
          if (isFirstProject || entry.isExported()){
            javaProject =
              JavaUtils.getJavaProject(entry.getPath().segment(0));
            if (javaProject != null){
              // breadth first, not depth first, to preserve dependency ordering
              nextProjects.add(javaProject);
            }
          }
          break;
        case IClasspathEntry.CPE_SOURCE :
          IPath out = entry.getOutputLocation();
          if (out != null){
            paths.add(
                ProjectUtils.getFilePath(
                  javaProject.getProject(),
                  out.addTrailingSeparator().toOSString()));
          }
          break;
      }
    }
    // depth second
    for(final IJavaProject nextProject : nextProjects) {
      collect(nextProject, paths, visited, false);
    }
  }
}
