package simulator.interfaces.swing;

import java.awt.Component;
import java.awt.FlowLayout;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Term;
import models.dataConstraintModel.JsonTerm;
import models.dataConstraintModel.MapTerm;
import models.dataConstraintModel.ResourcePath;
import models.dataFlowModel.DataTransferChannel;
import simulator.Event;
import simulator.Resource;
import simulator.Simulator;
import simulator.SystemState;
import simulator.interfaces.INativeReceiver;

public class SwingPresenter implements INativeReceiver {
	// Native channel names.
	public final String screenUpdateChannelName = "ScreenUpdate";
	public final String setLayoutChannelName = "SetLayout";
	public final String setVisibleChannelName = "SetVisible";
	public final String setTextChannelName = "SetText";
	public final String setXChannelName = "SetX";
	public final String setYChannelName = "SetY";
	public final String setWidthChannelName = "SetWidth";
	public final String setHeightChannelName = "SetHeight";
	public final String mouseEventChannelName = "MouseEvent";
	public final String textEventChannelName = "TextEvent";
	
	protected JPanel mainPanel;
	protected Simulator simulator;
	protected Map<String, Component> components;
	
	// Native channels.
	protected DataTransferChannel screenUpdateChannel;
	protected DataTransferChannel setLayoutChannel;
	protected DataTransferChannel setVisibleChannel;
	protected DataTransferChannel setTextChannel;
	protected DataTransferChannel setXChannel;
	protected DataTransferChannel setYChannel;
	protected DataTransferChannel setWidthChannel;
	protected DataTransferChannel setHeightChannel;
	protected DataTransferChannel mouseEventChannel;
	protected DataTransferChannel textEventChannel;
	
	protected Map<DataTransferChannel, Map<String, Resource>> channelAndResourcesForReceiving = new HashMap<>();
	
	public SwingPresenter(JPanel mainPanel, Simulator simulator) {
		this.mainPanel = mainPanel;
		this.simulator = simulator;
		components = new HashMap<>();
		screenUpdateChannel = (DataTransferChannel) simulator.getModel().getChannel(screenUpdateChannelName);
		setLayoutChannel = (DataTransferChannel) simulator.getModel().getChannel(setLayoutChannelName);
		setVisibleChannel = (DataTransferChannel) simulator.getModel().getChannel(setVisibleChannelName);
		setTextChannel = (DataTransferChannel) simulator.getModel().getChannel(setTextChannelName);
		setXChannel = (DataTransferChannel) simulator.getModel().getChannel(setXChannelName);
		setYChannel = (DataTransferChannel) simulator.getModel().getChannel(setYChannelName);
		setWidthChannel = (DataTransferChannel) simulator.getModel().getChannel(setWidthChannelName);
		setHeightChannel = (DataTransferChannel) simulator.getModel().getChannel(setHeightChannelName);
		mouseEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(mouseEventChannelName);
		textEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(textEventChannelName);
		simulator.addNativeReceiver(this, screenUpdateChannel);
		simulator.addNativeReceiver(new SwingLayout(), setLayoutChannel);
	}

