package Connection;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

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 ui.controller.Control;
import ui.view.Console;
/**
 * Easy Connection via Http Request. Repeat Request with a delay.
 * 
 * @author tom
 *
 */
public class ConnectPhysical implements AddOn{
	//Holeg
	private Control  control;
	
	
	
	
	//Gui
	private JPanel content = new JPanel();
	private Console console;

	private JLabel rotorLabel;
	private JLabel houseLabel;
	private boolean onlyOnChange = false;
	//
	Future<?> future;
	private boolean lessInformation = false;
	private int delay = 1000;
	JLabel warningLabel;
	
	public enum HolonObjectStatus{
		Connected , NotSelected, ObjectDeleted 
	}
	
	public class PhysicalLinkWrapper{
		public HolonObject hObject;
		public HolonObjectStatus status;
		public String postAddress;
		
		PhysicalLinkWrapper(HolonObject hObject, HolonObjectStatus status, String postAddress){
			this.hObject = hObject;
			this.status = status;
			this.postAddress = postAddress;
		}
	}
	
	
	//Object to look at
	PhysicalLinkWrapper rotor = new PhysicalLinkWrapper(null, HolonObjectStatus.NotSelected, "/rotor/");
	
	//Because House is special
	PhysicalLinkWrapper house = new PhysicalLinkWrapper(null, HolonObjectStatus.NotSelected, "notUsed");
	String room1Address = "/room1/";
	String room2Address = "/room2/";
	//the keywords are for the sepreation in 2 rooms
	String room1Keyword = "room1";
	String room2Keyword = "room2";


	//OnChange
	int oldValueRotor = -1;
	int oldValueRoom1 = -1;
	int oldValueRoom2 = -1;




	







	public static void main(String[] args)
	{
	      JFrame newFrame = new JFrame("exampleWindow");
	      ConnectPhysical instance = new ConnectPhysical();
	      newFrame.setContentPane(instance.getPanel());
	      newFrame.pack();
	      newFrame.setVisible(true);
	      newFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	
	
	
	
	public ConnectPhysical() {
		content.setLayout(new BorderLayout());
		console = new Console();
		JScrollPane scrollPane = new JScrollPane(console);
		JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
				createOptionPanel() , scrollPane);
		splitPane.setResizeWeight(0.0);
		content.add(splitPane, BorderLayout.CENTER);
		content.setPreferredSize(new Dimension(800,800));	
	}

	private Component createOptionPanel() {
		JPanel optionPanel = new JPanel(new BorderLayout());
		JScrollPane scrollPane = new JScrollPane(createParameterPanel());
		scrollPane.setBorder(BorderFactory.createTitledBorder("Settings"));
		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 lessInformationLabel = new JLabel("Less information in Console:");
		lessInformationLabel.setBounds(200, 180, 200, 20);
		parameterPanel.add(lessInformationLabel);	
		
		JCheckBox lessInformationCheckBox = new JCheckBox();
		lessInformationCheckBox.setSelected(false);
		lessInformationCheckBox.setBounds(400, 180, 25, 20);
		lessInformationCheckBox.addActionListener(actionEvent -> {
			lessInformation = lessInformationCheckBox.isSelected();
		});
		parameterPanel.add(lessInformationCheckBox);
		
		JLabel onlyOnChangeLabel = new JLabel("OnlyOnChange:");
		onlyOnChangeLabel.setBounds(200, 240, 200, 20);
		parameterPanel.add(onlyOnChangeLabel);	
		
		
		JCheckBox onlyOnChangeCheckBox = new JCheckBox();
		onlyOnChangeCheckBox.setSelected(false);
		onlyOnChangeCheckBox.setBounds(400, 240, 25, 20);
		onlyOnChangeCheckBox.addActionListener(actionEvent -> {
			onlyOnChange = onlyOnChangeCheckBox.isSelected();
		});
		parameterPanel.add(onlyOnChangeCheckBox);
		
		
		
		
		JLabel delayLabel = new JLabel("Delay:");
		delayLabel.setBounds(200, 210, 50, 20);
		parameterPanel.add(delayLabel);	
		
		JLabel delayUnitLabel = new JLabel("[ms]");
		delayUnitLabel.setBounds(300, 210, 50, 20);
		parameterPanel.add(delayUnitLabel);	
		
		warningLabel = new JLabel(stringToHtml(stringWithColor("You need to Stop and Run again to affect delay change.", "red")));
		warningLabel.setBounds(200, 280, 400, 20);
		warningLabel.setVisible(false);
		parameterPanel.add(warningLabel);	
		
		//Integer formatter
		NumberFormat format = NumberFormat.getIntegerInstance();
		format.setGroupingUsed(false);
		format.setParseIntegerOnly(true);
		NumberFormatter integerFormatter = new NumberFormatter(format);
		integerFormatter.setMinimum(0);
		integerFormatter.setCommitsOnValidEdit(true);
		
		
		JFormattedTextField delayTextField = new  JFormattedTextField(integerFormatter);
		delayTextField.setValue(delay);
		delayTextField.setToolTipText("Only positive Integer.");
		delayTextField.addPropertyChangeListener(actionEvent -> {
			delay = Integer.parseInt(delayTextField.getValue().toString());
			if(future != null && !future.isCancelled()) {
				console.println("You need to Stop and Run again to affect this change.");
				warningLabel.setVisible(true);
			}
		});
		delayTextField.setBounds(250, 210, 50, 20);
		parameterPanel.add(delayTextField);

		
		
		rotorLabel = new JLabel(stringToHtml("Rotor Status: " + statusToString(rotor.status)));
		rotorLabel.setBounds(200, 60, 220, 30);
		parameterPanel.add(rotorLabel);	
		
		houseLabel = new JLabel(stringToHtml("House Status: " + statusToString(house.status)));
		houseLabel.setBounds(200, 90, 220, 30);
		parameterPanel.add(houseLabel);
		
		JLabel keywordsLabel = new JLabel("Room Seperation Keywords: " + room1Keyword + " " + room2Keyword);
		keywordsLabel.setBounds(200, 120, 320, 30);
		parameterPanel.add(keywordsLabel);
		
		JLabel keywordsHintLabel = new JLabel("HolonElements with a name that contains the Keyword count.");
		keywordsHintLabel.setBounds(200, 135, 450, 30);
		parameterPanel.add(keywordsHintLabel);
		
		
		JButton selectRotorButton = new JButton("Select");
		selectRotorButton.setBounds(420,65, 90, 20);
		selectRotorButton.addActionListener(actionEvent -> this.selectGroupNode(rotor));
		parameterPanel.add(selectRotorButton);
		
		JButton selectRoom1Button = new JButton("Select");
		selectRoom1Button.setBounds(420,95, 90, 20);
		selectRoom1Button.addActionListener(actionEvent -> this.selectGroupNode(house));
		parameterPanel.add(selectRoom1Button);
		
		
		
		return parameterPanel;
	}



