package application.simulator;

import algorithms.TypeInference;
import application.editor.Editor;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;
import models.dataFlowModel.DataTransferModel;
import simulator.Event;
import simulator.Simulator;
import simulator.SystemState;
import simulator.interfaces.INativeReceiver;

import javax.swing.*;
import java.util.ArrayList;
import java.util.List;

public class SimulatorWindow extends JFrame implements INativeReceiver {

    private static final long serialVersionUID = -2425820512017088254L;
    public static final String title = "Simulation Tool";

    final int PORT_DIAMETER = 8;
    final int PORT_RADIUS = PORT_DIAMETER / 2;

    private Editor editor = null;
    private mxGraph graph = null;
    private mxGraphComponent graphComponent = null;

    private Simulator simulator = null;

    private boolean bReflectingArchitectureModel = false;
    private double x = 20;
    private double y = 20;
    private SimulatorMenuBar menuBar;

    public SimulatorWindow(Editor editor) {
        setTitle(title);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        this.graph = new mxGraph() {
            public boolean isPort(Object cell) {
                mxGeometry geo = getCellGeometry(cell);

                return (geo != null) ? geo.isRelative() : false;
            }

            public boolean isCellFoldable(Object cell, boolean collapse) {
                return false;
            }
        };

        this.graphComponent = new mxGraphComponent(graph);
        this.editor = editor;

        graph.getModel().addListener(mxEvent.CHANGE, new mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                List<mxCell> terminals = new ArrayList<>();
                mxCell cell = null;
                for (Object change : ((List) evt.getProperties().get("changes"))) {
                    if (change instanceof mxGraphModel.mxTerminalChange) {
                        mxGraphModel.mxTerminalChange terminalChange = (mxGraphModel.mxTerminalChange) change;
                        cell = (mxCell) terminalChange.getCell();
                        mxCell terminal = (mxCell) terminalChange.getTerminal();
                        terminals.add(terminal);
                    }
                }
                if (terminals.size() == 2) {
                    if (!editor.connectEdge(cell, terminals.get(0), terminals.get(1))) {
                        graph.removeCells(new mxCell[]{cell});
                    }
                }
            }
        });
        getContentPane().add(graphComponent);
        new mxRubberband(graphComponent);
        graph.setAllowDanglingEdges(false);
        graph.setCellsDisconnectable(true);

        setTitle(title);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setSize(870, 640);
        setVisible(true);

        DataTransferModel model = this.editor.getModel();
        TypeInference.infer(model);
        simulator = new Simulator(model);
        SimulationLayout layout = new SimulationLayout(simulator.getCurState());
        layout.constructSimulateGraph(graph, simulator);
        graphComponent.setCellEditor(new InputEventCellEditor(this, graphComponent, simulator, this.editor));
        simulator.addSystemReceiver(this);

        menuBar = new SimulatorMenuBar(this);
        setJMenuBar(menuBar);
    }


    @Override
    public void onReceiveFromModel(Event event, SystemState nextSystemState) {
        SimulationLayout layout = new SimulationLayout(nextSystemState);
        layout.constructSimulateGraph(graph, simulator);
    }

//	public mxGraph constructSimulateGraph(Set<Resource> simulateRes, DataTransferModel model, DataFlowGraph dataFlowGraph) { 
//		bReflectingArchitectureModel = true;
//		((mxGraphModel) graph.getModel()).clear();
//		Object parent = graph.getDefaultParent();
//		graph.getModel().beginUpdate();
//
//		try {
//			Map<Resource, Object> resources = new HashMap<>();
//
//			// create resource vertices
//			for (Resource resNode: simulateRes) {
//				int w = 80;
//				int h = 30;
//				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);
//				createChildSimulateResourceVerticies(resource, resNode, resources, w, h);
//				x+=80;
//			}
//
//		} finally {
//			graph.getModel().endUpdate();
//		}
//
//		bReflectingArchitectureModel = false;
//		return graph;
//	}
//	
//	
//	public void createChildSimulateResourceVerticies(Object resource, Resource resNode, Map<Resource, Object> resources, int w, int h) { //sample
//		
//		if(resNode.getChildren() != null) {
//			for (Resource childNode: resNode.getChildren()) {
//				ResourcePath childRes = childNode.getResourceIdentifier();
//				Object childResource = graph.insertVertex(resource, null,
//						childRes.toString(), 0, 0, w/2, h/2,
//						"shape=ellipse;perimeter=ellipsePerimeter;verticalAlign=top;"); // insert a resource as a vertex
//				resources.put(childNode, childResource);
//				createChildSimulateResourceVerticies(childResource, childNode, resources, w, h);
//			}
//		}
//		if (resNode.getChildren() == null || resNode.getChildren().size() == 0) {
//			Object state = graph.insertVertex(resource, 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
//		}
//	}

    public mxGraphComponent getGraphComponent() {
        return graphComponent;
    }

    public Editor getEditor() {
        return editor;
    }

    public void setEditor(Editor editor) {
        this.editor = editor;
    }

    public Simulator getSimulator() {
        return simulator;
    }

}
