package application.simulator;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.util.EventObject;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.view.mxICellEditor;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;

import application.editor.Editor;
import application.layouts.DAGLayout;
import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Term;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.DataFlowGraph;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import models.visualModel.FormulaChannel;
import parser.Parser;
import parser.Parser.TokenStream;
import parser.exceptions.ExpectedColon;
import parser.exceptions.ExpectedDoubleQuotation;
import parser.exceptions.ExpectedRightBracket;
import parser.exceptions.WrongJsonExpression;
import simulator.ChannelState;
import simulator.Resource;
import simulator.ResourceIdentifier;
import simulator.Simulator;
import simulator.Event;
import simulator.SystemState;

public class InputEventCellEditor implements mxICellEditor  {
	public int DEFAULT_MIN_WIDTH = 70;
	public int DEFAULT_MIN_HEIGHT = 30;
	public double DEFAULT_MINIMUM_EDITOR_SCALE = 1;
	private double x = 20;
	private double y = 20;

	protected double minimumEditorScale = DEFAULT_MINIMUM_EDITOR_SCALE;
	protected int minimumWidth = DEFAULT_MIN_WIDTH;
	protected int minimumHeight = DEFAULT_MIN_HEIGHT;

	private SimulatorWindow window;
	private Object editingCell;
	private EventObject trigger;
	private JComboBox<String> comboBox;
	private mxGraphComponent graphComponent;
	private Simulator simulator;
	private JComboBox<String> pulldownMenu;
	private Editor editor;
	private boolean bReflectingArchitectureModel = false;
	
	public InputEventCellEditor(SimulatorWindow window, mxGraphComponent graphComponent, Simulator simulator, Editor editor) {
		this.window = window;
		this.graphComponent = graphComponent;
		this.simulator = simulator;
		this.editor = editor;
	}

	@Override
	public Object getEditingCell() {
		return editingCell;
	}

	@Override
	public void startEditing(Object cell, EventObject evt) {
		if (editingCell != null) {
			stopEditing(true);
		}
		if (!graphComponent.getGraph().getModel().isEdge(cell)) {
			
			Resource res = simulator.getCurState().getResource((String) ((mxCell) cell).getValue());
			ResourceIdentifier resId = res.getResourceIdentifier();//resource
			ArrayList<DataTransferChannel> eventChs = new ArrayList<>();//iOchannelList
			ArrayList<String> messages = new ArrayList<>();//ADLmessage
			ArrayList<Expression> eventMessages = new ArrayList<>();//messageList
			ResourcePath eventResPath = null;

			for (Channel ch: simulator.getModel().getInputChannels()) {//all channel
				eventResPath = getSelectableMessages(ch, resId, eventChs, messages, eventMessages, eventResPath);
			}
			    
			if(messages.isEmpty()) {
				return;
			}
			String[] eventList = messages.toArray(new String[messages.size()]);
			JComboBox<String> event = new JComboBox<String>(eventList);
			
			JPanel eventChoice = new JPanel();
			eventChoice.add(event);//FirstEventChoice
			
			int ret = JOptionPane.showConfirmDialog(window, eventChoice, "Event Choice", JOptionPane.OK_CANCEL_OPTION);
			if (ret == JOptionPane.OK_OPTION) {
				
				JPanel inputEvent = new JPanel();
				int i , eventNum;
				i = eventNum = 0;
				
				for(String eventString : eventList) {
					if(eventString.equals(event.getSelectedItem().toString())) {
						eventNum = i;
					}
					i++;
				}
				
				JTextArea textArea = new JTextArea(eventMessages.get(eventNum).toString(), 10, 30);//EventInput
				inputEvent.add(textArea);
			
				int approve = JOptionPane.showConfirmDialog(window, inputEvent, "Event Code", JOptionPane.OK_CANCEL_OPTION);
				if (approve == JOptionPane.OK_OPTION) {
					try {
					
					TokenStream stream = new Parser.TokenStream();
					Parser parser = new Parser(stream);
					stream.addLine(textArea.getText());
					Expression eventMessage = parser.parseTerm(stream,simulator.getModel());
					
					Event newEvent = new Event(eventChs.get(eventNum), eventMessage, eventResPath, simulator.getCurState().getResource(resId)); 
					simulator.transition(newEvent);

//					SimulationLayout layout =  new SimulationLayout(simulator.getCurState());
//					layout.constructSimulateGraph(graph, simulator);

					graphComponent.setCellEditor(new InputEventCellEditor(window, graphComponent, simulator, this.editor));
					
					} catch (ParameterizedIdentifierIsFutureWork | ResolvingMultipleDefinitionIsFutureWork
							| InvalidMessage | UnificationFailed | ValueUndefined | ExpectedRightBracket | WrongJsonExpression | ExpectedColon | ExpectedDoubleQuotation e) {
						e.printStackTrace();
					}
				}
			}
			//resource
		return;
		}

		mxCellState state = graphComponent.getGraph().getView().getState(cell);
		if (state != null && state.getLabel() != null && !state.getLabel().equals("")) {
			editingCell = cell;
			trigger = evt;

			double scale = Math.max(minimumEditorScale, graphComponent.getGraph().getView().getScale());
			Object value = graphComponent.getGraph().getModel().getValue(cell);
			if (value != null && value instanceof PushPullAttribute) {
				PushPullAttribute attr = (PushPullAttribute) value;
				comboBox = new JComboBox<>(attr.getOptionStrings());
				comboBox.setBorder(BorderFactory.createEmptyBorder());
				comboBox.setOpaque(false);
				comboBox.setBounds(getEditorBounds(state, scale));
				comboBox.setVisible(true);
				graphComponent.getGraphControl().add(comboBox, 0);
				comboBox.updateUI();
			}
		}
	}

