package application.simulator;

import com.mxgraph.model.mxGraphModel;
import com.mxgraph.view.mxGraph;
import models.dataConstraintModel.ResourcePath;
import simulator.Resource;
import simulator.ResourceIdentifier;
import simulator.Simulator;
import simulator.SystemState;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class SimulationLayout {
    // Hold the division level for each resource
    HashMap<ResourceIdentifier,Integer> divisionLevelMap = new HashMap<>();
    // Hold the scaling factor for each resource
    HashMap<ResourceIdentifier,Double> resourceScaleMap = new HashMap<>();
    int maxDivisionLevel = 1;
    public final double BASE_WIDTH = 720;
    public final double BASE_HEIGHT = BASE_WIDTH/2;
    public final double MARGIN_SCALE = 0.92;
    SystemState systemState;
    public SimulationLayout(SystemState systemState){
        this.systemState = systemState;
        // Calculate the division level for each resource
        for (Resource res:systemState.getRootResources()) {
            setDivisionLevel(divisionLevelMap, res.getResourceIdentifier(), 1);
        }
        // Calculate the scaling factor for each resource
        for (ResourceIdentifier res:divisionLevelMap.keySet()) {
            double scale = (double) maxDivisionLevel / divisionLevelMap.get(res);
            resourceScaleMap.put(res, scale);
        }
    }
    public double getScale(ResourceIdentifier resource){
        return resourceScaleMap.get(resource);
    }
    public double getDivision(ResourceIdentifier resource){
        return divisionLevelMap.get(resource);
    }

    public int getMaxDivisionLevel() {
        return maxDivisionLevel;
    }

    public boolean isExistResource(ResourceIdentifier resourceIdentifier){
        return divisionLevelMap.containsKey(resourceIdentifier);
    }
    private void setDivisionLevel(HashMap<ResourceIdentifier,Integer> resourceSet, ResourceIdentifier resource, int childCount){
        int divisionLevel;
        if(resourceSet.get(resource.getParent()) == null){
            divisionLevel = 1;
        }else{
            divisionLevel = resourceSet.get(resource.getParent()) * childCount;
        }
        if (divisionLevel > maxDivisionLevel)maxDivisionLevel = divisionLevel;
        resourceSet.put(resource,divisionLevel);

        Collection<Resource> identifiers = systemState.getResource(resource).getChildren();
        if (identifiers != null) {
            for (Resource child:identifiers) {
                setDivisionLevel(resourceSet, child.getResourceIdentifier(), identifiers.size());
            }
        }
    }
    /**
     *
     * Draw an object corresponding to the target resource on the mxGraph.
     * @param graph The mxGraph for drawing.
     * @param parent The parent object of the target resource.
     * @param resourceIdentifier The ID of the target resource.
     * @param index The index.
     * @param length The number of sibling resources of the target resource.
     * @return The drawing object of the target resource.
     */

    private Object setLayout(mxGraph graph,Object parent, ResourceIdentifier resourceIdentifier,double index, double length){
        double width, height, x, y;
        double parentWidth = graph.getCellGeometry(parent).getWidth();
        double parentHeight = graph.getCellGeometry(parent).getHeight();

        width = parentWidth / length * MARGIN_SCALE;
        height = width/2;
        x = parentWidth * (index - 1)/length + (parentWidth/length)*(1-MARGIN_SCALE)/2;

        // Process to avoid hiding the parent's resource name
        if ((int)length == 1) {
            y = 20;
            height -=20;
        } else {
            y = parentHeight/2 - height/2;
        }

        Object result = graph.insertVertex(parent, null,
                resourceIdentifier.toString(), x, y, width, height,
                "shape=ellipse;perimeter=ellipsePerimeter;verticalAlign=top;");
        System.out.println(result);
        return result;
    }

    public mxGraph constructSimulateGraph(mxGraph graph, Simulator simulator){
        ((mxGraphModel) graph.getModel()).clear();
        Object parent = graph.getDefaultParent();
        graph.getModel().beginUpdate();

        try {
            Map<Resource, Object> resources = new HashMap<>();
            int i = 0;
            int childCount = simulator.getCurState().getRootResources().size();
            // create resource vertices
            for (Resource resNode: simulator.getCurState().getRootResources()) {
                double scale = this.getScale(resNode.getResourceIdentifier());
                double maxDiv = this.getMaxDivisionLevel();
                double w = this.BASE_WIDTH * scale / maxDiv / childCount;
                double h = this.BASE_HEIGHT * scale / maxDiv / childCount;
                double x = w * i + 50;
                double y = 20 ;
                ResourcePath res = resNode.getResourceIdentifier();
                Object resource = graph.insertVertex(parent, null,
                        res.toString(), x, y, w, h,
                        "shape=ellipse;perimeter=ellipsePerimeter;verticalAlign=top;"); // insert a resource as a vertex
                resources.put(resNode, resource);
                i++;
                createNextChildSimulateResourceVerticies(graph, resource, resNode, resources);

            }

        } finally {
            graph.getModel().endUpdate();
        }

        return graph;
    }
    private void createNextChildSimulateResourceVerticies(mxGraph graph, Object parent, Resource resNode, Map<Resource, Object> resources) { //sample
        Collection<Resource> children = resNode.getChildren();
        if (children != null) {
            //List<Resource> children = resNode.getChildren();
            double i = 1;
            double childCount = children.size();
            for (Resource childNode: children) {
                Object childResource = setLayout(graph, parent, childNode.getResourceIdentifier(), i, childCount);
                resources.put(childNode, childResource);
                i++;
                createNextChildSimulateResourceVerticies(graph, childResource, childNode, resources);

            }
        }
        if (children == null || children.size() == 0) {
            Object state = graph.insertVertex(parent, null,
                    resNode.getState().getValue().toString(), 0.5, 0.5, 0.25, 0.25, "opacity=0;verticalAlign=down;", true); // insert a state label as a vertex
        }
    }
}