	@Override
	public void onReceiveFromModel(Event event, SystemState nextSystemState) {
		Expression message = event.getMessage();
		if (message instanceof Term && ((Term) message).getChildren().size() >= 2) {
			Expression curScExp = ((Term) message).getChild(0);
			Expression nextScExp = ((Term) message).getChild(1);
			if (curScExp instanceof JsonTerm && nextScExp instanceof JsonTerm) {
				JsonTerm curSc = (JsonTerm) curScExp;
				JsonTerm nextSc = (JsonTerm) nextScExp;
				Expression newLayout = nextSc.get("layout");
				if (newLayout != null) {
					if (newLayout.toString().equals("true")) {
						mainPanel.setLayout(new FlowLayout());
					} else if (newLayout.toString().equals("false")) {
						mainPanel.setLayout(null);
					}
				}
				Expression oldWidgets = curSc.get("widgets");
				Expression newWidgets = nextSc.get("widgets");
				if (oldWidgets instanceof MapTerm && newWidgets instanceof MapTerm) {
					Set<String> oldWidSet = new HashSet<>(((MapTerm) oldWidgets).keySet());
					Set<String> newWidSet = new HashSet<>(((MapTerm) newWidgets).keySet());
					oldWidSet.removeAll(((MapTerm) newWidgets).keySet());
					newWidSet.removeAll(((MapTerm) oldWidgets).keySet());
					if (!oldWidSet.isEmpty() || !newWidSet.isEmpty()) {
						// If the set of screen components is changed.
						
						// Remove old components and their native receivers.
						for (String oldWid: oldWidSet) {
							mainPanel.remove(components.get(oldWid));
						}
						
						for (DataTransferChannel channel: channelAndResourcesForReceiving.keySet()) {
							Map<String, Resource> widToResource = channelAndResourcesForReceiving.get(channel);
							for (String oldWid: oldWidSet) {
								Resource resource = widToResource.remove(oldWid);
								if (resource != null) {
									simulator.removeNativeReceiver(channel, resource);
								}
							}
						}
						
						// Add new swing components.
						Resource screenResource = nextSystemState.getResource(event.getInputResource().getResourceIdentifier());
						Resource widgetsResource = screenResource.getChildrenMap().get("widgets");
						for (String newWid: newWidSet) {
							Expression value = ((MapTerm) newWidgets).get(newWid);
							if (value instanceof JsonTerm) {
								JsonTerm widget = (JsonTerm) value;
								Resource widgetResource = widgetsResource.getChildrenMap().get(newWid);
								Expression type = widget.get("type");
								if (type.toString().equals("\"button\"")) {
									// Add a button component.
									Expression text = widget.get("text");
									Expression x = widget.get("x");
									Expression y = widget.get("y");
									Expression width = widget.get("width");
									Expression height = widget.get("height");
									JButton button = new JButton(text.toString().replace("\"", ""));
									if (x != null && y != null) {
										button.setLocation(Integer.parseInt(x.toString()), Integer.parseInt(y.toString()));
									}
									if (width != null && height != null) {
										button.setSize(Integer.parseInt(width.toString()), Integer.parseInt(height.toString()));
									}
									mainPanel.add(button);
									components.put(newWid, button);
									// Connect swing component and model.
									ResourcePath resPath = mouseEventChannel.getOutputResources().iterator().next();
									button.addMouseListener(new ComponentMouseSender(simulator, mouseEventChannel, resPath, widgetResource));	// button => widgetResource
									
									ComponentVisibilityReceiver nativeVisibilityReceiver = new ComponentVisibilityReceiver(button);				// widgetResource => button
									simulator.addNativeReceiver(nativeVisibilityReceiver, setVisibleChannel, widgetResource);
									Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
									if (resources == null) {
										resources = new HashMap<>();
										channelAndResourcesForReceiving.put(setVisibleChannel, resources);
									}
									resources.put(newWid, widgetResource);
									
									ComponentTextReceiver nativeTextReceiver = new ComponentTextReceiver(button);								// widgetResource => button
									simulator.addNativeReceiver(nativeTextReceiver, setTextChannel, widgetResource);
									resources = channelAndResourcesForReceiving.get(setTextChannel);
									if (resources == null) {
										resources = new HashMap<>();
										channelAndResourcesForReceiving.put(setTextChannel, resources);
									}
									resources.put(newWid, widgetResource);
									
									Resource widgetXResource = widgetResource.getChildrenMap().get("x");
									if (widgetXResource != null) {
										ComponentXReceiver nativeXReceiver = new ComponentXReceiver(button);									// widgetResource => button
										simulator.addNativeReceiver(nativeXReceiver, setXChannel, widgetXResource);
										resources = channelAndResourcesForReceiving.get(setXChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setXChannel, resources);
										}
										resources.put(newWid, widgetXResource);
									}
									
									Resource widgetYResource = widgetResource.getChildrenMap().get("y");
									if (widgetYResource != null) {
										ComponentYReceiver nativeYReceiver = new ComponentYReceiver(button);									// widgetResource => button
										simulator.addNativeReceiver(nativeYReceiver, setYChannel, widgetYResource);
										resources = channelAndResourcesForReceiving.get(setYChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setYChannel, resources);
										}
										resources.put(newWid, widgetYResource);
									}
									
									Resource widgetWidthResource = widgetResource.getChildrenMap().get("width");
									if (widgetWidthResource != null) {
										ComponentWidthReceiver nativeWidthReceiver = new ComponentWidthReceiver(button);						// widgetResource => button
										simulator.addNativeReceiver(nativeWidthReceiver, setWidthChannel, widgetWidthResource);
										resources = channelAndResourcesForReceiving.get(setWidthChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setWidthChannel, resources);
										}
										resources.put(newWid, widgetWidthResource);
									}
									
									Resource widgetHeightResource = widgetResource.getChildrenMap().get("height");
									if (widgetHeightResource != null) {
										ComponentHeightReceiver nativeHeightReceiver = new ComponentHeightReceiver(button);						// widgetResource => button
										simulator.addNativeReceiver(nativeHeightReceiver, setHeightChannel, widgetHeightResource);
										resources = channelAndResourcesForReceiving.get(setHeightChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setHeightChannel, resources);
										}
										resources.put(newWid, widgetHeightResource);
									}
								} else if (type.toString().equals("\"label\"")) {
									// Add a label component.
									Expression text = widget.get("text");
									Expression x = widget.get("x");
									Expression y = widget.get("y");
									Expression width = widget.get("width");
									Expression height = widget.get("height");
									JLabel label = new JLabel(text.toString().replace("\"", ""));
									if (x != null && y != null) {
										label.setLocation(Integer.parseInt(x.toString()), Integer.parseInt(y.toString()));
									}
									if (width != null && height != null) {
										label.setSize(Integer.parseInt(width.toString()), Integer.parseInt(height.toString()));
									}
									mainPanel.add(label);
									components.put(newWid, label);
									
									// Connect swing component and model.
									ComponentVisibilityReceiver nativeVisibilityReceiver = new ComponentVisibilityReceiver(label);				// widgetResource => label
									simulator.addNativeReceiver(nativeVisibilityReceiver, setVisibleChannel, widgetResource);
									Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
									if (resources == null) {
										resources = new HashMap<>();
										channelAndResourcesForReceiving.put(setVisibleChannel, resources);
									}
									resources.put(newWid, widgetResource);
									
									ComponentTextReceiver nativeTextReceiver = new ComponentTextReceiver(label);								// widgetResource => label
									simulator.addNativeReceiver(nativeTextReceiver, setTextChannel, widgetResource);
									resources = channelAndResourcesForReceiving.get(setTextChannel);
									if (resources == null) {
										resources = new HashMap<>();
										channelAndResourcesForReceiving.put(setTextChannel, resources);
									}
									resources.put(newWid, widgetResource);
									
									Resource widgetXResource = widgetResource.getChildrenMap().get("x");
									if (widgetXResource != null) {
										ComponentXReceiver nativeXReceiver = new ComponentXReceiver(label);										// widgetResource => label
										simulator.addNativeReceiver(nativeXReceiver, setXChannel, widgetXResource);
										resources = channelAndResourcesForReceiving.get(setXChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setXChannel, resources);
										}
										resources.put(newWid, widgetXResource);
									}
									
									Resource widgetYResource = widgetResource.getChildrenMap().get("y");
									if (widgetYResource != null) {
										ComponentYReceiver nativeYReceiver = new ComponentYReceiver(label);										// widgetResource => label
										simulator.addNativeReceiver(nativeYReceiver, setYChannel, widgetYResource);
										resources = channelAndResourcesForReceiving.get(setYChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setYChannel, resources);
										}
										resources.put(newWid, widgetYResource);
									}
									
									Resource widgetWidthResource = widgetResource.getChildrenMap().get("width");
									if (widgetWidthResource != null) {
										ComponentWidthReceiver nativeWidthReceiver = new ComponentWidthReceiver(label);							// widgetResource => label
										simulator.addNativeReceiver(nativeWidthReceiver, setWidthChannel, widgetWidthResource);
										resources = channelAndResourcesForReceiving.get(setWidthChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setWidthChannel, resources);
										}
										resources.put(newWid, widgetWidthResource);
									}
									
									Resource widgetHeightResource = widgetResource.getChildrenMap().get("height");
									if (widgetHeightResource != null) {
										ComponentHeightReceiver nativeHeightReceiver = new ComponentHeightReceiver(label);						// widgetResource => label
										simulator.addNativeReceiver(nativeHeightReceiver, setHeightChannel, widgetHeightResource);
										resources = channelAndResourcesForReceiving.get(setHeightChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setHeightChannel, resources);
										}
										resources.put(newWid, widgetHeightResource);
									}
								} else if (type.toString().equals("\"textInput\"")) {
									// Add a text input component.
									Expression x = widget.get("x");
									Expression y = widget.get("y");
									Expression width = widget.get("width");
									Expression height = widget.get("height");
									JTextField textField = new JTextField(10);
									if (x != null && y != null) {
										textField.setLocation(Integer.parseInt(x.toString()), Integer.parseInt(y.toString()));
									}
									if (width != null && height != null) {
										textField.setSize(Integer.parseInt(width.toString()), Integer.parseInt(height.toString()));
									}
									mainPanel.add(textField);
									components.put(newWid, textField);
									// Connect swing component and model.
									ResourcePath resPath = textEventChannel.getOutputResources().iterator().next();
									textField.getDocument().addDocumentListener(new ComponentTextSender(simulator, textEventChannel, resPath, widgetResource));	// textField => widgetResource
									
									ComponentVisibilityReceiver nativeReceiver = new ComponentVisibilityReceiver(textField);					// widgetResource => textField
									simulator.addNativeReceiver(nativeReceiver, setVisibleChannel, widgetResource);
									Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
									if (resources == null) {
										resources = new HashMap<>();
										channelAndResourcesForReceiving.put(setVisibleChannel, resources);
									}
									resources.put(newWid, widgetResource);
									
									Resource widgetXResource = widgetResource.getChildrenMap().get("x");
									if (widgetXResource != null) {
										ComponentXReceiver nativeXReceiver = new ComponentXReceiver(textField);									// widgetResource => textField
										simulator.addNativeReceiver(nativeXReceiver, setXChannel, widgetXResource);
										resources = channelAndResourcesForReceiving.get(setXChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setXChannel, resources);
										}
										resources.put(newWid, widgetXResource);
									}
									
									Resource widgetYResource = widgetResource.getChildrenMap().get("y");
									if (widgetYResource != null) {
										ComponentYReceiver nativeYReceiver = new ComponentYReceiver(textField);									// widgetResource => textField
										simulator.addNativeReceiver(nativeYReceiver, setYChannel, widgetYResource);
										resources = channelAndResourcesForReceiving.get(setYChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setYChannel, resources);
										}
										resources.put(newWid, widgetYResource);
									}
									
									Resource widgetWidthResource = widgetResource.getChildrenMap().get("width");
									if (widgetWidthResource != null) {
										ComponentWidthReceiver nativeWidthReceiver = new ComponentWidthReceiver(textField);						// widgetResource => textField
										simulator.addNativeReceiver(nativeWidthReceiver, setWidthChannel, widgetWidthResource);
										resources = channelAndResourcesForReceiving.get(setWidthChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setWidthChannel, resources);
										}
										resources.put(newWid, widgetWidthResource);
									}
									
									Resource widgetHeightResource = widgetResource.getChildrenMap().get("height");
									if (widgetHeightResource != null) {
										ComponentHeightReceiver nativeHeightReceiver = new ComponentHeightReceiver(textField);					// widgetResource => textField
										simulator.addNativeReceiver(nativeHeightReceiver, setHeightChannel, widgetHeightResource);
										resources = channelAndResourcesForReceiving.get(setHeightChannel);
										if (resources == null) {
											resources = new HashMap<>();
											channelAndResourcesForReceiving.put(setHeightChannel, resources);
										}
										resources.put(newWid, widgetHeightResource);
									}
								}
							}
						}
						mainPanel.invalidate();
						mainPanel.validate();
						mainPanel.repaint();
					}
				}
			}
		}
	}
	
	public class SwingLayout implements INativeReceiver {

		@Override
		public void onReceiveFromModel(Event event, SystemState nextSystemState) {
			Expression message = event.getMessage();
			if (message instanceof Term && ((Term) message).getChildren().size() >= 1) {
				Expression newLayout = ((Term) message).getChild(0);
				if (newLayout instanceof Constant) {
					if (newLayout.toString().equals("true")) {
						mainPanel.setLayout(new FlowLayout());
					} else if (newLayout.toString().equals("false")) {
						mainPanel.setLayout(null);
					}
				}
			}
		}
		
	}

}
