package org.btcollider.cnc.comm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

import org.btcollider.cnc.dto.KeyRange;
import org.btcollider.cnc.keysrv.KeyServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClientWorker implements Runnable {
	final Logger log = LoggerFactory.getLogger(ClientWorker.class);

	private Socket clientSocket;
	private BufferedReader in;
	private PrintWriter out;

	public ClientWorker(Socket clientSocket) {
		this.clientSocket = clientSocket;
	}

	@Override
	public void run() {
		try {
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);
		} catch (IOException e) {
			log.error("Couldn't open communication channel", e);
			closeCommChannel();
			return;
		}

		while (!clientSocket.isClosed()) {
			String cmd;
			try {
				cmd = in.readLine();
			} catch (IOException e) {
				log.error("Couldn't read command from communication channel");
				closeCommChannel();
				return;
			}

			if (cmd == null) {
				log.info("Connection to {} closed by client", clientSocket.getInetAddress());
				shutDown();
				break;
			}

			switch (cmd) {
			case "WRK":
				sendWork();
				break;
			case "RES":
				retrieveResult();
				break;
			default:
				assert false;
			}
		}
	}

	private void closeCommChannel() {
		if (in != null) {
			try {
				in.close();
			} catch (IOException e) {
				log.error("Couldn't close communication channel cleanly", e);
			}
		}
		if (out != null) {
			out.flush();
			out.close();
		}
	}

	private void sendWork() {
		KeyServer ks = KeyServer.getInstance();
		KeyRange kr;

		synchronized (ks) {
			kr = ks.getRange();
			ks.setInWork(kr);
		}

		String work = Long.toHexString(kr.getStart()) + " " + Long.toHexString(kr.getEnd()) + " " + kr.getTotal();

		out.println(work);

		log.debug("Work <{}> sent to {}", work, clientSocket.getInetAddress());
	}

	private void retrieveResult() {
		String[] result;
		try {
			result = in.readLine().split(" ");
			KeyRange kr = new KeyRange(Long.parseLong(result[0], 16), Long.parseLong(result[1], 16));

			synchronized (KeyServer.getInstance()) {
				KeyServer.getInstance().setSearched(kr);
			}

			log.debug("Got result for {}", kr);

		} catch (IOException e) {
			log.error("Couldn't read result", e);
		}
	}

	private void shutDown() {
		closeCommChannel();
		if (!clientSocket.isClosed()) {
			try {
				clientSocket.close();
			} catch (IOException e) {
				log.error("Couldn't close client socket", e);
			}
		}
	}
}
