package algorithm.example;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.text.NumberFormatter;

import api.AddOn;
import classes.AbstractCanvasObject;
import classes.GroupNode;
import classes.HolonElement;
import classes.HolonObject;
import classes.HolonSwitch;
import model.DecoratedGroupNode;
import model.Model;
import ui.controller.Control;

public class DemoAlgo implements AddOn {

	//Parameter for Algo with default Values:
		
		//Settings For GroupNode using and cancel
		private boolean useGroupNode = false;
		private DecoratedGroupNode dGroupNode = null;
		private boolean cancel = false;


		//Parameter defined by Algo
		private HashMap<Integer, AccessWrapper> access;
		private List<Boolean> initialState;
		private List<HolonSwitch> switchList;
		private List<HolonObject> objectList;
		
		//Gui Part:
		private Control  control;
		private JTextArea textArea;
		private JPanel content = new JPanel();
		private long startTime;
		private Thread runThread;
		//Windrad
		private HolonObject windrad;
		
		
		private int waitDurationWindradStep = 400;
		private int waitDurationEnd = 1000;

		int counter;
		
		
		public static void main(String[] args)
		{
		      JFrame newFrame = new JFrame("exampleWindow");
		      DemoAlgo instance = new DemoAlgo();
		      newFrame.setContentPane(instance.getPanel());
		      newFrame.pack();
		      newFrame.setVisible(true);
		      newFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		}
		public DemoAlgo() {
			content.setLayout(new BorderLayout());
		
			textArea = new JTextArea();
			textArea.setEditable(false);
			JScrollPane scrollPane = new JScrollPane(textArea);
			JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
					createOptionPanel() , scrollPane);
			splitPane.setResizeWeight(0.0);
			content.add(splitPane, BorderLayout.CENTER);
			content.setPreferredSize(new Dimension(800,800));	
		}
		public JPanel createOptionPanel() {
			JPanel optionPanel = new JPanel(new BorderLayout());
			JScrollPane scrollPane = new JScrollPane(createParameterPanel());
			scrollPane.setBorder(BorderFactory.createTitledBorder("Parameter"));
			optionPanel.add(scrollPane,  BorderLayout.CENTER);
			optionPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
			return optionPanel;
		}
		
		private Component createParameterPanel() {
			JPanel parameterPanel = new JPanel(null);
			parameterPanel.setPreferredSize(new Dimension(510,300));
			
			
		
//			JLabel showDiagnosticsLabel = new JLabel("Set all switches closed:");
//			showDiagnosticsLabel.setBounds(200, 60, 170, 20);
//			parameterPanel.add(showDiagnosticsLabel);		
		
			
			JPanel borderPanel = new JPanel(null);
			borderPanel.setBounds(200, 85, 185, 50);
			borderPanel.setBorder(BorderFactory.createTitledBorder(""));
			parameterPanel.add(borderPanel);	
			
			JLabel showGroupNodeLabel = new JLabel("Use Group Node:");
			showGroupNodeLabel.setBounds(10, 1, 170, 20);
			borderPanel.add(showGroupNodeLabel);	
			
			JButton selectGroupNodeButton = new JButton("Select GroupNode");
			selectGroupNodeButton.setEnabled(false);
			selectGroupNodeButton.setBounds(10, 25, 165, 20);
			selectGroupNodeButton.addActionListener(actionEvent -> selectGroupNode());
			borderPanel.add(selectGroupNodeButton);	
			
			JCheckBox useGroupNodeCheckBox = new JCheckBox();
			useGroupNodeCheckBox.setSelected(false);
			useGroupNodeCheckBox.setBounds(155, 1, 25, 20);
			useGroupNodeCheckBox.addActionListener(actionEvent -> {
				useGroupNode = useGroupNodeCheckBox.isSelected();
				selectGroupNodeButton.setEnabled(useGroupNode);
			});
			borderPanel.add(useGroupNodeCheckBox);
			
			
//			JCheckBox switchesCheckBox = new JCheckBox();
//			switchesCheckBox.setSelected(closeSwitches);
//			switchesCheckBox.setBounds(370, 60, 25, 20);
//			switchesCheckBox.addActionListener(actionEvent -> closeSwitches = switchesCheckBox.isSelected());
//			parameterPanel.add(switchesCheckBox);
			
			JButton selectRoom1Button = new JButton("Select");
			selectRoom1Button.setBounds(10,300, 90, 20);
			selectRoom1Button.addActionListener(actionEvent -> this.selectHolonObject());
			parameterPanel.add(selectRoom1Button);
			NumberFormat format = NumberFormat.getIntegerInstance();
			format.setGroupingUsed(false);
			format.setParseIntegerOnly(true);
			NumberFormatter integerFormatter = new NumberFormatter(format);
			integerFormatter.setMinimum(0);
			integerFormatter.setCommitsOnValidEdit(true);
			JLabel portLabel = new JLabel("between:");
			portLabel.setBounds(10, 330, 70, 30);
			parameterPanel.add(portLabel);
			JFormattedTextField betweenTF = new JFormattedTextField(integerFormatter);
			betweenTF.setText(""+waitDurationWindradStep);
			betweenTF.setBounds(80 ,330, 80, 30);
			betweenTF.addPropertyChangeListener(propertyChange ->{
				String text = betweenTF.getValue().toString();
				text = text.replaceAll("\\s", "");
				waitDurationWindradStep = Integer.parseInt((text));
			});
			parameterPanel.add(betweenTF);
			JLabel afterLabel = new JLabel("after:");
			afterLabel.setBounds(10, 360, 70, 30);
			parameterPanel.add(afterLabel);
			JFormattedTextField afterTF = new JFormattedTextField(integerFormatter);
			afterTF.setText(""+waitDurationEnd);
			afterTF.setBounds(80 ,360, 80, 30);
			afterTF.addPropertyChangeListener(propertyChange ->{
				String text = afterTF.getValue().toString();
				text = text.replaceAll("\\s", "");
				waitDurationEnd = Integer.parseInt((text));
			});
			parameterPanel.add(afterTF);
			
			
			return parameterPanel;
		}
		public JPanel createButtonPanel() {
			JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
			JButton cancelButton =  new JButton("Cancel Run");
			cancelButton.addActionListener(actionEvent -> cancel());
			buttonPanel.add(cancelButton);
			JButton clearButton =  new JButton("Clear Console");
			clearButton.addActionListener(actionEvent -> clear());
			buttonPanel.add(clearButton);
			JButton resetButton =  new JButton("Reset");
			resetButton.setToolTipText("Resets the State to before the Algorithm has runed.");
			resetButton.addActionListener(actionEvent -> reset());
			buttonPanel.add(resetButton);
			JButton runButton =  new JButton("Run");
			runButton.addActionListener(actionEvent -> {
				Runnable task = () -> run();
				runThread = new Thread(task);
				runThread.start();
			});
			buttonPanel.add(runButton);
			return buttonPanel;
		}
		private void cancel() {
			if(runThread.isAlive()) {
				println("");
				println("Cancel run.");
				cancel = true;
			} else {
				println("Nothing to cancel.");
			}
		}
		
		private void run() {
			cancel = false;
			disableGuiInput(true);
			startTimer();
			executeDemoAlgo();
			if(cancel) {
				reset();
				disableGuiInput(false);
				return;
			}
			printElapsedTime();
			disableGuiInput(false);
		}
		
		private void reset() {
			if(initialState != null) {
				println("Resetting..");
				resetState();
				updateVisual();
			}else {
				println("No run inistialized.");
			}
		}
		
		
		private void disableGuiInput(boolean bool) {
			control.guiDisable(bool);
		}
		
		
		

		
		
		@Override
		public JPanel getPanel() {
			return content;
		}
		@Override
		public void setController(Control control) {
			this.control = control;
			
		}
		private void clear() {
			textArea.setText("");
		}
		private void println(String message) {
			textArea.append(message  + "\n");
		}
		private void selectGroupNode() {
			Object[] possibilities = control.getSimManager().getActualVisualRepresentationalState().getCreatedGroupNodes().values().stream().map(aCps -> new Handle<DecoratedGroupNode>(aCps)).toArray();
			@SuppressWarnings("unchecked")
			Handle<DecoratedGroupNode> selected = (Handle<DecoratedGroupNode>) JOptionPane.showInputDialog(content, "Select GroupNode:", "GroupNode?",  JOptionPane.OK_OPTION,new ImageIcon(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)) , possibilities, "");
			if(selected != null) {
				println("Selected: " + selected);
				dGroupNode = selected.object;
			}
		}
		
		private void startTimer(){
			startTime = System.currentTimeMillis();
		}
		private void printElapsedTime(){
			long elapsedMilliSeconds = System.currentTimeMillis() - startTime;
			println("Execution Time of Algo in Milliseconds:" + elapsedMilliSeconds);
		}
		
		
		
		
		//Algo Part:
		
		private void executeDemoAlgo() {
			extractPositionAndAccess();
			counter = 0;
			int actualIteration = control.getModel().getCurIteration();
			deactivateWindrad();
			setAllSwitchesClosed();
			updateVisual();
			
			try {
				//Schalte Slow das Windrad Ein
				if(windrad == null)return;
				for(int i = 0; i< windrad.getNumberOfElements(); i++) {
					windrad.getElements().get(i).setActive(true);
					TimeUnit.MILLISECONDS.sleep(waitDurationWindradStep);
					updateVisual();
				}
				TimeUnit.MILLISECONDS.sleep(waitDurationEnd);
			} catch (InterruptedException e) {
			}
			
			setHolonElemntsAktiv(actualIteration);
			println("Changed Elements: " + counter);
			updateVisual();
		}

		
		

			
		
		private void deactivateWindrad() {
			if(windrad == null)return;
			windrad.getElements().stream().forEach(ele -> ele.setActive(false));
		}
		private void setHolonElemntsAktiv(int actualIteration) {
			for(int i = 0;i<access.size();i++) {
				AccessWrapper aw = access.get(i);
				if(aw.getState(actualIteration) ==false) counter++;
				if(aw.getType() == AccessWrapper.HOLONELEMENT) aw.setState(true);
			}
		}
		private void setAllSwitchesClosed() {
			for(HolonSwitch hSwitch : switchList) {
				if(hSwitch.getManualMode() == false) counter++;
				hSwitch.setManualMode(true);
				hSwitch.setManualState(true);
			}
			
		}
		/**
		 * Method to get the current Position alias a ListOf Booleans for aktive settings on the Objects on the Canvas.
		 * Also initialize the Access Hashmap to swap faster positions.
		 * @param model
		 * @return
		 */
		private List<Boolean> extractPositionAndAccess() {
			Model model = control.getModel();
			switchList = new ArrayList<HolonSwitch>();
			objectList = new ArrayList<HolonObject>();
			initialState = new ArrayList<Boolean>(); 
			access= new HashMap<Integer, AccessWrapper>();
			rollOutNodes((useGroupNode && (dGroupNode != null))? dGroupNode.getModel().getNodes() :model.getObjectsOnCanvas(), initialState, model.getCurIteration());
			return initialState;
		}
		/**
		 * Method to extract the Informations recursively out of the Model.
		 * @param nodes
		 * @param positionToInit
		 * @param timeStep
		 */
		private void rollOutNodes(List<AbstractCanvasObject> nodes, List<Boolean> positionToInit, int timeStep) {
			for(AbstractCanvasObject aCps : nodes) {
				if (aCps instanceof HolonObject) {
					for (HolonElement hE : ((HolonObject) aCps).getElements()) {
						positionToInit.add(hE.isActive());
						access.put(positionToInit.size() - 1 , new AccessWrapper(hE));
					}
					objectList.add((HolonObject) aCps);
				}
				else if (aCps instanceof HolonSwitch) {
					HolonSwitch sw = (HolonSwitch) aCps;
					positionToInit.add(sw.getState(timeStep));
					switchList.add(sw);
					access.put(positionToInit.size() - 1 , new AccessWrapper(sw));
				}
				else if(aCps instanceof GroupNode) {
					rollOutNodes(((GroupNode)aCps).getNodes(), positionToInit ,timeStep );
				}
			}
		}
		/**
		 * To let the User See the current state without touching the Canvas.
		 */
		private void updateVisual() {
			control.calculateStateAndVisualForCurrentTimeStep();
			control.updateCanvas();
			control.getGui().triggerUpdateController(null);
		}
		/**
		 * Sets the Model back to its original State before the LAST run.
		 */
		private void resetState() {
			setState(initialState);
		}
		
		/**
		 * Sets the State out of the given position for calculation or to show the user.
		 * @param position
		 */
		private void setState(List<Boolean> position) {
			for(int i = 0;i<position.size();i++) {
				access.get(i).setState(position.get(i));
			}
		}
		

		private void selectHolonObject() {
			List<HolonObject> holonObjectList = new ArrayList<HolonObject>();
			addObjectToList(control.getModel().getObjectsOnCanvas(),holonObjectList);
			Object[] possibilities = holonObjectList.stream().map(aCps -> new Handle<HolonObject>(aCps)).toArray();
			@SuppressWarnings("unchecked")
			Handle<HolonObject> selected = (Handle<HolonObject>) JOptionPane.showInputDialog(content, "Select HolonObject:", "HolonObject?",  JOptionPane.OK_OPTION,new ImageIcon(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)) , possibilities, "");
			if(selected != null) {
				//println("Selected: " + selected);
				windrad = selected.object;
			}
		}




		private void addObjectToList(List<AbstractCanvasObject> listToSearch, List<HolonObject> listToAdd){
			for (AbstractCanvasObject aCps : listToSearch) {
				if (aCps instanceof HolonObject) listToAdd.add((HolonObject) aCps);
				else if(aCps instanceof GroupNode) {
					addObjectToList(((GroupNode)aCps).getNodes(),listToAdd);
				}
			}
		}
		
		
		
		
		/**
		 * A Wrapper Class for Access HolonElement and HolonSwitch in one Element and not have to split the List.
		 */
		private class AccessWrapper {
			public static final int HOLONELEMENT = 0;
			public static final int SWITCH = 1;
			private int type;
			private HolonSwitch hSwitch;
			private HolonElement hElement;
			public AccessWrapper(HolonSwitch hSwitch){
				type = SWITCH;
				this.hSwitch = hSwitch;
			}
			public AccessWrapper(HolonElement hElement){
				type = HOLONELEMENT;
				this.hElement = hElement;
			}
			public void setState(boolean state) {
				if(type == HOLONELEMENT) {
					hElement.setActive(state);
				}else{//is switch
					hSwitch.setManualMode(true);
					hSwitch.setManualState(state);
				}
					
			}
			public boolean getState(int timeStep) {
				return (type == HOLONELEMENT)?hElement.isActive():hSwitch.getState(timeStep);
			}
			public int getType() {
				return type;
			}
		}
		
		
		
		
		private   class  Handle<T>{
			public T object;
			Handle(T object){
				this.object = object;
			}
			public String toString() {
				return object.toString();
			}
		}

}
