package edu.cpp.WholesomeChat.Controllers;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import eu.hansolo.tilesfx.Tile;
import eu.hansolo.tilesfx.TileBuilder;
import eu.hansolo.tilesfx.Tile.SkinType;
import eu.hansolo.tilesfx.chart.ChartData;
import eu.hansolo.tilesfx.chart.RadarChart.Mode;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import me.Miic.WholesomeChat.DataStructs.ReschedulableTimer;

public class MainController implements Initializable, Observer {
	
	
    static class ChatAccess extends Observable {
        private Socket socket;
        private OutputStream outputStream;

        @Override
        public void notifyObservers(Object arg) {
            super.setChanged();
            super.notifyObservers(arg);
        }
        
        /** Checks socket connection */
        public boolean isConnected() {
        	if (socket != null) {
        		return socket.isConnected();
        	}
        	return false;
        }
        
        public boolean isClosed() {
        	if (socket != null) {
        		return socket.isClosed();
        	}
        	return false;
        }

        /** Create socket, and receiving thread */
        public void InitSocket(String server, int port) throws IOException {
            socket = new Socket(server, port);
            outputStream = socket.getOutputStream();

            Thread receivingThread = new Thread() {
                @Override
                public void run() {
                    try {
                        BufferedReader reader = new BufferedReader(
                                new InputStreamReader(socket.getInputStream()));
                        String line;
                        while ((line = reader.readLine()) != null)
                            notifyObservers(line);
                    } catch (IOException ex) {
                        notifyObservers(ex);
                    }
                }
            };
            receivingThread.start();
        }

        private static final String CRLF = "\r\n"; // newline

        /** Send a line of text */
        public void send(String text) {
            try {
                outputStream.write((text + CRLF).getBytes());
                outputStream.flush();
            } catch (IOException ex) {
                notifyObservers(ex);
            }
        }

        /** Close the socket */
        public void close() {
            try {
                socket.close();
            } catch (IOException ex) {
                notifyObservers(ex);
            }
        }
        
    }

//    private final String server = "192.99.55.216";
//    private final int port = 2222;
    
	private Stage stage;
	private AnchorPane anchor;
	private Tile tile;
	
    private ChatAccess chatAccess;
    
    private ReschedulableTimer timer;
    private Runnable runnable;
    
    private Gson gson;
//    private String lazyCache;
	
	/* Used for changing chart pushing to API
	 * Since this is a demo client this is done here
	 * In a real server-client setup, API pushing should be done on server end.
	 */
	
	@FXML
	public void onButtonAction(ActionEvent event) {
		System.out.println("Button Clicked");
		Scene scene = stage.getScene();
		
		TextField inputTextField = (TextField) scene.lookup("#textfield");
        
        JsonObject str = new JsonObject();
        str.addProperty("text", inputTextField.getText().replaceAll("\"", "\\\"").trim());
        
        if (str != null && inputTextField.getText().length() > 0) {
        	str.addProperty("intent", "message");
            chatAccess.send(gson.toJson(str));
        }
        
        inputTextField.selectAll();
        inputTextField.requestFocus();
        inputTextField.setText("");
        
        runnable.run();
        
	}
	
	/* Resets timer clock when user types
	 * 
	 */
	
	@FXML
	public void onType(KeyEvent event) {
		timer.reschedule(runnable, 1000);
	}
	

	/* 
	 * Gets Toxicity of a Clicked Message
	 */
	
	@FXML
	public void onListClick(MouseEvent event) {
		Scene scene = stage.getScene();
		ListView<String> list = (ListView<String>) scene.lookup("#list");
    	if (list.getSelectionModel() != null && list.getSelectionModel().getSelectedItem() != null) {
	        JsonObject str = new JsonObject();
	        str.addProperty("text", list.getSelectionModel().getSelectedItem().replaceAll("\\[.*?\\] @.*? > ", "").replaceAll("\"", "\\\"").trim());
	        str.addProperty("intent", "precheck");
	        chatAccess.send(gson.toJson(str));
    	}
	}
	
