/*******************************************************************************
 * Copyright (c) 2017, 2018 Pivotal, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Pivotal, Inc. - initial API and implementation
 *******************************************************************************/
package org.springframework.ide.vscode.commons.gradle;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.gradle.tooling.model.build.BuildEnvironment;
import org.gradle.tooling.model.eclipse.EclipseExternalDependency;
import org.gradle.tooling.model.eclipse.EclipseProject;
import org.gradle.tooling.model.eclipse.EclipseProjectDependency;
import org.gradle.tooling.model.eclipse.EclipseSourceDirectory;
import org.springframework.ide.vscode.commons.jandex.JandexClasspath;
import org.springframework.ide.vscode.commons.jandex.JandexIndex;
import org.springframework.ide.vscode.commons.java.ClasspathData;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider;
import org.springframework.ide.vscode.commons.javadoc.TypeUrlProviderFromContainerUrl;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.Log;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;

/**
 * Implementation of {@link IClasspath} for Gradle projects
 *
 * @author Alex Boyko
 *
 */
public class GradleProjectClasspath implements IClasspath {

	private static final String JAVA_HOME = "java.home";
	private static final String JAVA_RUNTIME_VERSION = "java.runtime.version";
	private static final String JAVA_BOOT_CLASS_PATH = "sun.boot.class.path";

	private EclipseProject project;
	private BuildEnvironment buildEnvironment;

	public GradleProjectClasspath(GradleCore gradle, File projectDir) throws GradleException {
		super();
		this.project = gradle.getModel(projectDir, EclipseProject.class);
		this.buildEnvironment = gradle.getModel(projectDir, BuildEnvironment.class);
	}

//	@Override
//	protected JandexIndex[] getBaseIndices() {
//		return new JandexIndex[] { new JandexIndex(getJreLibs().map(path -> path.toFile()).collect(Collectors.toList()),
//				jarFile -> findIndexFile(jarFile), (classpathResource) -> {
//					try {
//						String javaVersion = getJavaRuntimeMinorVersion();
//						if (javaVersion == null) {
//							javaVersion = "8";
//						}
//						URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/");
//						return new HtmlJavadocProvider(
//								(type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
//										.sourceUrl(javadocUrl, type.getFullyQualifiedName()));
//					} catch (MalformedURLException e) {
//						Log.log(e);
//						return null;
//					}
//				}) };
//	}

	private EclipseProject getRootProject() {
		EclipseProject root = project;
		if (root == null) {
			return root;
		}
		while(root.getParent() != null) {
			root = root.getParent();
		}
		return root;
	}

	@Override
	public ImmutableList<CPE> getClasspathEntries() throws Exception {
		EclipseProject root = getRootProject();
		if (project == null) {
			return ImmutableList.of();
		} else {
			Builder<CPE> entries = ImmutableList.builder();
			for (EclipseExternalDependency dep : project.getClasspath()) {
				entries.add(new CPE(Classpath.ENTRY_KIND_BINARY, dep.getFile().getAbsolutePath()));
			}
			for (EclipseProjectDependency dep : project.getProjectDependencies()) {
				EclipseProject peer = findPeer(root, dep.getTargetProject().getName());
				if (peer!=null) {
					entries.add(new CPE(Classpath.ENTRY_KIND_BINARY,
						peer.getProjectDirectory().toPath().resolve(peer.getOutputLocation().getPath()).toString()
					));
				}
			}
			for (EclipseSourceDirectory sf : project.getSourceDirectories()) {
				File sourceFolder = sf.getDirectory();
				String of = sf.getOutput();
				entries.add(CPE.source(sourceFolder.getAbsoluteFile(), new File(project.getProjectDirectory(), of)));
			}
			return entries.build();
		}
	}

	private EclipseProject findPeer(EclipseProject root, String name) {
		return root.getChildren().stream().filter(p -> p.getName().equals(name)).findFirst().orElse(null);
	}

	@Override
	public String getName() {
		return project == null ? null : project.getName();
	}

	public String getGradleVersion()  throws GradleException {
		if (buildEnvironment == null) {
			throw new GradleException(new Exception("Cannot find Gradle version"));
		} else {
			return buildEnvironment.getGradle().getGradleVersion();
		}
	}

	public File getGradleHome() throws GradleException {
		if (buildEnvironment == null) {
			throw new GradleException(new Exception("Cannot find Gradle home folder"));
		} else {
			return buildEnvironment.getGradle().getGradleUserHome();
		}
	}

	public String getJavaRuntimeVersion() {
		return System.getProperty(JAVA_RUNTIME_VERSION);
	}

	public String getJavaRuntimeMinorVersion() {
		String fullVersion = getJavaRuntimeVersion();
		String[] tokenized = fullVersion.split("\\.");
		if (tokenized.length > 1) {
			return tokenized[1];
		} else {
			Log.log("Cannot determine minor version for the Java Runtime Version: " + fullVersion);
			return null;
		}
	}

	private String getJavaHome() {
		if (buildEnvironment == null) {
			return System.getProperty(JAVA_HOME);
		} else {
			return buildEnvironment.getJava().getJavaHome().toString();
		}
	}

	private Stream<Path> getJreLibs() {
		String s = System.getProperty(JAVA_BOOT_CLASS_PATH);
		return Arrays.stream(s.split(File.pathSeparator))
				.map(strPath -> strPath.replace(System.getProperty(JAVA_HOME), getJavaHome()))
				.map(File::new)
				.filter(f -> f.canRead())
				.map(f -> f.toPath());
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof GradleProjectClasspath) {
			return super.equals(obj);
		}
		return false;
	}

}
