package com.beijunyi.parallelgit.web.protocol;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;

import com.beijunyi.parallelgit.filesystem.GitFileSystem;
import com.beijunyi.parallelgit.filesystem.exceptions.UnsuccessfulOperationException;
import com.beijunyi.parallelgit.filesystem.io.GfsTreeIterator;
import com.beijunyi.parallelgit.web.data.FileType;
import com.beijunyi.parallelgit.web.protocol.model.DeltaType;
import com.beijunyi.parallelgit.web.protocol.model.FileDelta;
import com.beijunyi.parallelgit.web.protocol.model.FileState;
import com.google.common.base.Optional;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.*;

import static com.beijunyi.parallelgit.utils.CommitUtils.getCommit;
import static com.beijunyi.parallelgit.utils.TreeUtils.getObjectId;
import static com.beijunyi.parallelgit.utils.TreeUtils.isDirectory;
import static com.beijunyi.parallelgit.web.data.FileType.MISSING;
import static com.beijunyi.parallelgit.web.protocol.model.DeltaType.*;
import static org.eclipse.jgit.treewalk.filter.TreeFilter.ANY_DIFF;

public class DiffDirectoryHandler extends AbstractGfsRequestHandler {

  private static final int CURRENT = 0;
  private static final int PREVIOUS = 1;

  @Nonnull
  @Override
  public String getType() {
    return "diff-directory";
  }

  @Nonnull
  @Override
  public ServerResponse handle(@Nonnull ClientRequest request, @Nonnull GitFileSystem gfs) throws IOException {
    AbstractTreeIterator source = getSourceTree(request, gfs);
    AbstractTreeIterator target = getTargetTree(request, gfs);
    TreeWalk tw = setupDiffTree(source, target, gfs.getRepository());
    List<FileDelta> deltas = collectDeltas(tw);
    return request.respond().ok(deltas);
  }

  @Nonnull
  private static AbstractTreeIterator getSourceTree(@Nonnull ClientRequest request, @Nonnull GitFileSystem gfs) throws IOException {
    Optional<String> revision = request.getOptionalString("source-revision");
    String path = request.getString("source-path");
    return getTree(revision, path, gfs);
  }

  @Nonnull
  private static AbstractTreeIterator getTargetTree(@Nonnull ClientRequest request, @Nonnull GitFileSystem gfs) throws IOException {
    Optional<String> revision = request.getOptionalString("target-revision");
    String path = request.getString("target-path");
    return getTree(revision, path, gfs);
  }

  @Nonnull
  private static AbstractTreeIterator getTree(@Nonnull Optional<String> revision, @Nonnull String path, @Nonnull GitFileSystem gfs) throws IOException {
    AbstractTreeIterator ret;
    if(revision.isPresent()) {
      ret = getTreeFromRevision(revision.get(), path, gfs.getRepository());
    } else {
      ret = getTreeFromGfs(path, gfs);
    }
    return ret;
  }

  @Nonnull
  private static AbstractTreeIterator getTreeFromRevision(@Nonnull String revision, @Nonnull String path, @Nonnull Repository repo) throws IOException{
    AnyObjectId rootTree = getCommit(revision, repo).getTree();
    AnyObjectId dirTree = getObjectId(path, rootTree, repo);
    if(dirTree != null) {
      CanonicalTreeParser treeParser = new CanonicalTreeParser();
      treeParser.reset(repo.newObjectReader(), dirTree);
      return treeParser;
    } else {
      return new EmptyTreeIterator();
    }
  }

  @Nonnull
  private static AbstractTreeIterator getTreeFromGfs(@Nonnull String path, @Nonnull GitFileSystem gfs) throws IOException{
    throw new UnsuccessfulOperationException();
  }

  @Nonnull
  private static TreeWalk setupDiffTree(@Nonnull AbstractTreeIterator source, @Nonnull AbstractTreeIterator target, @Nonnull Repository repo) throws IOException {
    TreeWalk ret = new TreeWalk(repo);
    ret.addTree(source);
    ret.addTree(target);
    ret.setRecursive(true);
    ret.setFilter(ANY_DIFF);
    return ret;
  }

  @Nonnull
  private static List<FileDelta> collectDeltas(@Nonnull TreeWalk tw) throws IOException {
    List<FileDelta> ret = new ArrayList<>();
    while(tw.next()) {
      FileState current = readState(tw, CURRENT);
      FileState previous = readState(tw, PREVIOUS);
      DeltaType type = computeDeltaType(current, previous);
      ret.add(new FileDelta(tw.getPathString(), current, previous, type));
    }
    return ret;
  }

  @Nonnull
  private static FileState readState(@Nonnull TreeWalk tw, int index) {
    return new FileState(tw.getObjectId(index).getName(), FileType.fromMode(tw.getFileMode(index)));
  }


  @Nonnull
  private static DeltaType computeDeltaType(@Nonnull FileState current, @Nonnull FileState previous) {
    if(previous.getType() != MISSING && current.getType() != MISSING) {
      boolean contentChanged = previous.getHash().equals(current.getHash());
      boolean typeChanged = previous.getType().equals(current.getType());
      if(contentChanged && typeChanged)
        return DATA_AND_TYPE_DELTA;
      if(contentChanged)
        return DATA_DELTA;
      if(typeChanged)
        return TYPE_DELTA;
    }
    if(previous.getType() == MISSING && current.getType() != MISSING)
      return INSERTION;
    if(previous.getType() != MISSING && current.getType() == MISSING)
      return DELETION;
    throw new IllegalStateException();
  }

}

@@ -302,71 +302,8 @@
 on;%0A
-import com.beijunyi.parallelgit.filesystem.io.GfsTreeIterator;%0A
 impo
@@ -539,48 +539,8 @@
 te;%0A
-import com.google.common.base.Optional;%0A
 impo
@@ -793,76 +793,8 @@
 Id;%0A
-import static com.beijunyi.parallelgit.utils.TreeUtils.isDirectory;%0A
 impo
@@ -926,16 +926,67 @@
 Type.*;%0A
+import static org.eclipse.jgit.lib.Constants.HEAD;%0A
 import s
@@ -1857,32 +1857,22 @@
 n %7B%0A    
-Optional%3C
 String
-%3E
  revisio
@@ -1882,32 +1882,24 @@
  request.get
-Optional
 String(%22sour
@@ -2159,32 +2159,22 @@
 n %7B%0A    
-Optional%3C
 String
-%3E
  revisio
@@ -2188,24 +2188,16 @@
 uest.get
-Optional
 String(%22
@@ -2383,24 +2383,14 @@
 ull 
-Optional%3C
 String
-%3E
  rev
@@ -2508,31 +2508,77 @@
  if(
-revision.isPresent()) %7B
+HEAD.equals(revision)) %7B%0A      Repository repo = gfs.getRepository();
 %0A   
@@ -2618,22 +2618,16 @@
 sion
-.get()
 , path, 
 gfs.
@@ -2622,35 +2622,20 @@
 , path, 
-gfs.getRepository()
+repo
 );%0A    %7D

use HEAD rather than null to represent head of gfs