	/* Used for setting up variable beforehand.
	 * DO NOT USE FOR INITIALIZING TILES OR OBJECTS IN UI
	 * the stage is not guaranteed fully initialized at this point
	 * 
	 */
		
	public void initialize(URL location, ResourceBundle resources) {
		gson = new Gson();
		timer = new ReschedulableTimer();
		runnable = new Runnable() {
    		public void run() {
    			Scene scene = stage.getScene();
    			TextField inputTextField = (TextField) scene.lookup("#textfield");
    	    	if (inputTextField.getText().trim().length() != 0) {
	    	        JsonObject str = new JsonObject();
	    	        str.addProperty("text", inputTextField.getText().trim());
	    	        str.addProperty("intent", "precheck");
	    	        chatAccess.send(gson.toJson(str));
    	    	}
    		}
		};
	}
	
	@Override
	public void update(Observable o, Object arg) {
		final Object finalArg = arg;
		System.out.println(finalArg.toString());
        Platform.runLater(new Runnable() {
            public void run() {            	
            	Scene scene = stage.getScene();	

				JsonParser parser = new JsonParser();
				
				if (!chatAccess.isClosed()) {
					JsonObject json = parser.parse(finalArg.toString()).getAsJsonObject();
					
					if (json.has("text")) {
		        		@SuppressWarnings("unchecked")
						ListView<String> list = (ListView<String>) scene.lookup("#list");
	
						ObservableList<String> items = list.getItems();
						if (items == null) {
							items = FXCollections.observableArrayList();
						}				
						items.add(json.get("text").getAsString());
						list.setItems(items);
					}
					
					if (json.has("toxicity")) {
						Tile gauge = (Tile) scene.lookup("#gauge");
						gauge.setValue(json.get("toxicity").getAsFloat() * 100);
					}
					
					if (json.has("Complexity")) {
						Tile circle = (Tile) scene.lookup("#circle");
						Float val = json.get("Complexity").getAsFloat();
						if (!Float.isNaN(json.get("Complexity").getAsFloat())) {
							circle.setValue(val);
						} else {
							circle.setValue(0);
						}
					}
					
					if (json.has("tones")) {
						JsonArray tones = json.get("tones").getAsJsonArray();
						List<ChartData> dat = new ArrayList<ChartData>(); 
					   	for(int i = 0; i < tones.size(); i++) {
							ChartData newElemente = new ChartData(Math.round(tones.get(i).getAsJsonObject().get("score").getAsFloat() * 100));
							newElemente.setName(tones.get(i).getAsJsonObject().get("tone_name").getAsString() + " ~" + newElemente.getValue() + "%");
							dat.add(newElemente);
				    	}
					   	for (int i = 5; dat.size() < 5; i--) {
							dat.add(new ChartData(0));
					   	}
						createToneChart(dat);
					}
	            } else {
	            	chatAccess.close();
	            }
            }
        });
	}
	
	
	/* Used to pass the stage currently being worked on.
	 * Useful for interacting with FXML generated objects.
	 * 
	 */
	
	public void passStage(Stage stage, ChatAccess chatAccess) {
		this.stage = stage;	
		this.chatAccess = chatAccess;
		this.chatAccess.addObserver(this);
		this.stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
	          public void handle(WindowEvent we) {
	              System.out.println("Stage is closing");
	              if (chatAccess != null && chatAccess.isConnected()) {
	            	JsonObject str = new JsonObject();
	      	        str.addProperty("text", "/quit");
	    	        str.addProperty("intent", "message");
	    	        chatAccess.send(gson.toJson(str));
	            	chatAccess.close();
	              }
	          }
	    });
	}
	
	/* Used to pass the login name from the Login page
	 * Useful for receiving login info
	 * If more than this info is being passed in the future, rework into a byte based transfer system.
	 * 
	 */