	private String stringToHtml(String string) {
		return "<html>" + string + "</html>";
	}

	private String statusToString(HolonObjectStatus status) {
		switch(status) {
		case Connected:
			return stringWithColor("Connected", "green");
		case NotSelected:
			return stringWithColor("Not selected", "red");
		case ObjectDeleted:
			return stringWithColor("Object deleted", "red");
		default:
			return "";
		
		}
	}
	private String stringWithColor(String string, String color) {
		return "<font color='"+color + "'>" + string + "</font>";
	}

	private void  updateStatusLabels() {
		rotorLabel.setText(stringToHtml("Rotor Status: " + statusToString(rotor.status)));
		houseLabel.setText(stringToHtml("House Status: " + statusToString(house.status)));
	}
	
	
	
	private Component createButtonPanel() {
		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		JButton clearButton =  new JButton("Clear Console");
		clearButton.addActionListener(actionEvent -> console.clear());
		buttonPanel.add(clearButton);
		JButton stopButton =  new JButton("Stop");
		stopButton.addActionListener(actionEvent -> stop());
		buttonPanel.add(stopButton);
		JButton runButton =  new JButton("Run");
		runButton.addActionListener(actionEvent -> initSchedule());
		buttonPanel.add(runButton);
		return buttonPanel;
	}

	
	private void stop() {
		if(future!= null) {
			if(future.isCancelled()) {
				console.println("Is cancelled.");
			}
			else {
				future.cancel(true);
				console.println("Stopped sending Requests on localhost:2019 ...");
			}
		}
		else {
			console.println("Not started jet.");
		}
	}

