package hu.sebcsaba.gitrelocate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

public class GitRelocate {
	
	private final GitRunner git;
	private final GraphBuilder builder;
	private final PointerMode modeOfBranches;
	private final PointerMode modeOfTags;
	
	public GitRelocate(GitRunner git, GraphBuilder builder, PointerMode modeOfBranches, PointerMode modeOfTags) {
		this.git = git;
		this.builder = builder;
		this.modeOfBranches = modeOfBranches;
		this.modeOfTags = modeOfTags;
	}

	public void relocate(CommitID cutPoint, CommitID newBase) {
		GitSubGraph fullTree = builder.buildFullTree();
		GitSubGraph subTree = builder.getSubTree(fullTree, cutPoint);
		String previousHead = git.getActualHeadName();
		relocate(subTree, cutPoint, newBase);
		git.checkOut(previousHead);
	}

	private void relocate(GitSubGraph subTree, CommitID cutPoint, CommitID newBase) {
		Queue<Commit> commitsToClone = new LinkedList<Commit>();
		commitsToClone.addAll(subTree.getCommits());
		
		Map<CommitID, CommitID> cloneMap = new HashMap<CommitID, CommitID>();
		cloneMap.put(cutPoint, newBase);
		
		while (!commitsToClone.isEmpty()) {
			Commit commitToClone = commitsToClone.poll();
			if (commitToClone.getId().equals(cutPoint)) {
				// this is in the queue, but not need to clone
			} else if (hasAllParentsCloned(subTree, commitToClone, cloneMap)) {
				clone(commitToClone, cloneMap);
			} else {
				commitsToClone.add(commitToClone);
			}
		}
		if (modeOfBranches!=PointerMode.SKIP) {
			moveOrClonePointers(cloneMap, subTree.getBranches(), modeOfBranches, false);
		}
		if (modeOfTags!=PointerMode.SKIP) {
			moveOrClonePointers(cloneMap, subTree.getTags(), modeOfTags, true);
		}
	}

	private void moveOrClonePointers(Map<CommitID, CommitID> cloneMap, Map<String, CommitID> pointers, PointerMode mode, boolean isTag) {
		for (String pointerName : pointers.keySet()) {
			CommitID oldId = pointers.get(pointerName);
			if (cloneMap.containsKey(oldId)) {
				CommitID newId = cloneMap.get(oldId);
				if (mode==PointerMode.CLONE) {
					if (isTag) {
						git.createTag("clone-"+pointerName, newId);
					} else {
						git.createBranch("clone-"+pointerName, newId);
					}
				} else if (mode==PointerMode.MOVE) {
					if (isTag) {
						git.removeTag(pointerName);
						git.createTag(pointerName, newId);
					} else {
						git.moveBranch(pointerName, newId);
					}
				}
			}
		}
	}

	private boolean hasAllParentsCloned(GitSubGraph treeToClone, Commit commit, Map<CommitID, CommitID> cloneMap) {
		for (CommitID parentID : commit.getParents()) {
			if (treeToClone.hasCommit(parentID) && !cloneMap.containsKey(parentID)) {
				return false;
			}
		}
		return true;
	}

	private void clone(Commit commitToClone, Map<CommitID, CommitID> cloneMap) {
		if (commitToClone.getParents().isEmpty()) {
			throw new UnsupportedOperationException("Cloning root commit is not implemented yet");
		}
		
		CommitID oldParent1Id = commitToClone.getParents().get(0);
		CommitID newParent1Id = cloneMap.containsKey(oldParent1Id) ? cloneMap.get(oldParent1Id) : oldParent1Id;
		git.checkOut(newParent1Id.toString());
		
		if (commitToClone.getParents().size()==1) {
			CommitID newId = git.cherryPick(commitToClone.getId());
			cloneMap.put(commitToClone.getId(), newId);
		} else {
			List<CommitID> newParentsIds = new ArrayList<CommitID>();
			for (CommitID oldParentId : commitToClone.getParents()) {
				newParentsIds.add(cloneMap.containsKey(oldParentId) ? cloneMap.get(oldParentId) : oldParentId);
			}
			CommitID newId = git.cherryPickMergeCommit(commitToClone.getId(), newParentsIds);
			cloneMap.put(commitToClone.getId(), newId);
		}
	}
	
}

@@ -658,53 +658,192 @@
 %7B%0A%09%09
-GitSubGraph fullTree = builder.buildFullTree(
+%0A%09%09System.out.print(%22Building full tree...%22);%0A%09%09GitSubGraph fullTree = builder.buildFullTree();%0A%09%09System.out.println(%22done%22);%0A%09%09%0A%09%09System.out.print(%22Calculating subtree to relocate...%22
 );%0A%09
@@ -905,16 +905,49 @@
 Point);%0A
+%09%09System.out.println(%22done%22);%0A%09%09%0A
 %09%09String
@@ -987,16 +987,54 @@
 Name();%0A
+%09%09%0A%09%09System.out.print(%22Relocate...%22);%0A
 %09%09reloca
@@ -1065,16 +1065,49 @@
 wBase);%0A
+%09%09System.out.println(%22done%22);%0A%09%09%0A
 %09%09git.ch

add log messages