package com.neuronrobotics.bowlerstudio;

import com.neuronrobotics.addons.driving.HokuyoURGDevice;
import com.neuronrobotics.bowlerstudio.assets.AssetFactory;
import com.neuronrobotics.bowlerstudio.threed.VirtualCameraDevice;
import com.neuronrobotics.bowlerstudio.utils.BowlerConnectionMenu;
import com.neuronrobotics.imageprovider.AbstractImageProvider;
//import com.neuronrobotics.imageprovider.OpenCVImageProvider;
import com.neuronrobotics.imageprovider.StaticFileProvider;
import com.neuronrobotics.imageprovider.URLImageProvider;
import com.neuronrobotics.sdk.addons.gamepad.BowlerJInputDevice;
import com.neuronrobotics.sdk.addons.gamepad.IJInputEventListener;
import com.neuronrobotics.sdk.addons.kinematics.FirmataBowler;
import com.neuronrobotics.sdk.addons.kinematics.gcodebridge.GcodeDevice;
import com.neuronrobotics.sdk.common.*;
import com.neuronrobotics.sdk.javaxusb.UsbCDCSerialConnection;
import com.neuronrobotics.sdk.network.BowlerTCPClient;
import com.neuronrobotics.sdk.network.UDPBowlerConnection;
import com.neuronrobotics.sdk.serial.SerialConnection;
import com.neuronrobotics.sdk.ui.AbstractConnectionPanel;
import com.neuronrobotics.sdk.util.ThreadUtil;
import com.neuronrobotics.sdk.wireless.bluetooth.BluetoothSerialConnection;
import gnu.io.NRSerialPort;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.Event;
import org.reactfx.util.FxTimer;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

//import org.bytedeco.javacv.OpenCVFrameGrabber;

public class ConnectionManager extends Tab implements IDeviceAddedListener ,EventHandler<ActionEvent> {

	private static VBox rootItem;
	private static final ArrayList<PluginManagerWidget> plugins = new ArrayList<PluginManagerWidget>();
	//private BowlerStudioController bowlerStudioController;
	String formatStr="%1$-40s %2$-60s  %3$-40s";
	private static final ConnectionManager connectionManager;
	private static Button disconnectAll;
	private static HBox topLine;
	final static Accordion accordion = new Accordion (); 
	static{
		connectionManager = new ConnectionManager();
	}


	private Node getIcon(String s) {
		return new ImageView(new Image(
				AbstractConnectionPanel.class.getResourceAsStream(s)));
	}

	public ConnectionManager() {
		if(connectionManager!=null){
			throw new RuntimeException("Connection manager is a static singleton, access it using ConnectionManager.getConnectionmanager()");
		}
		setText("My Devices");
		setGraphic(AssetFactory.loadIcon("My-Devices.png"));
		rootItem = new VBox(10);
		
//		rootItem.getColumnConstraints().add(new ColumnConstraints(30)); // column 1 is 75 wide
//		rootItem. getColumnConstraints().add(new ColumnConstraints(80)); // column 2 is 300 wide
//		rootItem.getColumnConstraints().add(new ColumnConstraints(100)); // column 2 is 100 wide
//		rootItem.getColumnConstraints().add(new ColumnConstraints(50)); // column 2 is 100 wide
//		
		topLine = new HBox(20);
		
		disconnectAll = new Button("Disconnect All",AssetFactory.loadIcon("Disconnect-All.png"));
		disconnectAll.setOnAction(new EventHandler<ActionEvent>() {
		    @Override public void handle(ActionEvent e) {
		    	disconnectAll();
		    }
		});
		disconnectAll.setDisable(true);
		topLine.getChildren().addAll(AssetFactory.loadIcon("Connected-Devices.png"),new Text("Connected Devices"),disconnectAll);
		rootItem.getChildren().add(topLine);

		rootItem.getChildren().add(accordion);
//		rootItem = new CheckBoxTreeItem<String>( String.format("  "+formatStr, "SCRIPTING NAME","DEVICE TYPE","MAC ADDRESS"),
//				getIcon("images/connection-icon.png"
//				// "images/usb-icon.png"
//				));
//		rootItem.setExpanded(true);
//		rootItem.setSelected(true);
//		rootItem.selectedProperty().addListener(b -> {
//			if (!rootItem.isSelected()) {
//				disconnectAll();
//			}
//		});
		

		setContent(rootItem);
		
		DeviceManager.addDeviceAddedListener(this);
			

	}



