package org.bdware.sc.crdt.planning;

import java.util.*;

public class SharableNetworkPlanning {
    protected String[] nodeIds;
    protected int[] writers;
    protected int[] readers;
    protected long maxDelay;

    protected int bandwidthDownload;
    protected int bandwidthUpload;
    protected int dataSize;


    protected long wDelay;
    protected long rDelay;
    protected long w2rDelay;


    protected double totalCountW;
    protected double rootCountW;
    protected double treeNodeCountW;
    protected double treeDegreeW;
    protected double treeHeightW;
    protected double frequencySyncW;


    protected double totalCountR;
    protected double rootCountR;
    protected double treeNodeCountR;
    protected double treeDegreeR;
    protected double treeHeightR;
    protected double frequencySyncR;

    protected double frequencySyncWR;

    protected long totalData;

    protected static double logNM(double n, double m) {
        return Math.log(m) / Math.log(n);
    }

    public void adjustWriterTree(int rootCountW, int treeDegreeW) {
        this.rootCountW = rootCountW;
        this.treeDegreeW = treeDegreeW;
        this.treeNodeCountW = Math.ceil(totalCountW / rootCountW);
        if (treeDegreeW > 1) {
            this.treeHeightW =
                    Math.ceil(logNM(treeDegreeW, treeNodeCountW * (treeDegreeW - 1) + 1));
        } else {
            this.treeHeightW = treeNodeCountW;
        }
    }

    public void adjustReaderTree(int rootCountR, int treeDegreeR) {
        this.rootCountR = rootCountR;
        this.treeDegreeR = treeDegreeR;
        this.treeNodeCountR = Math.ceil(totalCountR / rootCountR);
        if (treeDegreeR > 1) {
            this.treeHeightR =
                    Math.ceil(logNM(treeDegreeR, treeNodeCountR * (treeDegreeR - 1) + 1));
        } else {
            this.treeHeightR = treeNodeCountR;
        }
    }

    protected void adjustAndCalc() {
        long minTotalData = Long.MAX_VALUE;
        String result = "";
        for (int rootCountW = 1; rootCountW <= writers.length; ++rootCountW) {
            int nodeCountPerTree = (int) Math.ceil(writers.length * 1.0 / rootCountW);
            for (int treeDegreeW = 1; treeDegreeW <= nodeCountPerTree; ++treeDegreeW) {
                adjustWriterTree(rootCountW, treeDegreeW);
                for (int rootCountR = 1; rootCountR <= readers.length; ++rootCountR) {
                    int maxTreeDegreeR = (int) Math.ceil(readers.length * 1.0 / rootCountR);
                    for (int treeDegreeR = 1; treeDegreeR <= maxTreeDegreeR; ++treeDegreeR) {
                        adjustReaderTree(rootCountR, treeDegreeR);
                        calcOptimizedResult();
                        if (!readerTreeConstraint()) {
                            // System.out.println("reader");
                            continue;
                        }
                        if (!writerTreeConstraint()) {
                            // System.out.println("writer");
                            continue;
                        }
                        if (!writer2ReaderConstraint()) {
                            // System.out.println("writer2Reader");
                            continue;
                        }
                        if (totalData > 0 && minTotalData > totalData) {
                            minTotalData = totalData;
                            result = toString();
                        }
                    }
                }
            }
        }

        System.out.println(minTotalData);
        System.out.println(result);
    }

    protected void calcOptimizedResult() {}

    protected boolean writer2ReaderConstraint() {
        return true;
    }

    protected boolean writerTreeConstraint() {
        return true;
    }

    protected boolean readerTreeConstraint() {
        return true;
    }

    public void allocate() {
        Set<Integer> writerOnlySet = new LinkedHashSet<>();
        for (int i : writers) {
            writerOnlySet.add(i);
        }
        Set<Integer> readerOnlySet = new LinkedHashSet<>();
        Set<Integer> rwSet = new LinkedHashSet<>();
        for (int i : readers) {
            if (writerOnlySet.contains(i)) {
                rwSet.add(i);
                writerOnlySet.remove(i);
            } else {
                readerOnlySet.add(i);
            }
        }
        int[] writerParents = allocateTreeNode(writerOnlySet, rwSet, (int) rootCountW,
                (int) treeDegreeW, wDelay, (int) treeHeightW);
        int[] readerParents = allocateTreeNode(readerOnlySet, rwSet, (int) rootCountR,
                (int) treeDegreeR, rDelay, (int) treeHeightR);
        System.out.println(readerParents);
    }

    private int[] allocateTreeNode(Set<Integer> onlySet, Set<Integer> rwSet, int rootCount,
            int degree, long delay, int height) {
        int[] result = new int[nodeIds.length];
        long[] writerInterval = new long[nodeIds.length];
        Arrays.fill(result, -2);

        Map<Integer, List<Integer>> children = new HashMap<>();
        Deque<Integer> notFullNodesIdx = new LinkedList<>();
        for (Integer idx : onlySet) {
            if (children.size() < rootCount) {
                children.put(idx, new ArrayList<>());
                notFullNodesIdx.add(idx);
                result[idx] = -1;
            } else {
                int parentIdx = notFullNodesIdx.peek();
                children.computeIfAbsent(parentIdx, k -> new ArrayList<>()).add(idx);
                notFullNodesIdx.addLast(idx);
                if (children.get(parentIdx).size() >= degree) {
                    notFullNodesIdx.pop();
                }
                writerInterval[idx] = delay / (height - 1);
            }
        }

        // 读写节点放在尽量靠近叶子节点，以便带宽的平衡
        for (Integer idx : rwSet) {
            if (children.size() < rootCount) {
                children.put(idx, new ArrayList<>());
                notFullNodesIdx.add(idx);
                result[idx] = -1;
            } else {
                int parentIdx = notFullNodesIdx.peek();
                children.computeIfAbsent(parentIdx, k -> new ArrayList<>()).add(idx);
                notFullNodesIdx.addLast(idx);
                if (children.get(parentIdx).size() >= degree) {
                    notFullNodesIdx.pop();
                }
                writerInterval[idx] = delay / (height - 1);
            }
        }

        for (Integer parentId : children.keySet()) {
            for (int childId : children.get(parentId)) {
                result[childId] = parentId;
            }
        }
        return result;
    }

    @Override
    public String toString() {
        String result = "[" + wDelay + "," + w2rDelay + "," + rDelay + "]\n"
                + "writer tree: degree " + treeDegreeW + ", count " + rootCountW + ",\n"
                + "reader tree: degree " + treeDegreeR + ", count " + rootCountR;
        return result;
    }
}
