package graphql.execution2.result;

import graphql.Assert;
import graphql.Internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Internal
public class ExecutionResultMultiZipper {


    private final ExecutionResultNode commonRoot;
    private final List<ExecutionResultZipper> zippers;


    public ExecutionResultMultiZipper(ExecutionResultNode commonRoot, List<ExecutionResultZipper> zippers) {
        this.commonRoot = Assert.assertNotNull(commonRoot);
        this.zippers = new ArrayList<>(Assert.assertNotNull(zippers));
    }

    public ExecutionResultNode toRootNode() {
        if (zippers.size() == 0) {
            return commonRoot;
        }

        List<ExecutionResultZipper> curZippers = new ArrayList<>(zippers);
        while (curZippers.size() > 1) {

            List<ExecutionResultZipper> deepestZippers = getDeepestZippers(curZippers);
            Map<ExecutionResultNode, List<ExecutionResultZipper>> sameParent = zipperWithSameParent(deepestZippers);

            List<ExecutionResultZipper> newZippers = new ArrayList<>();
            for (Map.Entry<ExecutionResultNode, List<ExecutionResultZipper>> entry : sameParent.entrySet()) {
                ExecutionResultZipper newZipper = moveUp(entry.getKey(), entry.getValue());
                newZippers.add(newZipper);
            }
            curZippers.removeAll(deepestZippers);
            curZippers.addAll(newZippers);
        }
        Assert.assertTrue(curZippers.size() == 1, "illegal state");
        return curZippers.get(0).toRootNode();
    }

    public ExecutionResultNode getCommonRoot() {
        return commonRoot;
    }

    public List<ExecutionResultZipper> getZippers() {
        return new ArrayList<>(zippers);
    }

    public ExecutionResultMultiZipper withZippers(List<ExecutionResultZipper> zippers) {
        return new ExecutionResultMultiZipper(commonRoot, zippers);
    }

    public ExecutionResultMultiZipper withReplacedZipper(ExecutionResultZipper oldZipper, ExecutionResultZipper newZipper) {
        int index = zippers.indexOf(oldZipper);
        Assert.assertTrue(index >= 0, "oldZipper not found");
        List<ExecutionResultZipper> newZippers = new ArrayList<>(zippers);
        newZippers.set(index, newZipper);
        return new ExecutionResultMultiZipper(commonRoot, newZippers);
    }


    private List<ExecutionResultZipper> getDeepestZippers(List<ExecutionResultZipper> zippers) {
        //todo: change to linkedhashmap
        Map<Integer, List<ExecutionResultZipper>> grouped =
                zippers.stream().collect(Collectors.groupingBy(executionResultZipper -> executionResultZipper.getBreadcrumbList().size()));
        Integer maxLevel = Collections.max(grouped.keySet());
        return grouped.get(maxLevel);
    }

    private ExecutionResultZipper moveUp(ExecutionResultNode parent, List<ExecutionResultZipper> sameParent) {
        Map<ExecutionResultNodePosition, ExecutionResultNode> newChildren = new LinkedHashMap<>();
        List<Breadcrumb> restBreadcrumbs = Collections.emptyList();
        for (ExecutionResultZipper zipper : sameParent) {
            restBreadcrumbs = zipper.getBreadcrumbList().subList(1, zipper.getBreadcrumbList().size());
            newChildren.put(zipper.getBreadcrumbList().get(0).position, zipper.getCurNode());
        }
        ExecutionResultNode newNode = parent.withNewChildren(newChildren);
        return new ExecutionResultZipper(newNode, restBreadcrumbs);
    }

    private Map<ExecutionResultNode, List<ExecutionResultZipper>> zipperWithSameParent(List<ExecutionResultZipper> zippers) {
        //todo: change to LinkedHashMap
        return zippers.stream().collect(Collectors.groupingBy(ExecutionResultZipper::getParent));
    }

}

@@ -234,25 +234,163 @@
 til.
-stream.Collectors
+function.Function;%0Aimport java.util.stream.Collectors;%0A%0Aimport static java.util.stream.Collectors.mapping;%0Aimport static java.util.stream.Collectors.toList
 ;%0A%0A@
@@ -2678,48 +2678,8 @@
 ) %7B%0A
-        //todo: change to linkedhashmap%0A
     
@@ -2870,16 +2870,76 @@
 ).size()
+, LinkedHashMap::new, mapping(Function.identity(), toList())
 ));%0A    
@@ -3865,48 +3865,8 @@
 ) %7B%0A
-        //todo: change to LinkedHashMap%0A
     
@@ -3955,16 +3955,76 @@
 etParent
+, LinkedHashMap::new, mapping(Function.identity(), toList())
 ));%0A    

use linkedHashMap for groupBy