	public static void addConnection(BowlerAbstractDevice newDevice, String name) {
		if(!VirtualCameraDevice.class.isInstance(newDevice))
			DeviceManager.addConnection(newDevice, name);
		
	}

	@Override
	public void handle(ActionEvent event) {
		// TODO Auto-generated method stub

	}

	public static ArrayList<PluginManagerWidget>  getPlugins() {
		return plugins;
	}
//
//	public BowlerStudioController getBowlerStudioController() {
//		return bowlerStudioController;
//	}
//
//	public void setBowlerStudioController(
//			BowlerStudioController bowlerStudioController) {
//		this.bowlerStudioController = bowlerStudioController;
//	}
	
	public static BowlerAbstractDevice pickConnectedDevice(@SuppressWarnings("rawtypes") Class class1) {
		List<String> choices = DeviceManager.listConnectedDevice(class1);
		
		if(!choices.isEmpty()){
			ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0),
					choices);
			dialog.setTitle("Bowler Device Chooser");
			dialog.setHeaderText("Choose connected bowler device");
			dialog.setContentText("Device Name:");
	
			// Traditional way to get the response value.
			Optional<String> result = dialog.showAndWait();
			if (result.isPresent()) {
				for (int i = 0; i < plugins.size(); i++) {
					if (plugins.get(i).getManager().getName().contains(result.get())) {
						return plugins.get(i).getManager().getDevice();
					}
				}
			}
		}else{
			Alert alert = new Alert(AlertType.INFORMATION);
			alert.setTitle("Device not availible");
			alert.setHeaderText("Connect a "+class1.getSimpleName());
			alert.setContentText("A device of type "+class1.getSimpleName()+" is needed");
			alert .initModality(Modality.APPLICATION_MODAL);
			alert.show();
		}
		return null;
	}


//	public BowlerAbstractDevice pickConnectedDevice() {
//
//		return pickConnectedDevice(null);
//	}

	public static void disconnectAll() {

		//extract list int thread safe object
		Object [] pms= plugins.toArray();
		for (int i=0;i<pms.length;i++) {
			disconectAndRemoveDevice(((PluginManagerWidget)pms[i]).getManager());
			//ThreadUtil.wait(50);
		}

	}
	
