package de.schunterkino.kinoapi.dolby;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DolbySocketCommands implements Runnable {

	private class CommandContainer {
		public Commands cmd;
		public int value;

		public CommandContainer(Commands cmd) {
			this.cmd = cmd;
			this.value = 0;
		}

		public CommandContainer(Commands cmd, int value) {
			this.cmd = cmd;
			this.value = value;
		}
	}

	private final CommandContainer noneCommand = new CommandContainer(Commands.None);

	private Socket socket;
	private boolean stop;
	private LinkedList<CommandContainer> commandQueue;
	private CommandContainer currentCommand;
	private Pattern faderPattern;
	private int volume;
	private Instant lastGetVolume;

	private LinkedList<IDolbyStatusUpdateReceiver> listeners;

	public DolbySocketCommands(Socket socket) {
		this.socket = socket;
		this.faderPattern = Pattern.compile("cp750\\.sys\\.fader (\\d+)");
		this.commandQueue = new LinkedList<>();
		this.listeners = new LinkedList<>();
		this.lastGetVolume = null;
	}

	public void run() {

		// Notify listeners.
		synchronized (listeners) {
			for (IDolbyStatusUpdateReceiver listener : listeners) {
				listener.onDolbyConnected();
			}
		}

		// Go in a loop to
		try {
			do {

				// We're waiting on a response for that command. See if there's
				// something here.
				if (currentCommand.cmd != Commands.None) {
					String ret = read();
					// Nothing yet. Keep waiting.
					if (ret == null)
						continue;

					System.out.println("Dolby: Current command: " + currentCommand.cmd + ". Received: " + ret);

					switch (currentCommand.cmd) {
					case GetVolume:
						// Parse the response
						Matcher matcher = faderPattern.matcher(ret);
						// Wait until we get the desired response.
						while (matcher.find()) {
							String volume = matcher.group(1);
							if (volume != null) {
								updateVolumeValue(Integer.parseInt(volume));
								currentCommand = noneCommand;
							}
						}
						break;
					default:
						currentCommand = noneCommand;
						break;
					}
				}

				// See if someone wanted to send some command.
				currentCommand = noneCommand;
				synchronized (commandQueue) {
					if (!commandQueue.isEmpty())
						currentCommand = commandQueue.removeFirst();
				}

				// Throw in a GetVolume command from time to time if the
				// current command is None.
				if (currentCommand.cmd == Commands.None) {
					// Get the current volume every 5 seconds.
					// TODO: Increase the interval if no websocket clients are
					// connected.
					if (lastGetVolume == null || Duration.between(lastGetVolume, Instant.now()).toMillis() > 5000)
						currentCommand = new CommandContainer(Commands.GetVolume);
				}

				// Send the right command now.
				String command = null;
				switch (currentCommand.cmd) {
				case None:
					break;
				case GetVolume:
					command = "cp750.sys.fader ?";
					lastGetVolume = Instant.now();
					break;
				case SetVolume:
					command = "cp750.sys.fader " + currentCommand.value;
					break;
				case IncreaseVolume:
					command = "cp750.ctrl.fader_delta 1";
					break;
				case DecreaseVolume:
					command = "cp750.ctrl.fader_delta -1";
					break;
				}

				if (command != null) {
					socket.getOutputStream().write((command + "\r\n").getBytes(Charset.forName("ascii")));
					System.out.println("Dolby: Sent: " + command);
				}

				// Wait a bit until processing the next command.
				Thread.sleep(500);
			} while (!stop);
		} catch (IOException | InterruptedException e) {
			if (!stop) {
				System.err.println("Dolby: Error in reader thread: " + e.getMessage());
				e.printStackTrace();
			}
		}

		// Notify listeners.
		synchronized (listeners) {
			for (IDolbyStatusUpdateReceiver listener : listeners) {
				listener.onDolbyDisconnected();
			}
		}
	}

	public void increaseVolume() {
		synchronized (commandQueue) {
			commandQueue.add(new CommandContainer(Commands.IncreaseVolume));
			// Get the new volume right away afterwards.
			commandQueue.add(new CommandContainer(Commands.GetVolume));
		}
	}

	public void decreaseVolume() {
		synchronized (commandQueue) {
			commandQueue.add(new CommandContainer(Commands.DecreaseVolume));
			// Get the new volume right away afterwards.
			commandQueue.add(new CommandContainer(Commands.GetVolume));
		}
	}

	public int getVolume() {
		return volume;
	}

	public void setVolume(int volume) {
		synchronized (commandQueue) {
			commandQueue.add(new CommandContainer(Commands.SetVolume, volume));
			// Get the new volume right away afterwards.
			commandQueue.add(new CommandContainer(Commands.GetVolume));
		}
	}

	private String read() throws IOException {
		InputStream in = socket.getInputStream();
		byte[] buffer = new byte[1024];

		int ret_read = in.read(buffer);
		if (ret_read == -1)
			throw new IOException("EOF");

		if (ret_read == 0)
			return null;

		return new String(buffer, 0, ret_read);
	}

	public void stop() {
		stop = true;
	}

	public void registerListener(IDolbyStatusUpdateReceiver listener) {
		synchronized (listeners) {
			listeners.add(listener);
		}
	}

	private void updateVolumeValue(int volume) {
		this.volume = volume;

		// Notify listeners.
		synchronized (listeners) {
			for (IDolbyStatusUpdateReceiver listener : listeners) {
				listener.onVolumeChanged(volume);
			}
		}
	}
}

@@ -1054,16 +1054,116 @@
 socket;%0A
+%09%09this.stop = false;%0A%09%09this.commandQueue = new LinkedList%3C%3E();%0A%09%09this.currentCommand = noneCommand;%0A
 %09%09this.f
@@ -1234,41 +1234,48 @@
 his.
-commandQueue = new LinkedList%3C%3E()
+volume = -1;%0A%09%09this.lastGetVolume = null
 ;%0A%09%09
@@ -1316,39 +1316,21 @@
 );%0A%09
-%09this.lastGetVolume = null;%0A%09%7D%0A
+%7D%0A%0A%09@Override
 %0A%09pu
@@ -1539,17 +1539,16 @@
 %09%09%09do %7B%0A
-%0A
 %09%09%09%09// W

Fix trying to access uninitialized variables