type type_event =
{
	timestamp: int;
	kind: string;
	data: any;
};


type type_user =
{
	name: string;
	role: string;
};


type type_connection =
{
	events: Array<type_event>;
	users: Array<type_user>;
	client: any;
};


type type_internal_request =
{
	id: (null | string);
	action: string;
	data: any;
};


type type_internal_response = any;


function get_timestamp(): int
{
	return Math.floor(Date.now()/1000);
}

function generate_id(): string
{
	return (Math.random() * (1 << 24)).toFixed(0).padStart(8, '0');
}


var nm_irc: any = require("irc");


var _connections: Record<string, type_connection> = {};


var _conf: any = {};


function log(level: int, incident: string, details: Record<string, any> = {}): void
{
	if (level <= _conf["verbosity"])
	{
		process.stderr.write(`-- ${incident} | ${lib_json.encode(details)}\n`);
	}
}


function get_connection(id: string): type_connection
{
	if (! _connections.hasOwnProperty(id))
	{
		throw (new Error("no connection for ID '" + id + "'"));
	}
	else
	{
		return _connections[id];
	}
}


async function execute(internal_request: type_internal_request, ip_address: string): Promise<type_internal_response>
{
	switch (internal_request.action)
	{
		default:
		{
			throw (new Error("unhandled action: " + internal_request.action));
			break;
		}
		case "connect":
		{
			if (_connections.hasOwnProperty(internal_request.id))
			{
				throw (new Error("already connected"));
			}
			else
			{
				const id: string = generate_id();
				const client = new nm_irc.Client
				(
					internal_request.data["server"],
					internal_request.data["nickname"],
					{
						"userName": lib_sha256.get(ip_address).slice(0, 8),
						"channels": internal_request.data["channels"],
						"showErrors": true,
						"autoConnect": false,
					}
				);
				const connection: type_connection = 
				{
					"client": client,
					"events": [],
					"users": [],
				};
				client.addListener
				(
					"message",
					(from, to, message) =>
					{
						connection.events.push
						({
							"timestamp": get_timestamp(),
							"kind": "channel_message",
							"data":
							{
								"from": from,
								"to": to,
								"message": message,
							}
						});
					}
				);
				client.addListener
				(
					"pm",
					(from, message) =>
					{
						connection.events.push
						({
							"timestamp": get_timestamp(),
							"kind": "private_message",
							"data":
							{
								"from": from,
								"message": message,
							}
						});
					}
				);
				client.addListener
				(
					"names",
					(channel, users) =>
					{
						connection.users = Object.entries(users).map(([key, value]) => ({"name": key, "role": value.toString()}));
					}
				);
				client.addListener
				(
					"error",
					(error) =>
					{
						log(0, "irc_error", {"reason": error.message});
					}
				);
				client.connect
				(
					3,
					() =>
					{
						_connections[id] = connection;
					}
				);
				return Promise.resolve<type_internal_response>(id);
			}
			break;
		}
		case "check":
		{
			try
			{
				get_connection(internal_request.id);
				return Promise.resolve<type_internal_response>(true);
			}
			catch (error)
			{
				return Promise.resolve<type_internal_response>(false);
			}
			break;
		}
		case "disconnect":
		{
			const connection: type_connection = get_connection(internal_request.id);
			delete _connections[internal_request.id];
			connection.client.disconnect("", () => {});
			return Promise.resolve<type_internal_response>(null);
			break;
		}
		case "say":
		{
			const connection: type_connection = get_connection(internal_request.id);
			connection.client.say(internal_request.data["channel"], internal_request.data["message"]);
			return Promise.resolve<type_internal_response>(null);
			break;
		}
		case "fetch":
		{
			const connection: type_connection = get_connection(internal_request.id);
			const internal_response: type_internal_response =
			{
				"users": connection.users,
				"events": connection.events,
			};
			connection.events = [];
			return Promise.resolve<type_internal_response>(internal_response);
			break;
		}
	}
}

async function main(): Promise<void>
{
	_conf = await lib_plankton.file.read("conf.json").then<any>(x => lib_json.decode(x));
	const server: lib_server.class_server = new lib_server.class_server
	(
		_conf["port"],
		async (input: string, metadata?: lib_server.type_metadata): Promise<string> =>
		{
			const http_request: lib_http.type_request = lib_http.decode_request(input);
			log(2, "http_request", http_request);
			const internal_request: type_internal_request = lib_json.decode(http_request.body);
			log(1, "internal_request", internal_request);
			let internal_response: type_internal_response;
			let error: (null | Error);
			try
			{
				internal_response = await execute(internal_request, metadata.ip_address);
				error = null;
			}
			catch (error_)
			{
				internal_response = null;
				error = error_;
			}
			if (error !== null)
			{
				log(0, "error_in_execution", {"reason": error.toString()});
			}
			const http_response: lib_http.type_response =
			(
				(error !== null)
				? {
					"statuscode": 500,
					"headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "text/plain"},
					"body": "error executing the request; check the server logs for details",
				}
				: {
					"statuscode": 200,
					"headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "application/json"},
					"body": lib_json.encode(internal_response)
				}
			);
			log(2, "http_response", http_response);
			const output: string = lib_http.encode_response(http_response);
			return Promise.resolve<string>(output);
		}
	);
	return server.start();
}


main();