//	public void passNick(String nick) {
//		JsonObject str = new JsonObject();
//        str.addProperty("text", nick);
//        //Quick fix: Clients will silently pass nickname to clientThread as initial contact. Unsafe and hackable, fix as argument later.
//        chatAccess.send(str.toString());
//
//	}
	
	/* PassAnchor is used by the Application class to pass the anchorpanel being worked on
	 * Allowing the controller class to add new TilesFX since they cannot be 
	 * from an FXML file as it is a third-party library object 
	 */

	public void passAnchor(AnchorPane anchor) {
		this.anchor = anchor;
		ArrayList<ChartData> dat = new ArrayList<>();
		dat.add(new ChartData(50));
		dat.add(new ChartData(50));
		dat.add(new ChartData(50));
		dat.add(new ChartData(50));
		dat.add(new ChartData(50));
		createToneChart(dat);
		
	 	Tile gauge = TileBuilder.create()
	 	        .skinType(SkinType.GAUGE)
	 	        .prefSize(150, 150)
	 	        .layoutX(490)
	 	        .layoutY(440)
	 	        .title("Toxcity")
	 	        .unit("%")
	 	        .threshold(100)
	 	        .thresholdVisible(false)
	 	        .build();
	 	gauge.setId("gauge");
	    anchor.getChildren().add(gauge);
	    
	    Tile circularProgressTile = TileBuilder.create()
                .skinType(SkinType.CIRCULAR_PROGRESS)
                .prefSize(150, 150)
	 	        .layoutX(490 + 150 + 10)
	 	        .layoutY(440)
                .title("Complexity")
                .text("")
                .unit("\u0025")
                //.graphic(new WeatherSymbol(ConditionAndIcon.CLEAR_DAY, 48, Color.WHITE))
                .build();
	    circularProgressTile.setId("circle");
	    anchor.getChildren().add(circularProgressTile);
	}
	
	/* 
	 * Passes ChatAccess to Client
	 * 
	 */
	
	/* Due to the data being dynamic and the chart being static,
	 * giving a Radar Chart data that does not match its current data structure causes it to freeze
	 * Therefore, we must create a new chart and delete the old one at every chart change.
	 */
	
	@SuppressWarnings("unchecked")
	private void createToneChart(List<ChartData> list) {
		if (tile != null) {
			anchor.getChildren().remove(tile);
			tile = null;
		}
		
		List<ChartData> emptyList = new ArrayList<ChartData>();
		for(int i = 0; i < list.size(); i++) {
			emptyList.add(new ChartData(0));
		}
		
	 	tile = TileBuilder.create().skinType(SkinType.RADAR_CHART)
                .prefSize(310, 410)
                .layoutX(490)
                .layoutY(15)
                .minValue(0)
                .maxValue(100)
                //.title("RadarChart Tile")
                .unit("Tones")
                .radarChartMode(Mode.POLYGON)
                .gradientStops(new Stop(0.00000, Color.TRANSPARENT),
                               new Stop(0.00001, Color.web("#3552a0")),
                               new Stop(0.09090, Color.web("#456acf")),
                               new Stop(0.27272, Color.web("#45a1cf")),
                               new Stop(0.36363, Color.web("#30c8c9")),
                               new Stop(0.45454, Color.web("#30c9af")),
                               new Stop(0.50909, Color.web("#56d483")),
                               new Stop(0.72727, Color.web("#9adb49")),
                               new Stop(0.81818, Color.web("#efd750")),
                               new Stop(0.90909, Color.web("#ef9850")),
                               new Stop(1.00000, Color.web("#ef6050")))
                //.text("Sector")
                .tooltipText("")
                .chartData(emptyList)
                .animated(true)
                .build();
	 	emptyList = tile.getChartData();
	 	for (int i = 0; i < list.size(); i++) {
	 		emptyList.get(i).setName(list.get(i).getName());
	 		emptyList.get(i).setValue(list.get(i).getValue());
	 	}
    	anchor.getChildren().add(tile);
	}


	
}