	private ResourcePath getSelectableMessages(Channel ch, ResourceIdentifier resId,
			ArrayList<DataTransferChannel> eventChs, ArrayList<String> messages, ArrayList<Expression> eventMessages,
			ResourcePath eventResPath) {
		if (((DataTransferChannel)ch).getInputResources().size() == 0) {//ioch Or normalch
			for (ChannelMember out: ((DataTransferChannel)ch).getOutputChannelMembers()) {
				ResourcePath resPath = out.getResource();
				if (resId.isInstanceOf(resPath)) {//account.uid == acounts.123
					eventResPath = resPath;
					eventChs.add(((DataTransferChannel)ch));
					String message = null;
					Expression mesExp = out.getStateTransition().getMessageExpression();//sync(a,b)
					eventMessages.add(mesExp);
					if (mesExp instanceof Term) {
						message = ((Term) mesExp).getSymbol().toString();//sync
					} else if(mesExp instanceof Variable) {
						message = ((Variable) mesExp).getName();//x,y,z..
					}
					messages.add(message);//pulldown 
				}
			}
			for (Channel childCh: ch.getChildren()) {
				eventResPath = getSelectableMessages(childCh, resId, eventChs, messages, eventMessages, eventResPath);
			}
		}
		return eventResPath;
	}

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

	@Override
	public void stopEditing(boolean cancel) {
		if (editingCell != null) {
			comboBox.transferFocusUpCycle();
			Object cell = editingCell;
			editingCell = null;
			if (!cancel) {
				EventObject trig = trigger;
				trigger = null;
				Object value = graphComponent.getGraph().getModel().getValue(cell);
				if (value != null && value instanceof PushPullAttribute) {
					PushPullAttribute attr = (PushPullAttribute) value;
					List<PushPullValue> options = attr.getOptions();
					PushPullValue selected = null;
					for (PushPullValue option: options) {
						if (option.toString().equals(getCurrentValue())) {
							selected = option;
							break;
						}
					}
					if (selected != null) {
						options.remove(selected);
						options.add(0, selected);
					}
					graphComponent.labelChanged(cell, attr, trig);
				}
			} else {
				mxCellState state = graphComponent.getGraph().getView().getState(cell);
				graphComponent.redraw(state);
			}

			if (comboBox.getParent() != null) {
				comboBox.setVisible(false);
				comboBox.getParent().remove(comboBox);
			}

			graphComponent.requestFocusInWindow();
		}
	}

	public String getCurrentValue() {
		return (String) comboBox.getSelectedItem();
	}

	/**
	 * Returns the bounds to be used for the editor.
	 */
	public Rectangle getEditorBounds(mxCellState state, double scale) {
		mxIGraphModel model = state.getView().getGraph().getModel();
		Rectangle bounds = null;

		bounds = state.getLabelBounds().getRectangle();
		bounds.height += 10;

		// Applies the horizontal and vertical label positions
		if (model.isVertex(state.getCell())) {
			String horizontal = mxUtils.getString(state.getStyle(), mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

			if (horizontal.equals(mxConstants.ALIGN_LEFT)) {
				bounds.x -= state.getWidth();
			} else if (horizontal.equals(mxConstants.ALIGN_RIGHT)) {
				bounds.x += state.getWidth();
			}

			String vertical = mxUtils.getString(state.getStyle(),
					mxConstants.STYLE_VERTICAL_LABEL_POSITION,
					mxConstants.ALIGN_MIDDLE);

			if (vertical.equals(mxConstants.ALIGN_TOP)) {
				bounds.y -= state.getHeight();
			} else if (vertical.equals(mxConstants.ALIGN_BOTTOM)) {
				bounds.y += state.getHeight();
			}
		}

		bounds.setSize(
				(int) Math.max(bounds.getWidth(),
						Math.round(minimumWidth * scale)),
				(int) Math.max(bounds.getHeight(),
						Math.round(minimumHeight * scale)));

		return bounds;
	}

}
