using System;
using System.Collections.Generic;
using System.Text;
using Webserver.UrlHandlers;

namespace Webserver.SSE {
	public abstract class AbsEvent {
		private const int encodingBufferSize = 1024 * 1024;

		private readonly SseHandler parent;
		public readonly string Name;

		private readonly byte[] encodingBuffer;
		private readonly StringBuilder stringBuilder = new StringBuilder ();

		private readonly List<SseClient> openClients = new List<SseClient> ();

		private readonly BlockingQueue<(string _eventName, string _data)> sendQueue =
			new BlockingQueue<(string _eventName, string _data)> ();

		private int currentlyOpen;
		private int totalOpened;
		private int totalClosed;

		protected AbsEvent (SseHandler _parent, bool _reuseEncodingBuffer = true, string _name = null) {
			Name = _name ?? GetType ().Name;
			parent = _parent;
			if (_reuseEncodingBuffer) {
				encodingBuffer = new byte[encodingBufferSize];
			}
		}

		public void AddListener (SseClient _client) {
			totalOpened++;
			currentlyOpen++;

			openClients.Add (_client);
		}

		protected void SendData (string _eventName, string _data) {
			sendQueue.Enqueue ((_eventName, _data));
			parent.SignalSendQueue ();
		}

		public void ProcessSendQueue () {
			while (sendQueue.HasData ()) {
				(string eventName, string data) = sendQueue.Dequeue ();
				
				stringBuilder.Append ("event: ");
				stringBuilder.AppendLine (eventName);
				stringBuilder.Append ("data: ");
				stringBuilder.AppendLine (data);
				
				stringBuilder.AppendLine ("");

				string output = stringBuilder.ToString ();
				stringBuilder.Clear ();

				byte[] buf;
				int bytesToSend;
				if (encodingBuffer != null) {
					buf = encodingBuffer;
					try {
						bytesToSend = Encoding.UTF8.GetBytes (output, 0, output.Length, buf, 0);
					} catch (ArgumentException e) {
						logError ("Exception while encoding data for output, most likely exceeding buffer size:", false);
						Log.Exception (e);
						return;
					}
				} else {
					buf = Encoding.UTF8.GetBytes (output);
					bytesToSend = buf.Length;
				}

				sendBufToListeners (buf, bytesToSend);
			}
		}

		private void sendBufToListeners (byte[] _bytes, int _bytesToSend) {
			for (int i = openClients.Count - 1; i >= 0; i--) {
				ESseClientWriteResult writeResult = openClients [i].Write (_bytes, _bytesToSend);
				if (writeResult == ESseClientWriteResult.Ok) {
					continue;
				}

				currentlyOpen--;
				totalClosed++;

				if (writeResult == ESseClientWriteResult.Error) {
					logError ("Can not write to endpoint, closing", true);
				}
			}
		}

		protected void logError (string _message, bool _printConnections) {
			Log.Error (_printConnections
				? $"[Web] [SSE] '{Name}': {_message} (Left open: {currentlyOpen}, total opened: {totalOpened}, closed: {totalClosed})"
				: $"[Web] [SSE] '{Name}': {_message}");
		}

		public virtual int DefaultPermissionLevel () => 0;

		public void ClientClosed (SseClient _client) {
			openClients.Remove (_client);
		}
	}
}