(async () => {

	// load modules
	const assets=require('assets');
	const Popup=require('popup');
	const SnekGame=require('snek');
	const configEditor=require('configEditor');
	const input=require('input');
	const levels=require('levels');
	const config=require('config');

	// get a known state
	await new Promise(ok => assets.onReady(ok));
	location.hash='menu';

	// get our DOM in check
	const main=document.querySelector('main');
	const nav=main.querySelector('nav');
	const canvas=main.querySelector('canvas');
	const hud=main.querySelector('#hud');

	// load data from server
	const levelList=assets.get('levelList');

	// get our global variables
	let currentGame=null;

	// forward-declare functions
	let resizeCanvas, getLevel, startGame, stopGame, handleWin, handleDeath, menu, help, settings, restart;

	// handle window resize and fullscreen
	resizeCanvas=() => {
		if(document.fullscreenElement) {
			canvas.width=screen.width;
			canvas.height=screen.height;
		} else {
			canvas.width=main.clientWidth;
			canvas.height=main.clientHeight;
		}
	};
	resizeCanvas();
	window.addEventListener('resize', resizeCanvas);
	window.addEventListener('keydown', async e => {
		if(e.key=='f') {
			if(document.fullscreenElement) await document.exitFullscreen();
			else await main.requestFullscreen();
			resizeCanvas();
		}
	});

	// get a level for a category and an id
	getLevel=(category, id) => {
		const cat=levelList[category];
		id=''+id;

		const displayName=cat.levelDisplay
			.replace(/<n>/g, id)
			.replace(/<l>/g, id.toLowerCase());
		const fileName=cat.levelFilename
			.replace(/<n>/g, id)
			.replace(/<l>/g, id.toLowerCase());
		const levelString=category+'/'+id+'/'+fileName;

		return {
			displayName,
			fileName,
			levelString
		};
	};

	// buid menu from level list
	Object.keys(levelList).forEach(category => {
		const cat=levelList[category];

		const section=nav.appendChild(document.createElement('section'));
		const h1=section.appendChild(document.createElement('h1'));
		h1.innerText=category[0].toUpperCase()+category.slice(1)+" Mode";

		const p=section.appendChild(document.createElement('p'));
		p.innerText=cat.desc;

		const ul=section.appendChild(document.createElement('ul'));
		cat.levels.forEach((level, i) => {
			const {displayName, fileName, levelString}=getLevel(category, level);
			const li=ul.appendChild(document.createElement('li'));
			const a=li.appendChild(document.createElement('a'));
			a.href='#'+levelString;
			a.innerText=displayName;
			if(cat.levelDesc) {
				const span=li.appendChild(document.createElement('span'));
				span.innerText=cat.levelDesc[i];
			}
		});
	});

	// stop a running game
	stopGame=() => {
		if(currentGame) {
			// stop the actual game
			currentGame.playing=false;

			// setup the DOM
			nav.classList.remove('hidden');
			canvas.classList.add('hidden');
			hud.classList.add('hidden');
		}
	};

	// start a new game
	startGame=async (category, levelId, filename) => {
		// stop any running games and clear popups
		stopGame();
		Popup.dismiss();

		// load rules and level from cache or server
		const rules=levelList[category].rules || {};
		const level=await levels.get(filename);

		// stop any running games and clear popups again
		stopGame();
		Popup.dismiss();

		// create the game and attach the callbacks and config
		const snek=currentGame=new SnekGame(level, canvas, rules);
		snek.callback=evt => {
			if(evt=='tick') {
				input.framefn();
				snek.handleInputs(input.inputs);
			} else if(evt=='win') {
				handleWin(snek);
			} else if(evt=='die') {
				handleDeath(snek);
			}
		};

		// setup the DOM
		nav.classList.add('hidden');
		canvas.classList.remove('hidden');
		hud.classList.remove('hidden');

		// push some userdata to the snake
		snek.userdata={
			category,
			levelId,
			filename
		};

		// reset the inputs
		input.clear();

		// start the actual game
		snek.start();
	};

	// return to the menu
	menu=() => {
		stopGame();
		Popup.dismiss();
	};

	// show config editor
	settings=async () => {
		stopGame();
		Popup.dismiss();
		await configEditor.show();
		location.hash='menu';
	};

	// show help page
	help=async () => {
		stopGame();
		Popup.dismiss();
		let iframe=document.createElement('iframe');
		iframe.src='help.html';
		iframe.style.width='100%';
		iframe.style.height='100%';
		await new Popup(
			"Help",
			[iframe],
			{ok: "OK"},
			true
		).display();
		location.hash='menu';
	};

	// display the win popup
	handleWin=async snek => {
		// hide the HUD
		hud.classList.add('hidden');

		// fetch userdata from the game
		const {category, levelId, filename}=snek.userdata;

		// create and configure popup
		let popup=new Popup("Finished!");
		popup.addStrong("You won!");
		popup.addContent({
			"Time": snek.playTime/1000+'s',
			"Score": snek.score,
			"Final length": snek.length
		});
		popup.buttons={
			retry: "Retry",
			menu: "Main menu"
		};
		if(levelList[category].nextLevel) {
			let nextId=(+levelId)+1;
			if(levelList[category].levels.includes(nextId)) popup.buttons.next="Next level";
		}

		// show the actual popup
		let result=await popup.display(main);

		// act on it
		if(result=='retry') {
			startGame(category, levelId, filename);
		} else if(result=='menu') {
			location.hash='menu';
		} else if(result=='next') {
			const {category, levelId}=snek.userdata;
			let nextId=(+levelId)+1;
			let {levelString}=getLevel(category, nextId)
			location.hash=levelString;
		}
	};

	// display the death popup
	handleDeath=async snek => {
		// hide the HUD
		hud.classList.add('hidden');

		// create and configure popup
		let popup=new Popup("Finished!");
		popup.addStrong("You died...");
		popup.addContent({
			"Time": snek.playTime/1000+'s',
			"Score": snek.score,
			"Final length": snek.length
		});
		popup.buttons={
			retry: "Retry",
			menu: "Main menu"
		};

		// show the actual popup
		let result=await popup.display(main);

		// act on it
		if(result=='retry') {
			const {category, levelId, filename}=snek.userdata;
			startGame(category, levelId, filename);
		} else if(result=='menu') {
			location.hash='menu';
		}
	};

	// quick restart
	restart=() => {
		if(currentGame && currentGame.playing) {
			const {category, levelId, filename}=currentGame.userdata;
			startGame(category, levelId, filename);
		}
	}
	window.addEventListener('keydown', e => {
		if(e.key=='r') restart();
	});
	(() => {
		let restartbtn=hud.appendChild(document.createElement('span'));
		restartbtn.classList.add('restart');
		restartbtn.addEventListener('click', restart);
		restartbtn.addEventListener('touchend', restart);
	})();

	// handle page navigation
	window.addEventListener('hashchange', () => {
		const hash=location.hash.substr(1);

		if(hash=='' || hash=='menu') return menu();
		else if(hash=='help') return help();
		else if(hash=='settings') return settings();

		const [_, category, levelId, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/([a-zA-Z0-9_-]+?)\/(.+)/);
		startGame(category, levelId, filename);
	});

	// enable input methods overlay
	input.init({hud});
})();
