package ee.shy.core.diff;

import ee.shy.core.Tree;
import ee.shy.core.TreeItem;
import ee.shy.storage.DataStorage;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class TreePairs {

    interface Visitor {
        void visitFilePair(String path, TreeItem lhs, TreeItem rhs) throws IOException;
        void visitTreePair(String path, TreeItem lhs, TreeItem rhs) throws IOException;
    }

    private static abstract class Pair implements Comparable<Pair> {
        protected final String path;
        protected final TreeItem lhs;
        protected final TreeItem rhs;

        public Pair(String path, TreeItem lhs, TreeItem rhs) {
            this.path = path;
            this.lhs = lhs;
            this.rhs = rhs;
        }

        public abstract void visit(Visitor visitor) throws IOException;

        public abstract Pair flip();

        @Override
        public int compareTo(Pair o) {
            return path.compareTo(o.path); // TODO: 1.05.16 compare by respective path elements and addition/deletion
        }
    }

    private static class FilePair extends Pair {
        public FilePair(String path, TreeItem lhs, TreeItem rhs) {
            super(path, lhs, rhs);
        }

        @Override
        public Pair flip() {
            return new FilePair(path, rhs, lhs);
        }

        @Override
        public void visit(Visitor visitor) throws IOException {
            visitor.visitFilePair(path, lhs, rhs);
        }
    }

    private static class TreePair extends Pair {
        public TreePair(String path, TreeItem lhs, TreeItem rhs) {
            super(path, lhs, rhs);
        }

        @Override
        public Pair flip() {
            return new TreePair(path, rhs, lhs);
        }

        @Override
        public void visit(Visitor visitor) throws IOException {
            visitor.visitTreePair(path, lhs, rhs);
        }
    }

    public static void visitPairs(DataStorage storage, Tree lhs, Tree rhs, Visitor visitor) throws IOException {
        List<Pair> lhsPairs = new ArrayList<>();
        walk(storage, lhsPairs, "", lhs, rhs);

        List<Pair> rhsPairs = new ArrayList<>();
        walk(storage, rhsPairs, "", rhs, lhs);
        rhsPairs = rhsPairs.stream().map(Pair::flip).collect(Collectors.toList());

        // use TreeSet+Comparable for great profit:
        // + remove duplicate pairs of lhs == rhs
        // + order deletes before adds
        // + order pairs by path
        Set<Pair> pairs = new TreeSet<>();
        pairs.addAll(lhsPairs);
        pairs.addAll(rhsPairs);

        for (Pair pair : pairs) {
            pair.visit(visitor);
        }
    }

    private static void walk(DataStorage storage, List<Pair> pairs, String path, Tree lhs, Tree rhs) throws IOException {
        for (Map.Entry<String, TreeItem> entry : lhs.getItems().entrySet()) {
            String itemPath = entry.getKey();
            TreeItem lhsItem = entry.getValue();
            TreeItem rhsItem = rhs.getItems().get(itemPath);
            String fullPath = path + "/" + itemPath;
            if (lhsItem.getType() == TreeItem.Type.FILE) {
                if (rhsItem == null || rhsItem.getType() == TreeItem.Type.TREE) {
                    pairs.add(new FilePair(fullPath, lhsItem, null));
                }
                if (rhsItem != null && rhsItem.getType() == TreeItem.Type.FILE) {
                    pairs.add(new FilePair(fullPath, lhsItem, rhsItem));
                }
            }
            if (lhsItem.getType() == TreeItem.Type.TREE) {
                Tree lhsNextTree = storage.get(lhsItem.getHash(), Tree.class);
                if (rhsItem == null || rhsItem.getType() == TreeItem.Type.FILE) {
                    pairs.add(new TreePair(fullPath, lhsItem, null));
                    walk(storage, pairs, fullPath, lhsNextTree, Tree.EMPTY);
                }
                if (rhsItem != null && rhsItem.getType() == TreeItem.Type.TREE) {
                    pairs.add(new TreePair(fullPath, lhsItem, rhsItem)); // TODO: useless?
                    walk(storage, pairs, fullPath, lhsNextTree, storage.get(rhsItem.getHash(), Tree.class));
                }
            }
        }
    }
}

@@ -74,16 +74,45 @@
 eeItem;%0A
+import ee.shy.core.TreePath;%0A
 import e
@@ -550,22 +550,24 @@
 d final 
-String
+TreePath
  path;%0A 
@@ -658,30 +658,32 @@
 public Pair(
-String
+TreePath
  path, TreeI
@@ -980,24 +980,26 @@
             
+//
 return path.
@@ -1092,16 +1092,482 @@
 eletion%0A
+            int treePathCompare = path.compareTo(o.path);%0A            if (treePathCompare == 0) %7B%0A                if (o.lhs != null && o.rhs == null) %7B%0A                    return 1;%0A                %7D%0A                else if (o.lhs == null && o.rhs != null) %7B%0A                    return -1;%0A                %7D%0A                else %7B%0A                    return 0;%0A                %7D%0A            %7D%0A            else %7B%0A                return treePathCompare;%0A            %7D%0A
         
@@ -1640,38 +1640,40 @@
 public FilePair(
-String
+TreePath
  path, TreeItem 
@@ -1958,32 +1958,43 @@
 sitFilePair(path
+.toString()
 , lhs, rhs);%0A   
@@ -2072,38 +2072,40 @@
 public TreePair(
-String
+TreePath
  path, TreeItem 
@@ -2394,24 +2394,35 @@
 reePair(path
+.toString()
 , lhs, rhs);
@@ -2633,18 +2633,30 @@
 sPairs, 
-%22%22
+new TreePath()
 , lhs, r
@@ -2746,10 +2746,22 @@
 rs, 
-%22%22
+new TreePath()
 , rh
@@ -3287,22 +3287,24 @@
  pairs, 
-String
+TreePath
  path, T
@@ -3589,22 +3589,24 @@
         
-String
+TreePath
  fullPat
@@ -3617,25 +3617,25 @@
 path
- + %22/%22 + 
+.resolve(
 itemPath
 ;%0A  
@@ -3630,16 +3630,17 @@
 itemPath
+)
 ;%0A      

Replace path strings in TreePairs with TreePaths