package de.srsoftware.web4rail;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.keawe.tools.translations.Translation;
import de.srsoftware.web4rail.tags.Button;
import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Form;
import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Label;

public class ControlUnit extends Thread implements Constants{	
	private static final Logger LOG = LoggerFactory.getLogger(ControlUnit.class);
	private static final String DEFAULT_HOST = "localhost";
	private static final int DEFAULT_PORT = 4303;
	private static final int OK_PROTO = 201;
	private static final int OK_MODE = 202;
	private static final int OK = 200;
	private static final String HOST = "host";
	private static final String PORT = "port";
	private static final String BUS = "bus";
	
	public class Reply{
		private long secs;
		private int milis;
		private int code;
		private String message;
		
		public Reply(Scanner scanner) {
			String word = scanner.next();
			secs = Long.parseLong(word.substring(0, word.length()-4));
			milis = Integer.parseInt(word.substring(word.length()-3));
			code = scanner.nextInt();
			message = scanner.nextLine().trim();
			LOG.info("recv {}.{} {} {}.",secs,milis,code,message);
		}
		
		public String message() {
			return message;
		}

		@Override
		public String toString() {
			return "Reply("+secs+"."+milis+" / "+code+" / "+message+")";
		}

		public boolean is(int code) {
			return code == this.code;
		}
	}
	
	
	
	private String host = DEFAULT_HOST;
	private int port = DEFAULT_PORT;
	private int bus = 0;
	private boolean stopped = true;
	private LinkedList<Command> queue = new LinkedList<Command>();
	private Socket socket;
	private Scanner scanner;
	private boolean power = false;

	/**
	 * @return stops the loop at the next interval
	 */
	public ControlUnit end() {
		stopped = true;
		return this;
	}

	private void handshake() throws IOException {
		String proto = null;
		if (scanner.hasNext()) {
			String line = scanner.nextLine();
			LOG.debug("recv: "+line);
			for (String part : line.split(";")) {
				part = part.trim();
				if (part.startsWith("SRCP ")) proto = part.substring(5); 
			}
			if (proto == null) throw new IOException("Handshake failed: "+line);
			if (!proto.startsWith("0.8.")) throw new IOException("Unsupported protocol: "+proto);
			writeln("SET PROTOCOL SRCP "+proto);			
		} else throw new IOException("Handshake expected.");
		
		Reply reply = new Reply(scanner);
		if (reply.code != OK_PROTO) throw new IOException("Handshake failed: "+reply);
		
		writeln("SET CONNECTIONMODE SRCP COMMAND"); // preset following mode: COMMAND MODE
		reply = new Reply(scanner);
		if (reply.code != OK_MODE) throw new IOException("Handshake failed: "+reply);
		writeln("GO"); // switch mode
		reply = new Reply(scanner);
		if (reply.code != OK) throw new IOException("Handshake failed: "+reply);
	}
	
	private JSONObject json() {
		JSONObject json = new JSONObject();
		json.put(HOST, host);
		json.put(PORT, port);
		json.put(BUS, bus);
		return json;
	}
	
	public void load(String filename) throws IOException {
		BufferedReader file = new BufferedReader(new FileReader(filename));
		JSONObject json = new JSONObject(file.readLine());
		file.close();
		if (json.has(PORT)) port = json.getInt(PORT);
		if (json.has(BUS)) bus = json.getInt(BUS);
		if (json.has(HOST)) host = json.getString(HOST);
	}
	
	public static void main(String[] args) throws InterruptedException {
		ControlUnit cu = new ControlUnit().setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart();
		Thread.sleep(1000);
		cu.queue("SET {} POWER ON");
		cu.queue("SET {} GL 1 0 10 128");
		Thread.sleep(1000);
		cu.end();
	}
	
	public Object process(HashMap<String, String> params) {
		switch (params.get(ACTION)) {
		case ACTION_CONNECT:
			restart();
			return t("Control unit (re)started.");
		case ACTION_POWER:
			return togglePower();
		case ACTION_PROPS:
			return properties();
		case ACTION_UPDATE:
			return update(params);
		}
		
		return t("Unknown action: {}",params.get(ACTION));
	}
	
	public Object properties() {
		Window win = new Window("cu-props", t("Properties of the control unit"));
		Form form = new Form();
		new Input(ACTION,ACTION_UPDATE).hideIn(form);
		new Input(REALM,REALM_CU).hideIn(form);
		Fieldset fieldset = new Fieldset(t("Server connection"));
		new Input(HOST,host).addTo(new Label(t("Hostname"))).addTo(fieldset);
		new Input(PORT,port).numeric().addTo(new Label(t("Port"))).addTo(fieldset);
		new Input(BUS,bus).numeric().addTo(new Label(t("Bus"))).addTo(fieldset);
		new Button(t("Save")).addTo(fieldset).addTo(form).addTo(win);
		
		fieldset = new Fieldset("Actions");
		new Button(t("Connect"),"connectCu();").addTo(fieldset).addTo(win);
		return win;
	}

	public Command queue(String command) {
		Command promise = new Command(command);
		queue.add(promise);
		return promise;
	}
	
	/**
	 * Should close the server connection and establish new server connection
	 * @return 
	 */
	public ControlUnit restart() {
		end();
		start();
		return this;
	}
	
	@Override
	public void run() {		
		while (!stopped) {
			try {	
				if (queue.isEmpty()) {
					Thread.sleep(10);
				} else {
					Command command = queue.pop();
					command.complete(send(command));
				}
			} catch (InterruptedException | IOException e) {
				e.printStackTrace();
			}
		}
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void save(String filename) throws IOException {
		BufferedWriter file = new BufferedWriter(new FileWriter(filename));
		file.write(json()+"\n");
		file.close();
	}
	
	/**
	 * send command to Server
	 * @param command
	 * @return 
	 * @throws IOException 
	 */
	private Reply send(Command command) throws IOException {
		if (command == null) return null;
		writeln(command.toString());
		return new Reply(scanner);
	}
	
	private ControlUnit setBus(int bus) {
		this.bus = bus;
		return this;
	}
	
	public ControlUnit setEndpoint(String newHost, int newPort){
		host = newHost;
		port = newPort;
		return this;
	}
	
	@Override
	public synchronized void start() {
		try {
			socket = new Socket(host, port);
			scanner = new Scanner(socket.getInputStream());
			handshake();			
			stopped = false;
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
		super.start();
	}
	
	private static String t(String text,Object...fills) {
		return Translation.get(Application.class, text, fills);
	}
	
	private Object togglePower() {
		power = !power;
		String PW = power?"ON":"OFF";
		queue("SET {} POWER "+PW);
		return t("Turned power {}.",PW);
	}

	
	public String update(HashMap<String, String> params) {
		if (params.containsKey(HOST)) host = params.get(HOST);
		if (params.containsKey(PORT)) port = Integer.parseInt(params.get(PORT));
		if (params.containsKey(BUS)) bus = Integer.parseInt(params.get(BUS));
		return t("Updated control unit settings");
	}

	private void writeln(String data) throws IOException {
		data = data.replace("{}", ""+bus);
		socket.getOutputStream().write((data+"\n").getBytes(StandardCharsets.US_ASCII));
		LOG.info("sent {}.",data);
	}
}