const fs = require('fs');
const path = require('path');

const project = new Project('Kinc');

function addKincDefine(name) {
	project.addDefine('KINC_' + name);
	project.addDefine('KORE_' + name);
}

const g1 = true;
addKincDefine('G1');

const g2 = true;
addKincDefine('G2');

const g3 = true;
addKincDefine('G3');

let g4 = false;

let g5 = false;

const a1 = true;
addKincDefine('A1');

const a2 = true;
addKincDefine('A2');

let a3 = false;

// Setting lz4x to false adds a BSD 2-Clause licensed component,
// which is a little more restrictive than Kinc's zlib license.
const lz4x = true;

project.addFile('Sources/**');

if (Options.kong) {
	project.addKongDir('KongShaders');
}
else {
	project.addFile('GLSLShaders/**');
}

if (lz4x) {
	addKincDefine('LZ4X');
	project.addExclude('Sources/kinc/io/lz4/**');
}
project.addIncludeDir('Sources');

function addBackend(name) {
	project.addFile('Backends/' + name + '/Sources/**');
	project.addIncludeDir('Backends/' + name + '/Sources');
}

let plugin = false;

if (platform === Platform.Windows) {
	addBackend('System/Windows');
	addBackend('System/Microsoft');
	project.addLib('dxguid');
	project.addLib('dsound');
	project.addLib('dinput8');

	project.addDefine('_CRT_SECURE_NO_WARNINGS');
	project.addDefine('_WINSOCK_DEPRECATED_NO_WARNINGS');
	project.addLib('ws2_32');
	project.addLib('Winhttp');

	const directshow = (graphics !== GraphicsApi.Direct3D12 && graphics !== GraphicsApi.Default);
	if (directshow) {
		project.addFile('Backends/System/Windows/Libraries/DirectShow/**');
		project.addIncludeDir('Backends/System/Windows/Libraries/DirectShow/BaseClasses');
		project.addLib('strmiids');
		project.addLib('winmm');
	}
	else {
		project.addDefine('KINC_NO_DIRECTSHOW');
	}

	project.addLib('wbemuuid');

	if (graphics === GraphicsApi.OpenGL1) {
		addBackend('Graphics3/OpenGL1');
		addKincDefine('OPENGL1');
		project.addDefine('GLEW_STATIC');
	}
	else if (graphics === GraphicsApi.OpenGL) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		addKincDefine('OPENGL');
		project.addDefine('GLEW_STATIC');
	}
	else if (graphics === GraphicsApi.Direct3D11) {
		g4 = true;
		addBackend('Graphics4/Direct3D11');
		addKincDefine('DIRECT3D');
		addKincDefine('DIRECT3D11');
		project.addLib('d3d11');
	}
	else if (graphics === GraphicsApi.Direct3D12 || graphics === GraphicsApi.Default) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Direct3D12');
		addKincDefine('DIRECT3D');
		addKincDefine('DIRECT3D12');
		project.addLib('dxgi');
		project.addLib('d3d12');
	}
	else if (graphics === GraphicsApi.Vulkan) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Vulkan');
		addKincDefine('VULKAN');
		project.addDefine('VK_USE_PLATFORM_WIN32_KHR');
		if (!process.env.VULKAN_SDK) {
			throw 'Could not find a Vulkan SDK';
		}
		project.addLibFor('x64', path.join(process.env.VULKAN_SDK, 'Lib', 'vulkan-1'));
		let libs = fs.readdirSync(path.join(process.env.VULKAN_SDK, 'Lib'));
		for (const lib of libs) {
			if (lib.startsWith('VkLayer_')) {
				project.addLibFor('x64', path.join(process.env.VULKAN_SDK, 'Lib', lib.substr(0, lib.length - 4)));
			}
		}
		project.addIncludeDir(path.join(process.env.VULKAN_SDK, 'Include'));
	}
	else if (graphics === GraphicsApi.Direct3D9) {
		g4 = true;
		addBackend('Graphics4/Direct3D9');
		addKincDefine('DIRECT3D');
		addKincDefine('DIRECT3D9');
		project.addLib('d3d9');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for Windows.');
	}

	if (audio === AudioApi.DirectSound) {
		addBackend('Audio2/DirectSound');
	}
	else if (audio === AudioApi.WASAPI || audio === AudioApi.Default) {
		addBackend('Audio2/WASAPI');
	}
	else {
		throw new Error('Audio API ' + audio + ' is not available for Windows.');
	}

	if (vr === VrApi.Oculus) {
		addKincDefine('VR');
		addKincDefine('OCULUS');
		project.addLibFor('x64', 'Backends/System/Windows/Libraries/OculusSDK/LibOVR/Lib/Windows/x64/Release/VS2017/LibOVR');
		project.addLibFor('Win32', 'Backends/System/Windows/Libraries/OculusSDK/LibOVR/Lib/Windows/Win32/Release/VS2017/LibOVR');
		project.addFile('Backends/System/Windows/Libraries/OculusSDK/LibOVRKernel/Src/GL/**');
		project.addIncludeDir('Backends/System/Windows/Libraries/OculusSDK/LibOVR/Include/');
		project.addIncludeDir('Backends/System/Windows/Libraries/OculusSDK/LibOVRKernel/Src/');
	}
	else if (vr === VrApi.SteamVR) {
		addKincDefine('VR');
		addKincDefine('STEAMVR');
		project.addDefine('VR_API_PUBLIC');
		project.addFile('Backends/System/Windows/Libraries/SteamVR/src/**');
		project.addIncludeDir('Backends/System/Windows/Libraries/SteamVR/src');
		project.addIncludeDir('Backends/System/Windows/Libraries/SteamVR/src/vrcommon');
		project.addIncludeDir('Backends/System/Windows/Libraries/SteamVR/headers');
	}
	else if (vr === VrApi.None) {

	}
	else {
		throw new Error('VR API ' + vr + ' is not available for Windows.');
	}
}
else if (platform === Platform.WindowsApp) {
	g4 = true;
	addKincDefine('WINDOWSAPP');
	addBackend('System/WindowsApp');
	addBackend('System/Microsoft');
	addBackend('Graphics4/Direct3D11');
	addBackend('Audio2/WASAPI_WinRT');
	project.addDefine('_CRT_SECURE_NO_WARNINGS');
	project.vsdeploy = true;

	if (vr === VrApi.HoloLens) {
		addKincDefine('VR');
		addKincDefine('HOLOLENS');
	}
	else if (vr === VrApi.None) {

	}
	else {
		throw new Error('VR API ' + vr + ' is not available for Windows Universal.');
	}
}
else if (platform === Platform.OSX) {
	addBackend('System/Apple');
	addBackend('System/macOS');
	addBackend('System/POSIX');
	if (graphics === GraphicsApi.Metal || graphics === GraphicsApi.Default) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Metal');
		addKincDefine('METAL');
		project.addLib('Metal');
		project.addLib('MetalKit');
	}
	else if (graphics === GraphicsApi.OpenGL1) {
		addBackend('Graphics3/OpenGL1');
		addKincDefine('OPENGL1');
		project.addLib('OpenGL');
	}
	else if (graphics === GraphicsApi.OpenGL) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		addKincDefine('OPENGL');
		project.addLib('OpenGL');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for macOS.');
	}
	project.addLib('IOKit');
	project.addLib('Cocoa');
	project.addLib('AppKit');
	project.addLib('CoreAudio');
	project.addLib('CoreData');
	project.addLib('CoreMedia');
	project.addLib('CoreVideo');
	project.addLib('AVFoundation');
	project.addLib('Foundation');
}
else if (platform === Platform.iOS || platform === Platform.tvOS) {
	if (platform === Platform.tvOS) {
		addKincDefine('TVOS');
	}
	addBackend('System/Apple');
	addBackend('System/iOS');
	addBackend('System/POSIX');
	if (graphics === GraphicsApi.Metal || graphics === GraphicsApi.Default) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Metal');
		addKincDefine('METAL');
		project.addLib('Metal');
	}
	else if (graphics === GraphicsApi.OpenGL) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		addKincDefine('OPENGL');
		addKincDefine('OPENGL_ES');
		project.addLib('OpenGLES');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for iOS.');
	}
	project.addLib('UIKit');
	project.addLib('Foundation');
	project.addLib('CoreGraphics');
	project.addLib('QuartzCore');
	project.addLib('CoreAudio');
	project.addLib('AudioToolbox');
	project.addLib('CoreMotion');
	project.addLib('AVFoundation');
	project.addLib('CoreFoundation');
	project.addLib('CoreVideo');
	project.addLib('CoreMedia');
}
else if (platform === Platform.Android) {
	addKincDefine('ANDROID');
	addBackend('System/Android');
	addBackend('System/POSIX');
	if (graphics === GraphicsApi.Vulkan || graphics === GraphicsApi.Default) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Vulkan');
		addKincDefine('VULKAN');
		project.addDefine('VK_USE_PLATFORM_ANDROID_KHR');
		project.addLib('vulkan');
		addKincDefine('ANDROID_API=24');
	}
	else if (graphics === GraphicsApi.OpenGL) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		addKincDefine('OPENGL');
		addKincDefine('OPENGL_ES');
		addKincDefine('ANDROID_API=19');
		project.addDefine('KINC_EGL');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for Android.');
	}
	project.addLib('log');
	project.addLib('android');
	project.addLib('EGL');
	if (Options.kong) {
		project.addLib('GLESv3');
	}
	else {
		project.addLib('GLESv2');
	}
	project.addLib('OpenSLES');
	project.addLib('OpenMAXAL');
}
else if (platform === Platform.Emscripten) {
	addKincDefine('EMSCRIPTEN');
	//project.addLib('websocket.js -sPROXY_POSIX_SOCKETS -sUSE_PTHREADS -sPROXY_TO_PTHREAD');
	addBackend('System/Emscripten');
	project.addLib('USE_GLFW=2');
	if (graphics === GraphicsApi.WebGPU) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/WebGPU');
		addKincDefine('WEBGPU');
	}
	else if (graphics === GraphicsApi.OpenGL || graphics === GraphicsApi.Default) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		project.addExclude('Backends/Graphics4/OpenGL/Sources/GL/**');
		addKincDefine('OPENGL');
		addKincDefine('OPENGL_ES');
		if (Options.kong) {
			project.addLib('USE_WEBGL2=1');
		}
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for Emscripten.');
	}
}
else if (platform === Platform.Wasm) {
	addKincDefine('WASM');
	addBackend('System/Wasm');
	project.addIncludeDir('miniClib');
	project.addFile('miniClib/**');
	if (graphics === GraphicsApi.WebGPU) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/WebGPU');
		addKincDefine('WEBGPU');
	}
	else if (graphics === GraphicsApi.OpenGL || graphics === GraphicsApi.Default) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		project.addExclude('Backends/Graphics4/OpenGL/Sources/GL/**');
		addKincDefine('OPENGL');
		addKincDefine('OPENGL_ES');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for Wasm.');
	}
}
else if (platform === Platform.Linux || platform === Platform.FreeBSD) {
	if (platform === Platform.FreeBSD) { // TODO
		addKincDefine('LINUX');
	}
	addBackend('System/Linux');
	addBackend('System/POSIX');
	project.addLib('asound');
	project.addLib('dl');

	if (platform === Platform.Linux) {
		project.addLib('udev');

		try {
			if (!fs.existsSync(targetDirectory)) {
				fs.mkdirSync(targetDirectory);
			}
			if (!fs.existsSync(path.join(targetDirectory, 'wayland'))) {
				fs.mkdirSync(path.join(targetDirectory, 'wayland'));
			}
			const waylandDir = path.join(targetDirectory, 'wayland', 'wayland-generated');
			if (!fs.existsSync(waylandDir)) {
				fs.mkdirSync(waylandDir);
			}

			const child_process = require('child_process');

			let good_wayland = false;

			const wayland_call = child_process.spawnSync('wayland-scanner', ['--version'], {encoding: 'utf-8'});
			if (wayland_call.status !== 0) {
				throw 'Could not run wayland-scanner to ask for its version';
			}
			const wayland_version = wayland_call.stderr;

			try {
				const scanner_versions = wayland_version.split(' ')[1].split('.');
				const w_x = parseInt(scanner_versions[0]);
				const w_y = parseInt(scanner_versions[1]);
				const w_z = parseInt(scanner_versions[2]);

				if (w_x > 1) {
					good_wayland = true;
				}
				else if (w_x === 1) {
					if (w_y > 17) {
						good_wayland = true;
					}
					else if (w_y === 17) {
						if (w_z >= 91) {
							good_wayland = true;
						}
					}
				}
			}
			catch (err) {
				log.error('Could not parse wayland-version ' + wayland_version);
			}

			let c_ending = '.c';
			if (good_wayland) {
				c_ending = '.c.h';
			}

			let chfiles = [];

			function wl_protocol(protocol, file) {
				chfiles.push(file);
				const backend_path = path.resolve(waylandDir);
				const protocol_path = path.resolve('/usr/share/wayland-protocols', protocol);
				if (child_process.spawnSync('wayland-scanner', ['private-code', protocol_path, path.resolve(backend_path, file + c_ending)]).status !== 0) {
					throw 'Failed to generate wayland protocol files for' + protocol;
				}
				if (child_process.spawnSync('wayland-scanner', ['client-header', protocol_path, path.resolve(backend_path, file + '.h')]).status !== 0) {
					throw 'Failed to generate wayland protocol header for' + protocol;
				}
			}

			if (child_process.spawnSync('wayland-scanner', ['private-code', '/usr/share/wayland/wayland.xml', path.resolve(waylandDir, 'wayland-protocol' + c_ending)]).status !== 0) {
				throw 'Failed to generate wayland protocol files for /usr/share/wayland/wayland.xml';
			}
			if (child_process.spawnSync('wayland-scanner', ['client-header', '/usr/share/wayland/wayland.xml', path.resolve(waylandDir, 'wayland-protocol.h')]).status !== 0) {
				throw 'Failed to generate wayland protocol header for /usr/share/wayland/wayland.xml';
			}
			wl_protocol('stable/viewporter/viewporter.xml', 'wayland-viewporter');
			wl_protocol('stable/xdg-shell/xdg-shell.xml', 'xdg-shell');
			wl_protocol('unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', 'xdg-decoration');
			wl_protocol('unstable/tablet/tablet-unstable-v2.xml', 'wayland-tablet');
			wl_protocol('unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 'wayland-pointer-constraint');
			wl_protocol('unstable/relative-pointer/relative-pointer-unstable-v1.xml', 'wayland-relative-pointer');

			if (good_wayland) {
				let cfile = '#include "wayland-protocol.c.h"\n';
				for (const chfile of chfiles) {
					cfile += '#include "' + chfile + '.c.h"\n';
				}
				fs.writeFileSync(path.resolve(waylandDir, 'waylandunit.c'), cfile);
			}

			project.addIncludeDir(path.join(targetDirectory, 'wayland'));
			project.addFile(path.resolve(waylandDir, '**'));
		}
		catch (err) {
			log.error('Failed to include wayland-support, setting KINC_NO_WAYLAND.');
			log.error('Wayland error was: ' + err);
			project.addDefine('KINC_NO_WAYLAND');
		}
	}
	else if (platform === Platform.FreeBSD) {
		addBackend('System/FreeBSD');
		project.addExclude('Backends/System/Linux/Sources/kinc/backend/input/gamepad.cpp');
		project.addExclude('Backends/System/Linux/Sources/kinc/backend/input/gamepad.h');
		project.addDefine('KINC_NO_WAYLAND');
	}

	if (graphics === GraphicsApi.Vulkan || (platform === Platform.Linux && graphics === GraphicsApi.Default)) {
		g4 = true;
		g5 = true;
		addBackend('Graphics5/Vulkan');
		project.addLib('vulkan');
		addKincDefine('VULKAN');
	}
	else if (graphics === GraphicsApi.OpenGL || (platform === Platform.FreeBSD && graphics === GraphicsApi.Default)) {
		g4 = true;
		addBackend('Graphics4/OpenGL');
		project.addExclude('Backends/Graphics4/OpenGL/Sources/GL/glew.c');
		project.addLib('GL');
		addKincDefine('OPENGL');
		project.addLib('EGL');
		project.addDefine('KINC_EGL');
	}
	else {
		throw new Error('Graphics API ' + graphics + ' is not available for Linux.');
	}
	if (platform === Platform.FreeBSD) { // TODO
		addKincDefine('POSIX');
	}
	project.addDefine('_POSIX_C_SOURCE=200112L');
	project.addDefine('_XOPEN_SOURCE=600');
}
else if (platform === Platform.Pi) {
	g4 = true;
	addKincDefine('RASPBERRY_PI');
	addBackend('System/Pi');
	addBackend('System/POSIX');
	addBackend('Graphics4/OpenGL');
	project.addExclude('Backends/Graphics4/OpenGL/Sources/GL/**');
	addKincDefine('OPENGL');
	addKincDefine('OPENGL_ES');
	addKincDefine('POSIX');
	project.addDefine('_POSIX_C_SOURCE=200112L');
	project.addDefine('_XOPEN_SOURCE=600');
	project.addIncludeDir('/opt/vc/include');
	project.addIncludeDir('/opt/vc/include/interface/vcos/pthreads');
	project.addIncludeDir('/opt/vc/include/interface/vmcs_host/linux');
	project.addLib('dl');
	project.addLib('GLESv2');
	project.addLib('EGL');
	project.addLib('bcm_host');
	project.addLib('asound');
	project.addLib('X11');
}
else {
	plugin = true;
	g4 = true;
	g5 = true;
}

if (g4) {
	addKincDefine('G4');
}
else {
	project.addExclude('Sources/Kore/Graphics4/**');
	project.addExclude('Sources/kinc/graphics4/**');
}

if (g5) {
	addKincDefine('G5');
	addKincDefine('G4ONG5');
	addBackend('Graphics4/G4onG5');
}
else {
	addKincDefine('G5');
	addKincDefine('G5ONG4');
	addBackend('Graphics5/G5onG4');
}

if (!a3) {
	if (cpp) {
		a3 = true;
		addKincDefine('A3');
		addBackend('Audio3/A3onA2');
	}
}

project.setDebugDir('Deployment');
project.kincProcessed = true;

if (plugin) {
	let backend = 'Unknown';
	if (platform === Platform.PS4) {
		backend = 'PlayStation4';
	}
	else if (platform === Platform.XboxOne) {
		backend = 'Xbox';
	}
	else if (platform === Platform.Switch) {
		backend = 'Switch';
	}
	else if (platform === Platform.XboxSeries) {
		backend = 'Xbox';
	}
	else if (platform === Platform.PS5) {
		backend = 'PlayStation5'
	}

	let ancestor = project;
	while (ancestor.parent != null) {
		ancestor = ancestor.parent;
	}

	let pluginPath = path.join(ancestor.basedir, 'Backends', backend);
	if (!fs.existsSync(pluginPath)) {
		pluginPath = path.join(__dirname, '..', 'Backends', backend);
	}
	if (!fs.existsSync(pluginPath)) {
		log.error('Was looking for a backend in ' + pluginPath + ' but it wasn\'t there.');
		throw 'backend not found';
	}
	await project.addProject(pluginPath);
	resolve(project);
}
else {
	resolve(project);
}