package simulator.interfaces.html;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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

public class HtmlPresenter implements INativeReceiver{

	public final String screenUpdateChannelName = "ScreenUpdate";
	public final String setVisibleChannelName = "SetVisible";
	public final String setTextChannelName = "SetText";
	public final String mouseEventChannelName = "MouseEvent";
	public final String textEventChannelName = "TextEvent";

	protected Simulator simulator;
	protected DataTransferChannel screenUpdateChannel;
	protected DataTransferChannel setVisibleChannel;
	protected DataTransferChannel mouseEventChannel;
	protected DataTransferChannel textEventChannel;
	protected DataTransferChannel setTextChannel;

	protected Map<DataTransferChannel, Map<String, Resource>> channelAndResourcesForReceiving = new HashMap<>();

	protected Map<String, HtmlElement> elements;

	protected IWebSocketMessageSender ws;
	

	public HtmlPresenter(Simulator simulator, IWebSocketMessageSender ws, Map<String, HtmlElement> elems) {
		this.simulator = simulator;
		this.ws = ws;
		screenUpdateChannel = (DataTransferChannel) simulator.getModel().getChannel(screenUpdateChannelName);
		setVisibleChannel = (DataTransferChannel) simulator.getModel().getChannel(setVisibleChannelName);
		mouseEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(mouseEventChannelName);
		setTextChannel = (DataTransferChannel) simulator.getModel().getChannel(setTextChannelName);
		textEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(textEventChannelName);
		simulator.addNativeReceiver(this, screenUpdateChannel);
		elements = elems;
		
		initScreen();
	}
	
	private void initScreen() {
		Expression initScExp = simulator.getCurState().getResource("screen").getState().getValue();
		if (initScExp instanceof JsonTerm) {
			JsonTerm sendData = new JsonTerm();
			sendData.addMember("\"method\"", new Constant("\"updateHtml\""));
			JsonTerm sendElements = new JsonTerm();
			ListTerm addElements = new ListTerm();
			ListTerm delElements = new ListTerm();

			JsonTerm nextSc = (JsonTerm) initScExp;
			Expression newWidgets = nextSc.get("widgets");

			if (newWidgets instanceof MapTerm) {

				Set<String> newWidSet = new HashSet<>(((MapTerm) newWidgets).keySet());

				if (!newWidSet.isEmpty()) {
					addNewWidgets(newWidSet, simulator.getCurState().getResource("screen"), newWidgets, addElements);
				}
			}

			sendElements.addMember("\"add\"", addElements);
			sendElements.addMember("\"delete\"", delElements);
			sendData.addMember("\"data\"", sendElements);
			ws.send(sendData.toString());
		}
	}

	@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 sendData = new JsonTerm();
				sendData.addMember("\"method\"", new Constant("\"updateHtml\""));
				JsonTerm sendElements = new JsonTerm();
				ListTerm addElements = new ListTerm();
				ListTerm delElements = new ListTerm();

