package com.atlassian.bitbucket.mesh.git.builder.push;

import javax.annotation.Nonnull;
import java.util.*;

import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;

public class PushProgressStateMachine {

    private final Map<ProgressStep, Node> stateMachine;

    private PushProgressStateMachine(Builder builder) {
        LinkedHashMap<ProgressStep, Node> stateMachine = new LinkedHashMap<>();

        for (Node node : builder.nodes) {
            stateMachine.put(node.state, node);
        }
        this.stateMachine = unmodifiableMap(stateMachine);
    }

    @Nonnull
    public Optional<Float> getTotalPercentage(@Nonnull ProgressStep step, float stepPercentage) {
        requireNonNull(step, "step");

        Node node = stateMachine.get(step);
        if (node == null) {
            return Optional.empty();
        }
        return Optional.of(node.cumulativeWeight + (stepPercentage * node.weight));
    }

    public static class Builder {

        private final List<Node> nodes;

        public Builder() {
            nodes = new ArrayList<>();
        }

        @Nonnull
        public PushProgressStateMachine build() {
            return new PushProgressStateMachine(this);
        }

        @Nonnull
        public Builder step(@Nonnull ProgressStep step, float weight) {
            requireNonNull(step, "step");

            float cumulativeWeight;
            if (nodes.isEmpty()) {
                cumulativeWeight = 0;
            } else {
                Node lastNode = nodes.get(nodes.size() - 1);
                cumulativeWeight = lastNode.cumulativeWeight + lastNode.weight;
            }
            nodes.add(new Node(step, weight, cumulativeWeight));

            return this;
        }
    }

    public enum ProgressStep {

        ENUMERATING_OBJECTS("Enumerating objects"),
        CHECKING_CONNECTIVITY("Checking connectivity"),
        COUNTING_OBJECTS("Counting objects"),
        COMPRESSING_OBJECTS("Compressing objects"),
        DELTA_COMPRESSION("Delta compression"),
        TOTAL("Total"),
        RECEIVING_OBJECTS("Receiving objects"),
        RESOLVING_DELTAS("Resolving deltas"),
        WRITING_OBJECTS("Writing objects");

        private final String label;

        ProgressStep(String label) {
            this.label = label;
        }

        public String getLabel() {
            return label;
        }
    }

    private static class Node {

        private final float cumulativeWeight;
        private final ProgressStep state;
        private final float weight;

        private Node(ProgressStep state, float weight, float cumulativeWeight) {
            this.cumulativeWeight = cumulativeWeight;
            this.state = state;
            this.weight = weight;
        }
    }
}
