const networkClock = 1000 / 16;

var allowedIDCharacters = "0123456789ABCDEF";
var tokenCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-";

var generateAccessToken = function () {
	var token = "";

	for (var c = 0; c < 64; c++) {
		token += tokenCharacters[
			Math.floor(
				Math.random() * tokenCharacters.length
			)
		];
	}

	return token;
};

var initMatrixClient = function (baseURL, mxID, withCrypto = false) {
	return new Promise(function (resolve, reject) {
		var matrixAccessToken = localStorage.getItem("matrix_access_token");
		if (!matrixAccessToken) {
			matrixAccessToken = generateAccessToken();
			localStorage.setItem("matrix_access_token", matrixAccessToken);
		}

		var matrixClient = global.matrixcs.createClient({
			"baseUrl": baseURL,
			"accessToken": matrixAccessToken,
			"userId": mxID,
			"deviceId": "XVoxel"
		});

		module.exports.matrixClient = matrixClient;

		resolve(matrixClient);

		if (withCrypto) {
			matrixClient.initCrypto().then(function () {

			}).catch(console.error);
		}
	});
};

var loginToMatrix = function (username, password) {
	return new Promise(function (resolve, reject) {
		if (!("matrixClient" in module.exports)) return reject(new Error("Matrix client hasn't been initialized yet"));
		module.exports.matrixClient.loginWithPassword(username, password).then(function (result) {
			var matrixAccessToken = module.exports.matrixClient.getAccessToken();
			localStorage.setItem("matrix_access_token", matrixAccessToken);
			resolve(result);
		}).catch(reject);
	});
};

var startMatrixClient = function () {
	module.exports.matrixClient.startClient();

	module.exports.matrixClient.on("sync", function (state, previousState, response) {
		if (state === "PREPARED") {
			var buttonReadyInterval = setInterval(function () {
				var mxWJBtn = document.querySelector("#matrixworldjoin");

				if (!mxWJBtn) return;

				mxWJBtn.disabled = false;
				clearInterval(buttonReadyInterval);
			}, 256);
		}
	});
};

var createWorldRoom = function (name) {
	return new Promise(function (resolve, reject) {
		module.exports.matrixClient.createRoom({
			"name": name,
			"visibility": "private",
			"topic": "A Voxel world",
			"creation_content": {
				"type": "m.world.voxel"
			}
		}).then(resolve).catch(reject);
	});
};

var getWorldRooms = function () {
	var rooms = module.exports.matrixClient.getRooms();

	if (!rooms) return [];

	return rooms.filter(room => room.getType() == "m.world.voxel");
};

var syncWorldFromMatrixRoom = function (room) {
	return new Promise(function (resolve, reject) {
		module.exports.matrixClient.getStateEvent(room.roomId, "m.world.voxel", "save").then(function (event) {
			fetch(module.exports.matrixClient.mxcUrlToHttp(event["save_uri"])).then(function (response) {
				response.arrayBuffer().then(function (arrayBuffer) {
					global.openSave(arrayBuffer).then(function (loadedWorlds) {
						for (var worldName in loadedWorlds) {
							if (loadedWorlds.hasOwnProperty(worldName)) {
								global.worlds[worldName] = loadedWorlds[worldName];
							}
						}

						resolve(global.worlds[room.name]);
					}).catch(reject);
				}).catch(reject);
			}).catch(reject);
		}).catch(reject);
	});
};

var syncWorldToMatrixRoom = function (room) {
	return new Promise(function(resolve, reject) {
		global.makeSave().then(function (archive) {
			module.exports.matrixClient.uploadContent(archive, {
				"name": room.name + ".zip",
				"type": "application/zip"
			}).then(function (response) {
				module.exports.matrixClient.sendStateEvent(room.roomId, "m.world.voxel", {
					"save_uri": response.content_uri
				}, "save").then(resolve).catch(reject);
			}).catch(reject);
		}).catch(reject);
	});
};

var connectMatrix = function (room) {
	return new Promise(function (resolve, reject) {
		var groupCall = module.exports.matrixClient.getGroupCallForRoom(room.roomId);
		if (!groupCall) {
			return module.exports.matrixClient.createGroupCall(
				room.roomId, global.matrixcs.GroupCallType["Voice"],
				false, global.matrixcs.GroupCallIntent["Room"], true
			).then(function (call) {
				call.initWithAudioMuted = true;
				call.initWithVideoMuted = true;
				call.setMicrophoneMuted(true);
				call.enter();
				resolve(call);
			}).catch(console.error);
		}
		groupCall.initWithAudioMuted = true;
		groupCall.initWithVideoMuted = true;
		groupCall.setMicrophoneMuted(true);
		groupCall.enter();
		resolve(groupCall);
	});
};

if (localStorage.getItem("matrix_is_connected")) {
	initMatrixClient(
		localStorage.getItem("matrix_baseurl"),
		localStorage.getItem("matrix_mxid")
	).then(function (matrixClient) {
		module.exports.matrixClient = matrixClient;
		startMatrixClient();
	}).catch(function (error) {
		console.error(error);
		localStorage.removeItem("matrix_is_connected");
	});
}