//	 public static OpenCVImageProvider onConnectCVCamera() {
//		List<String> choices = new ArrayList<>();
//		choices.add("0");
//		choices.add("1");
//		choices.add("2");
//		choices.add("3");
//		choices.add("4");
//		
//		ChoiceDialog<String> dialog = new ChoiceDialog<>("0", choices);
//		dialog.setTitle("OpenCV Camera Index Chooser");
//		dialog.setHeaderText("Choose an OpenCV camera");
//		dialog.setContentText("Camera Index:");
//
//		// Traditional way to get the response value.
//		Optional<String> result = dialog.showAndWait();
//		
//		// The Java 8 way to get the response value (with lambda expression).
//		if (result !=null) {
//			String letter = result.get();
//			OpenCVImageProvider p = new OpenCVImageProvider(Integer.parseInt(letter));
//			String name = "camera"+letter;
//			addConnection(p,name);
//			return p;
//		}
//		return null;
////		OpenCVImageProvider p = new OpenCVImageProvider(0);
////		String name = "camera0";
////		application.addConnection(p,name);
//		
//	}
//


	 public static void onConnectJavaCVCamera() {
		// onConnectCVCamera();
//		List<String> choices = new ArrayList<>();
//		try {
//			String[] des = OpenCVFrameGrabber.getDeviceDescriptions();
//			if(des.length==0)
//				return;
//			for (String s: des){
//				choices.add(s);
//			}
//		} catch (org.bytedeco.javacv.FrameGrabber.Exception |UnsupportedOperationException e1) {
//			choices.add("0");
//			choices.add("1");
//			choices.add("2");
//			choices.add("3");
//			choices.add("4");
//		}
//		
//		ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0), choices);
//		dialog.setTitle("JavaCV Camera Index Chooser");
//		dialog.setHeaderText("Choose an JavaCV camera");
//		dialog.setContentText("Camera Index:");
//
//		// Traditional way to get the response value.
//		Optional<String> result = dialog.showAndWait();
//		
//		// The Java 8 way to get the response value (with lambda expression).
//		result.ifPresent(letter -> {
//			JavaCVImageProvider p;
//			try {
//				p = new JavaCVImageProvider(Integer.parseInt(letter));
//				String name = "camera"+letter;
//				addConnection(p,name);
//			} catch (Exception e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//
//		});
//		

	}


	 public static void onConnectFileSourceCamera() {
		 Platform.runLater(()->{});
		 
		FileChooser fileChooser = new FileChooser();
		fileChooser.setTitle("Open Image File");
		File f = fileChooser.showOpenDialog(BowlerStudioModularFrame.getPrimaryStage());
		if(f!=null){
			AbstractImageProvider p = new StaticFileProvider(f);
			String name = "image";
			addConnection(p,name);
		}
	}


	public static void onConnectURLSourceCamera() {
		TextInputDialog dialog = new TextInputDialog("http://neuronrobotics.com/img/AndrewHarrington/2014-09-15-86.jpg");
		dialog.setTitle("URL Image Source");
		dialog.setHeaderText("This url will be loaded each capture.");
		dialog.setContentText("URL ");

		// Traditional way to get the response value.
		Optional<String> result = dialog.showAndWait();
		if (result.isPresent()){
			URLImageProvider p;
			try {
				p = new URLImageProvider(new URL(result.get()));
				String name = "url";
				addConnection(p,name);
			} catch (MalformedURLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	 public static void onMarlinGCODE() {
			Set<String> ports = NRSerialPort.getAvailableSerialPorts();
			List<String> choices = new ArrayList<>();
			if(ports.isEmpty())
				return;
			for (String s: ports){
				choices.add(s);
			}

			
			ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0), choices);
			dialog.setTitle("GCODE Device Serial Port Chooser");
			dialog.setHeaderText("Supports Marlin");
			dialog.setContentText("GCODE Device Port:");

			// Traditional way to get the response value.
			Optional<String> result = dialog.showAndWait();
			
			// The Java 8 way to get the response value (with lambda expression).
			result.ifPresent(letter -> {
				GcodeDevice p =  new GcodeDevice(new NRSerialPort(letter, 115200));
				p.connect();
				String name = "GCODE";
				addConnection(p,name);
			});
			
		}
	 public static void onConnectHokuyoURG() {
		Set<String> ports = NRSerialPort.getAvailableSerialPorts();
		List<String> choices = new ArrayList<>();
		if(ports.isEmpty())
			return;
		for (String s: ports){
			choices.add(s);
		}

		
		ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0), choices);
		dialog.setTitle("LIDAR Serial Port Chooser");
		dialog.setHeaderText("Supports URG-04LX-UG01");
		dialog.setContentText("Lidar Port:");

		// Traditional way to get the response value.
		Optional<String> result = dialog.showAndWait();
		
		// The Java 8 way to get the response value (with lambda expression).
		result.ifPresent(letter -> {
			HokuyoURGDevice p = new HokuyoURGDevice(new NRSerialPort(letter, 115200));
			p.connect();
			String name = "lidar";
			addConnection(p,name);
		});
		
	}


	public static void onConnectGamePad(String name ) {
		Controller[] ca = ControllerEnvironment.getDefaultEnvironment().getControllers();
		
		List<String> choices = new ArrayList<>();
		if(ca.length==0)
			return;
		for (Controller s: ca){
			choices.add(s.getName());
		}

		
		ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0), choices);
		dialog.setTitle("JInput Game Controller Select");
		dialog.setHeaderText("Connect a game controller");
		dialog.setContentText("Controller:");

		// Traditional way to get the response value.
		Optional<String> result = dialog.showAndWait();
		
		// The Java 8 way to get the response value (with lambda expression).
		result.ifPresent(letter -> {
			for(Controller s: ca){
				if(letter.contains(s.getName())){
					BowlerJInputDevice p =new BowlerJInputDevice(s);
					p.connect();
					IJInputEventListener l=new IJInputEventListener() {
						@Override
						public void onEvent(Component comp, Event event1,
								float value, String eventString) {
									//System.out.println(comp.getName()+" is value= "+value);
								}
					};
					p.addListeners(l);
					addConnection(p,name);
					return;
				}
			}

		});
		
	}

	@Override
	public void onNewDeviceAdded(BowlerAbstractDevice newDevice) {
		if(VirtualCameraDevice.class.isInstance(newDevice))
			return;
		PluginManager mp;
		Log.debug("Adding a "+newDevice.getClass().getName()+" with name "+newDevice.getScriptingName() );
		mp = new PluginManager(newDevice);
		

		BowlerAbstractConnection con = newDevice.getConnection();
		Node icon = getIcon("images/connection-icon.png"
		// "images/usb-icon.png"
		);
		if (SerialConnection.class.isInstance(con)) {
			icon = getIcon(
			// "images/ethernet-icon.png"
			"images/usb-icon.png");
		} else if (UsbCDCSerialConnection.class.isInstance(con)) {
			icon = getIcon(
			// "images/ethernet-icon.png"
			"images/usb-icon.png");
		} else if (BluetoothSerialConnection.class.isInstance(con)) {
			icon = getIcon(
			// "images/ethernet-icon.png"
			"images/bluetooth-icon.png");
		} else if (UDPBowlerConnection.class.isInstance(con)
				|| BowlerTCPClient.class.isInstance(con)) {
			icon = getIcon(
			// "images/ethernet-icon.png"
			"images/ethernet-icon.png");
		}
		String line = String.format(formatStr, 
				newDevice.getScriptingName(),
				newDevice.getClass().getSimpleName(),
				newDevice.getAddress());
		PluginManagerWidget e = new PluginManagerWidget(mp,icon);
		plugins.add(e);
		Platform.runLater(() -> accordion.getPanes().add(e));
		Platform.runLater(() -> disconnectAll.setDisable(false));
		
		
		mp.setName(newDevice.getScriptingName());

		newDevice.addConnectionEventListener(
				new IDeviceConnectionEventListener() {
					@Override
					public void onDisconnect(BowlerAbstractDevice source) {
						// clean up after yourself...
						//disconectAndRemoveDevice(mp);
						
						for(int i=0;i<plugins.size();i++){
							PluginManager p=plugins.get(i).getManager();
							if(p.getDevice()==source){
								DeviceManager.remove(p.getDevice());
								return;
							}
						}
						
					}

					// ignore
					@Override
					public void onConnect(BowlerAbstractDevice source) {
					}
				});
		if(	getBowlerStudioController()!=null)
			BowlerStudioModularFrame.getBowlerStudioModularFrame().setSelectedTab(this);
	}
	private BowlerStudioController getBowlerStudioController() {
		// TODO Auto-generated method stub
		return BowlerStudioController.getBowlerStudio();
	}


	private static void disconectAndRemoveDevice(PluginManager mp){
		System.out.println("CM Disconnecting " + mp.getName());
		Log.warning("Disconnecting " + mp.getName());
		if(mp.getDevice().isAvailable() || NonBowlerDevice.class.isInstance(mp))
			mp.getDevice().disconnect();	
		DeviceManager.remove(mp.getDevice());
	}

	@Override
	public void onDeviceRemoved(BowlerAbstractDevice bad) {
		Log.warning("Removing Device " + bad.getScriptingName());
		//new RuntimeException().printStackTrace();
		for(int i=0;i<plugins.size();i++){
			PluginManager p=plugins.get(i).getManager();
			if(p.getDevice()==bad){
				Log.warning("Found Device " + bad.getScriptingName());
				//new RuntimeException().printStackTrace();
				PluginManagerWidget torem = plugins.remove(i);
				Platform.runLater(() ->accordion.getPanes().remove(torem));
				if (plugins.isEmpty()){
					Platform.runLater(() ->disconnectAll.setDisable(true));
				}
				return;
			}
		}
	}

	public static void addConnection() {
		Stage s = new Stage();
		new Thread() {
			public void run() {
				BowlerDatagram.setUseBowlerV4(true);
				BowlerConnectionMenu controller = new BowlerConnectionMenu();
				try {
					controller.start(s);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}.start();
		//DeviceManager.addConnection();
	}


	public static ConnectionManager getConnectionManager() {
		return connectionManager;
	}

	 public static void onFirmata() {
			Set<String> ports = NRSerialPort.getAvailableSerialPorts();
			List<String> choices = new ArrayList<>();
			if(ports.isEmpty())
				return;
			for (String s: ports){
				choices.add(s);
			}

			
			ChoiceDialog<String> dialog = new ChoiceDialog<>(choices.get(0), choices);
			dialog.setTitle("Firmata Device Serial Port Chooser");
			dialog.setHeaderText("Supports Firmata");
			dialog.setContentText("Firmata Device Port:");

			// Traditional way to get the response value.
			Optional<String> result = dialog.showAndWait();
			
			// The Java 8 way to get the response value (with lambda expression).
			result.ifPresent(letter -> {
				new Thread(()->{
					System.out.print("\nConnecting Firmata...");
					FirmataBowler p = new FirmataBowler(letter);
					p.connect();
					String name = "firmata";
					addConnection(p,name);
					System.out.print("Done!\n");
				}).start();
			});
			
		}

	
}