package renaming.history;

import static com.google.common.base.Preconditions.checkArgument;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.revwalk.RevCommit;

import renaming.history.RepentDataParser.Renaming;
import codemining.java.codeutils.binding.JavaApproximateVariableBindingExtractor;
import codemining.java.codeutils.binding.JavaMethodDeclarationBindingExtractor;
import codemining.java.codeutils.binding.tui.JavaBindingsToJson.SerializableResolvedSourceCode;
import codemining.languagetools.bindings.ResolvedSourceCode;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import committools.data.RepositoryFileWalker;

public class RenamingDatasetExtractor {

	public static class BindingExtractor extends RepositoryFileWalker {

		List<RenamedSerializableResolvedSourceCode> renamedVariablesDatapoints = Lists
				.newArrayList();

		List<RenamedSerializableResolvedSourceCode> renamedMethodDeclarationsDatapoints = Lists
				.newArrayList();

		final Multimap<String, Renaming> renamings;

		private final JavaApproximateVariableBindingExtractor variableExtractor = new JavaApproximateVariableBindingExtractor();
		private final JavaMethodDeclarationBindingExtractor methodDeclExtractor = new JavaMethodDeclarationBindingExtractor();

		public BindingExtractor(final String repositoryDirectory,
				final Multimap<String, Renaming> renamings) throws IOException {
			super(repositoryDirectory, RepositoryFileWalker.BASE_WALK);
			this.renamings = renamings;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see
		 * committools.data.RepositoryFileWalker#isVisitableCommit(org.eclipse
		 * .jgit.revwalk.RevCommit)
		 */
		@Override
		public boolean isVisitableCommit(final RevCommit commit) {
			return renamings.containsKey(commit.name());
		}

		private boolean matchRenaming(final Renaming renaming, final ASTNode n) {
			if (!n.toString().equals(renaming.nameAfter)) {
				return false;
			}
			final CompilationUnit cu = (CompilationUnit) n.getRoot();
			return renaming.linesAfter.contains(cu.getLineNumber(n
					.getStartPosition()));
		}

		@Override
		public void visitCommitFiles(final RevCommit commit) {
			System.out.println("Visiting " + commit + " for renamings");
			final Collection<Renaming> renamingsAtThisPoint = renamings
					.get(commit.name());
			for (final Renaming renaming : renamingsAtThisPoint) {
				// Get the file
				final File currentFile = new File(
						repositoryDir.getAbsolutePath() + renaming.filename);
				checkArgument(currentFile.exists());

				try {
					ResolvedSourceCode resolvedCode = variableExtractor
							.getResolvedSourceCode(currentFile,
									n -> matchRenaming(renaming, n));
					if (!resolvedCode.getAllBindings().isEmpty()) {
						renamedVariablesDatapoints
								.add(RenamedSerializableResolvedSourceCode
										.fromResolvedSourceCode(
												resolvedCode,
												Lists.newArrayList(renaming.nameBefore)));
					} else {
						resolvedCode = methodDeclExtractor
								.getResolvedSourceCode(currentFile,
										n -> matchRenaming(renaming, n));
						if (!resolvedCode.getAllBindings().isEmpty()) {
							renamedMethodDeclarationsDatapoints
									.add(RenamedSerializableResolvedSourceCode.fromResolvedSourceCode(
											resolvedCode,
											Lists.newArrayList(renaming.nameBefore)));
						}
					}
				} catch (final IOException e) {
					// File always exists, since we checked above.
					throw new IllegalStateException(e);
				}
			}
		}
	}

	public static class RenamedSerializableResolvedSourceCode extends
			SerializableResolvedSourceCode {

		public static RenamedSerializableResolvedSourceCode fromResolvedSourceCode(
				final ResolvedSourceCode rsc, final List<String> previousNames) {
			return new RenamedSerializableResolvedSourceCode(rsc, previousNames);
		}

		public final List<String> previousNames;

		protected RenamedSerializableResolvedSourceCode(
				final ResolvedSourceCode rsc, final List<String> previousNames) {
			super(rsc);
			this.previousNames = previousNames;
		}

	}

	public static void main(final String[] args) throws NoWorkTreeException,
			NoHeadException, IOException, GitAPIException {
		if (args.length != 4) {
			System.err
					.println("Usage <datafile> <prefix> <repositoryDir> <outputFilePrefix>");
			System.exit(-1);
		}
		final SvnToGitMapper mapper = new SvnToGitMapper(args[2]);
		final RepentDataParser rdp = new RepentDataParser(new File(args[0]),
				mapper.mapSvnToGit(), args[1], new Predicate<Integer>() {

					@Override
					public boolean test(final Integer t) {
						return t > 250;
					}
				});
		final List<Renaming> renamings = rdp.parse();
		final Multimap<String, Renaming> renamingsPerSha = mapRenamingsToTargetSha(renamings);
		final BindingExtractor be = new BindingExtractor(args[2],
				renamingsPerSha);
		be.doWalk();

		writeJson(args[3], "_variables.json", be.renamedVariablesDatapoints);
		writeJson(args[3], "_methoddeclarations.json",
				be.renamedMethodDeclarationsDatapoints);
	}

	public static Multimap<String, Renaming> mapRenamingsToTargetSha(
			final List<Renaming> renamings) {
		final Multimap<String, Renaming> renamingsPerSha = HashMultimap
				.create();
		renamings.forEach(r -> renamingsPerSha.put(r.toVersion, r));
		return renamingsPerSha;
	}

	/**
	 * @param outputFilePrefix
	 * @param outputFileSuffix
	 * @param renamedVariablesDatapoints
	 * @throws IOException
	 * @throws JsonIOException
	 */
	public static void writeJson(
			final String outputFilePrefix,
			final String outputFileSuffix,
			final List<RenamedSerializableResolvedSourceCode> renamedVariablesDatapoints)
			throws IOException, JsonIOException {
		final File outputFile = new File(outputFilePrefix + outputFileSuffix);
		final FileWriter writer = new FileWriter(outputFile);
		try {
			final Gson gson = new Gson();
			gson.toJson(renamedVariablesDatapoints, writer);
		} finally {
			writer.close();
		}
	}

}