				JsonTerm curSc = (JsonTerm) curScExp;
				JsonTerm nextSc = (JsonTerm) nextScExp;
				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()) {
						deleteOldWidgets(oldWidSet, delElements);
					}

					if (!newWidSet.isEmpty()) {
						addNewWidgets(newWidSet, nextSystemState, event.getInputResource().getResourceIdentifier(), newWidgets, addElements);
					}
				}

				sendElements.addMember("\"add\"", addElements);
				sendElements.addMember("\"delete\"", delElements);
				sendData.addMember("\"data\"", sendElements);
				ws.send(sendData.toString());
			}
		}
	}


	private void addNewWidgets(Set<String> newWidSet, Resource screenResource, Expression newWidgets, ListTerm addElements) {
		
		
		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");
				switch(type.toString()) {
				case "\"button\"":
					addButton(widget, newWid, addElements, widgetResource);
					break;
				case "\"label\"":
					addLabel(widget, newWid, addElements, widgetResource);
					break;
				case "\"textInput\"":
					addTextInput(widget, newWid, addElements, widgetResource);
					break;
				}
			}
		}

	}
	
	private void addNewWidgets(Set<String> newWidSet, SystemState nextSystemState, ResourceIdentifier ri, Expression newWidgets, ListTerm addElements) {
		Resource screenResource = nextSystemState.getResource(ri);
		addNewWidgets(newWidSet, screenResource, newWidgets, addElements);
	}

	private void addButton(JsonTerm widget, String wid, ListTerm addElements, Resource widgetResource) {
		String text = widget.get("text").toString();
		HtmlElement button = new HtmlElement("button", wid, text);
		
		Set<String> keySet = widget.keySet();
		button.setStyle("\"position\"", "\"absolute\"");
		if(keySet.contains("x")) {
			button.setStyle("\"left\"", "\"" + widget.get("x") + "px\"");
		}
		if(keySet.contains("y")) {
			button.setStyle("\"top\"", "\"" + widget.get("y") + "px\"");
		}
		if(keySet.contains("width")) {
			button.setStyle("\"width\"", "\"" + widget.get("width") + "px\"");
		}
		if(keySet.contains("height")) {
			button.setStyle("\"height\"", "\"" + widget.get("height") + "px\"");
		}
		
		//ボタンが押されたり離されたりした時を検知
		ResourcePath resPath = mouseEventChannel.getOutputResources().iterator().next();
		button.setRestEventListener(new HtmlElementMouseSender(simulator, mouseEventChannel, resPath, widgetResource));
		
		//ボタンが透明になったことを通知
		HtmlElementVisibilityReceiver nativeVisibilityReceiver = new HtmlElementVisibilityReceiver(button, ws);
		simulator.addNativeReceiver(nativeVisibilityReceiver, setVisibleChannel, widgetResource);
		Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setVisibleChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		//ボタンの文字が変わったことを通知
		HtmlElementTextReceiver nativeTextReceiver = new HtmlElementTextReceiver(button, ws);
		simulator.addNativeReceiver(nativeTextReceiver, setTextChannel, widgetResource);
		resources = channelAndResourcesForReceiving.get(setTextChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setTextChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		elements.put(wid, button);
		addElements.append(button.toMap());
	}
	
	private void addLabel(JsonTerm widget, String wid, ListTerm addElements, Resource widgetResource) {
		String text = widget.get("text").toString();
		HtmlElement label = new HtmlElement("label", wid, text);
		
		Set<String> keySet = widget.keySet();
		label.setStyle("\"position\"", "\"absolute\"");
		if(keySet.contains("x")) {
			label.setStyle("\"left\"", "\"" + widget.get("x") + "px\"");
		}
		if(keySet.contains("y")) {
			label.setStyle("\"top\"", "\"" + widget.get("y") + "px\"");
		}
		if(keySet.contains("width")) {
			label.setStyle("\"width\"", "\"" + widget.get("width") + "px\"");
		}
		if(keySet.contains("height")) {
			label.setStyle("\"height\"", "\"" + widget.get("height") + "px\"");
		}
		
		//ボタンが透明になったことを通知
		HtmlElementVisibilityReceiver nativeVisibilityReceiver = new HtmlElementVisibilityReceiver(label, ws);
		simulator.addNativeReceiver(nativeVisibilityReceiver, setVisibleChannel, widgetResource);
		Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setVisibleChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		//ボタンの文字が変わったことを通知
		HtmlElementTextReceiver nativeTextReceiver = new HtmlElementTextReceiver(label, ws);
		simulator.addNativeReceiver(nativeTextReceiver, setTextChannel, widgetResource);
		resources = channelAndResourcesForReceiving.get(setTextChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setTextChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		elements.put(wid, label);
		addElements.append(label.toMap());
	}
	
	private void addTextInput(JsonTerm widget, String wid, ListTerm addElements, Resource widgetResource) {
		String text = widget.get("text").toString();
		HtmlElement textInput = new HtmlElement("textInput", wid, text);
		
		Set<String> keySet = widget.keySet();
		textInput.setStyle("\"position\"", "\"absolute\"");
		if(keySet.contains("x")) {
			textInput.setStyle("\"left\"", "\"" + widget.get("x") + "px\"");
		}
		if(keySet.contains("y")) {
			textInput.setStyle("\"top\"", "\"" + widget.get("y") + "px\"");
		}
		if(keySet.contains("width")) {
			textInput.setStyle("\"width\"", "\"" + widget.get("width") + "px\"");
		}
		if(keySet.contains("height")) {
			textInput.setStyle("\"height\"", "\"" + widget.get("height") + "px\"");
		}
		
		//文字の変更を検知
		ResourcePath resPath = textEventChannel.getOutputResources().iterator().next();
		textInput.setRestEventListener(new HtmlElementTextSender(simulator, textEventChannel, resPath, widgetResource));
		
		//ボタンが透明になったことを通知
		HtmlElementVisibilityReceiver nativeVisibilityReceiver = new HtmlElementVisibilityReceiver(textInput, ws);
		simulator.addNativeReceiver(nativeVisibilityReceiver, setVisibleChannel, widgetResource);
		Map<String, Resource> resources = channelAndResourcesForReceiving.get(setVisibleChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setVisibleChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		//ボタンの文字が変わったことを通知
		HtmlElementTextReceiver nativeTextReceiver = new HtmlElementTextReceiver(textInput, ws);
		simulator.addNativeReceiver(nativeTextReceiver, setTextChannel, widgetResource);
		resources = channelAndResourcesForReceiving.get(setTextChannel);
		if (resources == null) {
			resources = new HashMap<>();
			channelAndResourcesForReceiving.put(setTextChannel, resources);
		}
		resources.put(wid, widgetResource);
		
		elements.put(wid, textInput);
		addElements.append(textInput.toMap());
	}
	
	private void deleteOldWidgets(Set<String> oldWidSet, ListTerm delElements) {
		for (String oldWid : oldWidSet) {
			elements.remove(oldWid);
			//TODO
			// del widgets
			delElements.append(new Constant(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);
				}
			}
		}
	}

}