var connectRTC = function (options) {
	var randomID = (options.username || "Guest") + "-";
	for (var c = 0; c < 16; c++) {
		randomID += allowedIDCharacters[
			Math.floor(
				Math.random() * allowedIDCharacters.length
			)
		];
	}

	var peerNetwork = new global.peer.default(randomID, {
		"host": options.host,
		"port": options.port,
		"path": options.path
	});

	return peerNetwork;
};

var connect = function (url) {
	try {
		var connectionURL = new URL(url);
	} catch (error) {
		global.throwError(error);
	}

	var splitPathName = connectionURL.pathname.split(/[\/]/);

	var pathNameParts = splitPathName[2].split("@");

	if (pathNameParts.length > 1) {
		var pathNameCredentials = pathNameParts.split(":");

		var pathNameUsername = pathNameCredentials[0];

		if (pathNameCredentials.length > 1) {
			var pathNamePassword = pathNameCredentials[1];
		}

		var pathNameConnectionDataIndex = 1;
	} else {
		var pathNameConnectionDataIndex = 0;
	}

	var pathNameConnectionData = pathNameParts[pathNameConnectionDataIndex].split(":");

	var pathNameAddress = pathNameConnectionData[0];

	if (pathNameConnectionData.length > 1) {
		var pathNamePort = pathNameConnectionData[1];
	}

	var pathNamePath = "/" + splitPathName.slice(3).join("/");

	switch (connectionURL.protocol) {
		case "rtc:":
			return connectRTC({
				"host": pathNameAddress,
				"port": pathNamePort || 443,
				"path": pathNamePath || "/",
				"username": pathNameUsername || "Guest",
				"password": pathNamePassword || null
			});
			break;
		case "ws:":

			break;
		case "wss:":

			break;
		case "http:":

			break;
		case "https:":

			break;
		default:

	}
};

module.exports = {
	"initMatrixClient": initMatrixClient,
	"loginToMatrix": loginToMatrix,
	"startMatrixClient": startMatrixClient,
	"createWorldRoom": createWorldRoom,
	"getWorldRooms": getWorldRooms,
	"connectMatrix": connectMatrix,
	"connect": connect,
	"menus": {
		"play": {
			"elements": [
				{
					"type": "button",
					"label": "Multiplayer",
					"action": "open-menu",
					"action-args": ["serversel", 2]
				}
			]
		},
		"serversel": {
			"elements": [
				{
					"type": "text",
					"id": "serveraddressinput",
					"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.label"),
					"placeholder": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.placeholder"),
					"value": "",
					"action": "run",
					"runnable": function (event) {
						var serverConnectButton = global.document.getElementById("serverconnectbutton");

						if (event.path[0].value.length < 1) {
							serverConnectButton.disabled = true;
						} else {
							try {
								new URL(event.path[0].value);

								serverConnectButton.disabled = false;
							} catch (e) {
								serverConnectButton.disabled = true;
							}
						}
					}
				},
				{
					"type": "button",
					"id": "serverconnectbutton",
					"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serverconnectbutton.label"),
					"disabled": true,
					"action": "run",
					"runnable": function (event) {
						var serverAddressInputBox = global.document.getElementById("serveraddressinput");

						connect(serverAddressInputBox.value);
					}
				},
				{
					"type": "button",
					"id": "matrixworldjoin",
					"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.loadmatrixworldsbutton.label"),
					"disabled": true,
					"action": "run",
					"runnable": function (event) {
						for (var e = module.exports.menus["serversel"].elements.length - 1; e >= 0; e--) {
							var menuElement = module.exports.menus["serversel"].elements[e];

							if (("classes" in menuElement) && menuElement.classes.indexOf("mxworldjoinbtn") > -1) module.exports.menus["serversel"].elements.splice(e, 1);
						}

						var matrixWorldRooms = getWorldRooms();

						for (var r = 0; r < matrixWorldRooms.length; r++) {
							var matrixWorldRoom = matrixWorldRooms[r];

							var menuEntry = {
								"type": "button",
								"label": global.locales.getFormattedString(
									global.currentLanguage,
									"play.singleplayer.openworld.label",
									{
										"WORLD_NAME": matrixWorldRoom.name
									}
								),
								"action": "run",
								"runnable": (function (event) {
									syncWorldFromMatrixRoom(this).then((function (world) {
										global.worlds[this.name] = world;
										global.startGame(0, this.name);
									}).bind(this)).catch(global.throwError);

									global.menu.hideMenus(0);
								}).bind(matrixWorldRoom),
								"classes": [
									"mxworldjoinbtn"
								]
							};

								module.exports.menus["serversel"].elements.push(menuEntry);
						}

						global.menu.hideMenus(2);

						global.menu.menus["serversel"] = global.menu.constructMenu("serversel", module.exports.menus["serversel"]);

						global.menu.showMenu("serversel", 2);
					}
				}
			]
		}
	}
};