	private void initSchedule() {
		if(future != null && !future.isCancelled()) {
			console.println("Is running.");
			return;
		}
		warningLabel.setVisible(false);
		console.println("Starting sending Requests on localhost:2019");
		final ScheduledExecutorService 	executorService = Executors.newSingleThreadScheduledExecutor();
		final Runnable beeper = new Runnable() {
				
				//RepeatedMethod
		       	public void run() {
		       		if(lessInformation)console.print(".");
		       		checkWrapper(rotor);
		       		checkWrapperHouseSpecial(house);
		       	}


				private void checkWrapper(PhysicalLinkWrapper wrapper) {
					if(wrapper.status == HolonObjectStatus.Connected) checkConnected(wrapper);
		       		else if(!lessInformation)console.println(wrapper.postAddress +" is not connected.");
				}

				private void checkConnected(PhysicalLinkWrapper wrapper) {
					if(wrapper.hObject == null) {
						wrapper.status =  HolonObjectStatus.ObjectDeleted;
						updateStatusLabels();
						return;
					}
					if(wrapper.hObject.getNumberOfElements() > 0) {
						//OnlyForRotor
						
						int value = Math.round(((float)wrapper.hObject.getNumberOfActiveElements()/(float) wrapper.hObject.getNumberOfElements())*(float) 100);					
						if(onlyOnChange) {
							if(oldValueRotor != value) {
								sendRequest(wrapper.postAddress, value);
								oldValueRotor = value; 
							}
						}else {
							sendRequest(wrapper.postAddress, value);
						}
						
						
					}else {
						if(onlyOnChange) {
							if(oldValueRotor != 0) {
								sendRequest(wrapper.postAddress, 0);
								oldValueRotor = 0; 
							}
						}else {
							sendRequest(wrapper.postAddress, 0);
						}
					}
						
					
						
				}

				private void sendRequest(String postAddress, int value) {
					if(!lessInformation)console.println("Send " + "http://localhost:2019" + postAddress + value);
					doHttpUrlConnectionAction("http://localhost:2019" + postAddress + value);
				
				}

				  /**
				   * Send A Request to a URL.
				   * 
				   * @param desiredUrl
				   * @return
				   */
				  private void doHttpUrlConnectionAction(String desiredUrl)
				  {
				    URL url = null;

				      // create the HttpURLConnection
				      try {
						url = new URL(desiredUrl);
					
				      HttpURLConnection connection = (HttpURLConnection) url.openConnection();
				      
				      // just want to do an HTTP GET here
				      connection.setRequestMethod("GET");

				      connection.getResponseCode();
				     
				      // give it 15 seconds to respond
				      connection.setReadTimeout(1000);
				      connection.connect();
				      } catch (MalformedURLException e) {
				    	  console.println("MalformedURLException");
				    	  e.printStackTrace();
					} catch (IOException e) {
						console.println("IOException: Connection refused");
						e.printStackTrace();
					}
				  }
				  private void checkWrapperHouseSpecial(PhysicalLinkWrapper house) {
					if(!(house.status == HolonObjectStatus.Connected)) {
						if(!lessInformation)console.println("House" + " is not connected.");
						return;
					}
			       	//House is Connected
					if(house.hObject == null) {
						house.status =  HolonObjectStatus.ObjectDeleted;
						updateStatusLabels();
						return;
					}
					//House exist
					List<HolonElement> elementsOfRoom1 = house.hObject.getElements().stream().filter(ele -> ele.getEleName().contains(room1Keyword)).collect(Collectors.toList());
					List<HolonElement> elementsOfRoom2 = house.hObject.getElements().stream().filter(ele -> ele.getEleName().contains(room2Keyword)).collect(Collectors.toList());
					
					if(elementsOfRoom1.isEmpty()){
						if(onlyOnChange) {
							if(oldValueRoom1 != 0) {
								sendRequest(room1Address, 0);
								oldValueRoom1 = 0; 
							}
						}else {
							sendRequest(room1Address, 0);
						}
					}
					else{
						int value = Math.round(((float)elementsOfRoom1.stream().filter(ele -> ele.isActive()).count()/(float) elementsOfRoom1.size())*(float) 100);					
						if(onlyOnChange) {
							if(oldValueRoom1 != value) {
								sendRequest(room1Address, value);
								oldValueRoom1 = value; 
							}
						}else {
							sendRequest(room1Address, value);
						}
					}
					if(elementsOfRoom2.isEmpty()){
						if(onlyOnChange) {
							if(oldValueRoom2 != 0) {
								sendRequest(room2Address, 0);
								oldValueRoom2 = 0; 
							}
						}else {
							sendRequest(room2Address, 0);
						}
					}
					else{
						int value = Math.round(((float)elementsOfRoom2.stream().filter(ele -> ele.isActive()).count()/(float) elementsOfRoom2.size())*(float) 100);					
						if(onlyOnChange) {
							if(oldValueRoom2 != value) {
								sendRequest(room2Address, value);
								oldValueRoom2 = value; 
							}
						}else {
							sendRequest(room2Address, value);
						}
					}
					
					
				  }
		     };
		future = executorService.scheduleAtFixedRate(beeper, 0, delay, TimeUnit.MILLISECONDS);
	}
	

	
	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);
			}
		}
	}
	
	//SelectGroupNode
	private void selectGroupNode(PhysicalLinkWrapper wrapper) {
		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) {
			console.println("Selected: " + selected);
			wrapper.hObject = selected.object;
			wrapper.status = HolonObjectStatus.Connected;
			updateStatusLabels();
		}
	}
	private   class  Handle<T>{
		public T object;
		Handle(T object){
			this.object = object;
		}
		public String toString() {
			return object.toString();
		}
	}
	
	
	@Override
	public JPanel getPanel() {
		return content;
	}

	@Override
	public void setController(Control control) {
		this.control = control;
	}
	

	
	

}
