package org.bdware.sc.crdt.planning;

import java.util.HashMap;
import java.util.Map;

public class PlanningWithkExpansivity extends SharableNetworkPlanning {
    Map<Integer, Map<Integer, Double>> accumulationsCache;
    private final double k;
    private final double domainSize;

    public PlanningWithkExpansivity(String[] nodeIds, int[] writers, int[] readers, long maxDelay,
            int bandwidthDownload, int bandwidthUpload, int dataSize, double domainSize) {
        this.nodeIds = nodeIds;
        this.writers = writers;
        this.readers = readers;
        this.maxDelay = maxDelay;
        this.bandwidthDownload = bandwidthDownload;
        this.bandwidthUpload = bandwidthUpload;
        this.dataSize = dataSize;
        this.totalCountW = writers.length;
        this.totalCountR = readers.length;
        this.domainSize = domainSize;
        this.k = (domainSize - dataSize) / domainSize;
    }

    public boolean writerTreeConstraint() {
        if (frequencySyncW > 0) {
            if (treeDegreeW > 1) {
                double common = frequencySyncW * domainSize * (1 - Math.pow(k,
                        (Math.pow(treeDegreeW, treeHeightW - 1) - 1) / (treeDegreeW - 1)));
                // 非叶子节点下载带宽
                boolean result1 = bandwidthDownload >= common * treeDegreeW;
                // 非根节点上行带宽
                boolean result2 = bandwidthUpload >= common;
                return result1 && result2;
            } else if (treeDegreeW == 1) {
                double common = frequencySyncW * domainSize * (1 - Math.pow(k, treeHeightW - 1));
                boolean result1 = bandwidthDownload >= common;
                // 非根节点上行带宽
                boolean result2 = bandwidthUpload >= common;
                return result1 && result2;
            }
        }
        return true;
    }

    public boolean writer2ReaderConstraint() {
        double common = frequencySyncWR * domainSize * (1 - Math.pow(k, treeNodeCountW));
        // Writer根节点上行带宽
        boolean result1 = bandwidthUpload >= common * rootCountR;
        // Reader根节点下载带宽
        boolean result2 = bandwidthDownload >= common * rootCountW;
        return result1 && result2;
    }

    public boolean readerTreeConstraint() {
        if (frequencySyncR > 0) {
            double common = frequencySyncR * domainSize * (1 - Math.pow(k, totalCountW));
            // Reader非叶子节点上行带宽
            boolean result1 = bandwidthUpload >= common * treeDegreeR;
            // Reader非根节点下载带宽
            boolean result2 = bandwidthDownload >= common;
            return result1 && result2;
        }
        return true;
    }

    double calcAccumulationWithK() {
        if (accumulationsCache == null) {
            accumulationsCache = new HashMap<>();
        }
        int H1 = (int) treeHeightW;
        int D1 = (int) treeDegreeW;
        if (accumulationsCache.get(H1) != null && accumulationsCache.get(H1).get(D1) != null) {
            return accumulationsCache.get(H1).get(D1);
        }
        double result = 0;
        for (int h = 1; h < H1; ++h) {
            result += (Math.pow(D1, h) * (1 - Math.pow(k, (Math.pow(D1, H1 - h) - 1) / (D1 - 1))));
        }
        accumulationsCache.computeIfAbsent(H1, k -> new HashMap<>()).put(D1, result);
        return result;
    }

    double calcAccumulation() {
        int H1 = (int) treeHeightW;
        double result = 0;
        for (int h = 1; h < H1; ++h) {
            result += (1 - Math.pow(k, H1 - h));
        }
        return result;
    }

    public void calcOptimizedResult() {
        double a = 0;
        if (treeDegreeW > 1) {
            a = treeHeightW > 1 ? (treeHeightW - 1) * calcAccumulationWithK() * rootCountW : 0;
        } else if (treeDegreeW == 1) {
            a = treeHeightW > 1 ? (treeHeightW - 1) * calcAccumulation() * rootCountW : 0;
        }

        double b = rootCountR * rootCountW * (1 - Math.pow(k, treeNodeCountW));
        double c = treeHeightR > 1
                ? (treeHeightR - 1) * (totalCountR - rootCountR) * (1 - Math.pow(k, totalCountW))
                : 0;

        double A = Math.sqrt(a);
        double B = Math.sqrt(b);
        double C = Math.sqrt(c);
        wDelay = (long) Math.ceil(maxDelay * (A / (A + B + C)));
        w2rDelay = (long) Math.ceil(maxDelay * (B / (A + B + C)));
        rDelay = (long) Math.ceil(maxDelay * (C / (A + B + C)));


        frequencySyncW = wDelay > 0 ? (treeHeightW - 1) / wDelay : 0;
        frequencySyncR = rDelay > 0 ? (treeHeightR - 1) / rDelay : 0;
        frequencySyncWR = w2rDelay > 0 ? (1.0 / w2rDelay) : 0;

        totalData = (long) (domainSize
                * (frequencySyncW * a + frequencySyncWR * b + frequencySyncR * c));
    }
}
