//          Copyright Chris Josten 2020
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)
import std.array;
import std.algorithm;
import std.datetime;
import std.exception;
import std.format;
import std.random;
import std.range;

import vibe.vibe;

immutable size_t BUF_SIZE = 2048;

struct Staat {
		bool verbondenMetKerstman = false;
		long trommelenVerbodenTot = 0;
}


shared Staat staat;
shared ManualEvent staatGebeurtenis;
shared Task kerstmanTaak;

__gshared TaskMutex mutex;
shared static this() {
		staatGebeurtenis = createSharedManualEvent();
		mutex = new TaskMutex();
}

struct TrommelCommando {};

@trusted
void kerstezelProtocolLuister(TCPConnection verbinding) nothrow {
		try {
				auto lock = scopedMutexLock(mutex);
				new Sessie(verbinding).voerUit();
		} catch (Exception e) {
				logException(e, "Kon mutex niet verkrijgen");
		}
}

class UnicodeUitzondering : Exception {
		mixin basicExceptionCtors;
}

class ProtocolUitzondering : Exception {
		mixin basicExceptionCtors;
}

class Sessie {
		private TCPConnection l_verbinding;
		private bool blijfBezig = true;
		private SysTime trommelenVerbodenTot;
		private bool trommelenVerboden = false;

		@safe
		this(TCPConnection verbinding) nothrow {
				this.l_verbinding = verbinding;
		}

		@trusted
		public void voerUit() nothrow {
			logInfo("Nieuwe sessie probeert te verbinden");
			try {
				handGeven();
				logInfo("Sessie is verbonden");
				staat.verbondenMetKerstman = true;
				kerstmanTaak = Task.getThis();
				staatGebeurtenis.emit();

				while(true) {
					auto tijdsduur = trommelenVerboden
						? min(trommelenVerbodenTot - Clock.currTime, seconds(30))
						: seconds(30);

					bool received = receiveTimeout(tijdsduur,
						(TrommelCommando com) {
							logInfo("Trommelen");
							auto now = Clock.currTime();
							if (trommelenVerboden) return;
							l_verbinding.write("trommel\n");
							l_verbinding.flush();
							trommelenVerboden = true;
							trommelenVerbodenTot = now + seconds(30);
							staat.trommelenVerbodenTot = trommelenVerbodenTot.toUnixTime;
							staatGebeurtenis.emit();

							ubyte[] read = l_verbinding.readLine(BUF_SIZE, "\n");
							enforce!ProtocolUitzondering(read.equal("oké"), "Kerstman deed geen erkenning, maar deed %s".format(read));
						}
					);
					if (Clock.currTime() >= trommelenVerbodenTot) {
						trommelenVerboden = false;
						staatGebeurtenis.emit();
					}
					if (!received) {
						l_verbinding.write("ping\n");
						l_verbinding.flush();
						ubyte[] read = l_verbinding.readLine(BUF_SIZE, "\n");
						enforce!ProtocolUitzondering(read.equal("pong"), "Kerstman pongde niet terug, maar deed %s".format(read));
					}
				}
			} catch (ProtocolUitzondering u) {
				logException(u, "Protocoluitzondering");
			} catch (Exception e) {
				logException(e, "Andere uitzondering");
			} finally {
				logInfo("Sessie verbroken");
				staat.verbondenMetKerstman = false;
				staatGebeurtenis.emit();
				sluit();
			}
		}

		@safe
		private void handGeven () {
				// Maak een begroeting
				string begroeting2 = "e gaat 'ie?\n";
				// Maak het irritant om het met de hand te doen :P
				int aantal = cast(int) (rndGen.front % 200) + 100;
				logInfo("Aantal: %d".format(aantal));
				rndGen.popFront();

				string begroeting = "ho".replicate(aantal) ~ begroeting2;
				l_verbinding.write(begroeting);
				l_verbinding.flush();

				// Wacht op een antwoord in de vorm van (aantal * "go" + "ed!")
				ubyte[] data = l_verbinding.readLine(BUF_SIZE, "\n");
				string expected = "go".replicate(aantal) ~ "ed";
				enforce!ProtocolUitzondering(equal(data, expected), "Ongeldige handdruk");
		}

		@safe
		private void sluit() nothrow {
				try {
						l_verbinding.close();
				} catch(Exception e) {
						
				}
		}

}
