/* Copyright (c) 2019-2024 Griefer@Work                                       *
 *                                                                            *
 * This software is provided 'as-is', without any express or implied          *
 * warranty. In no event will the authors be held liable for any damages      *
 * arising from the use of this software.                                     *
 *                                                                            *
 * Permission is granted to anyone to use this software for any purpose,      *
 * including commercial applications, and to alter it and redistribute it     *
 * freely, subject to the following restrictions:                             *
 *                                                                            *
 * 1. The origin of this software must not be misrepresented; you must not    *
 *    claim that you wrote the original software. If you use this software    *
 *    in a product, an acknowledgement (see the following) in the product     *
 *    documentation is required:                                              *
 *    Portions Copyright (c) 2019-2024 Griefer@Work                           *
 * 2. Altered source versions must be plainly marked as such, and must not be *
 *    misrepresented as being the original software.                          *
 * 3. This notice may not be removed or altered from any source distribution. *
 */

BEGIN
	DEFINE({ "BUILDING_KERNEL_CORE" : "1" })
	SET_NOSTDLIB()

	local KENREL_LIBDL_BINARY_SOURCE = f"arch/{XARCH}/exec/libdl.S.incbin";
	local KERNEL_ASM_SOURCES = [
		"../../libc/hybrid/arch/" + XARCH_ASMFILES,
		"../../libunwind/arch/" + XARCH_ASMFILES,
		"../../libcpustate/arch/" + XARCH_ASMFILES,
		"arch/" + XARCH_ASMFILES,
	];
	local KERNEL_C_SOURCES = [
		/* Include libc auto- and hybrid-sources */
		/* TODO: Don't include all auto sources. - Only include those that actually
		 *       define anything outside of `#ifndef __KERNEL__'. - This should  be
		 *       automated by `generate_headers.dee' */
		"../../libc/auto/*.c",
		f"../../libc/hybrid/arch/{XARCH}/*.c",
		"../../libc/hybrid/*.c",

		/* Compile a couple of user-space libraries as part of the kernel core */

		/* Required for: Disasm integration within the debugger, and %[disasm] */
		"../../libdisasm/*.c",

		/* Required for: Addr2line support, and %[vinfo] */
		"../../libdebuginfo/*.c",

		/* Required for: Decompressing compressed .debug_* sections */
		"../../libzlib/*.c",

		/* Required for: Exception handlers to unify PC positions
		 * (exceptions always point _after_ the faulting instruction) */
		"../../libinstrlen/*.c",

		/* Required for: Exception handling, as well as tracebacks */
		f"../../libunwind/arch/{XARCH}/*.c",
		"../../libunwind/*.c",

		/* Required for: Kernel panic error logging */
		f"../../libregdump/arch/{XARCH}/*.c",
		"../../libregdump/*.c",

		/* Required for: General purpose cpu state operations */
		f"../../libcpustate/arch/{XARCH}/*.c",
		"../../libcpustate/*.c",

		/* Required for: High-level ANSI TTY escape codes (`\e[0m') */
		"../../libansitty/*.c",

		/* Required for: Low-level TTY support (`struct termios') */
		"../../libterm/*.c",

		/* Required for: Canonical line buffers and ring buffers (pipe(), readline(), etc.) */
		"../../libbuffer/*.c",

		/* Required for: Commandline decoding/encoding */
		"../../libcmdline/*.c",

		/* Required for: Config files/commandline decoding */
		"../../libjson/*.c",

		/* Required for: Keyboard key->character mappings */
		"../../libkeymap/*.c",

		/* Required for: VIO support */
		"../../libvio/*.c",
//		"../../libviocore/*.c", /* Empty folder... */
		f"../../libviocore/arch/{XARCH}/*.c",

		/* Pull in some hard-coded drivers */

		/* Required for: executing ELF files. */
		f"../modelf/arch/{XARCH}/*.c",
		"../modelf/*.c",

		/* Required for: executing #!-files. */
		"../modshebang/*.c",

		/* Required for: disk file access */
		"../modfat/*.c",

		/* Pull in the actual kernel sources */
		"debugger/*.c",
		"debugger-apps/*.c",
		"memory/*.c",
		"memory/mman/*.c",
		"dev/*.c",
		"fd/*.c",
		"filesys/*.c",
		"misc/*.c",
		"network/*.c",
		"sched/*.c",

		/* Do arch-specific files last */
		f"arch/{XARCH}/*.c",
	];

	/* Required for: disk I/O */
	if (TARGET_ARCH !in ["arm"]) { /* TODOB */
		KERNEL_C_SOURCES.append("../modide/*.c");
	}



	for (local x: fs.dir("kos/src/kernel/core/arch/" + XARCH)) {
		if ("." in x || x.startswith("__"))
			continue; /* Not a directory */
		local path = f"arch/{XARCH}/{x}/";
		KERNEL_ASM_SOURCES.append(path + ASMFILES);
		KERNEL_C_SOURCES.append(path + "*.c");
	}

	if (TARGET_ARCH in ["i386", "x86_64"]) {
		/* Required for: Emulation of BIOS functions (especially on x86_64, where EFLAGS_VM doesn't work) */
		KERNEL_C_SOURCES.append("../../libvm86/*.c");
		KERNEL_C_SOURCES.append("../../libbios86/*.c");

		/* Required for: Emulation and disassembly of instructions, VIO, etc. */
		KERNEL_C_SOURCES.append("../../libemu86/*.c");

		/* Required for: PCI device access. */
		KERNEL_C_SOURCES.append("../../libpciaccess/*.c");
	}

	if (TARGET_ARCH == "x86_64") {
		/* Configure GCC for a memory model to address -2Gb...2Gb */
		CC_FLAGS({ "-mcmodel=kernel" });
	}

	/* Generate compressed debug sections. */
	GCC_FLAGS({ "-gz=zlib" })

	LD_FLAGS({
		/* This  flag  disables  alignment  that
		 * would unnecessarily bloat the kernel. */
		"--nmagic",
		"--eh-frame-hdr",
		"--gc-sections",

		/* This right here is _needed_, else ld will remove ~unused~
		 * symbols  that  are   exported  from   the  kernel   core. */
		"--gc-keep-exported",

		/* This  following flag allows us to overlay sections,
		 * which is required for PERXXX templates and offsets. */
		"--no-check-sections",
	})

	BEGIN GROUP("kernel") { COMPILE };
		SET_LANGUAGE("assembler-with-cpp")
		SOURCE(KERNEL_ASM_SOURCES)
	END
	BEGIN GROUP("kernel") { COMPILE };
		SOURCE(KERNEL_C_SOURCES)
	END

	/* Special case: Include the rtld-flat.bin binary blob within the kernel core.
	 *               We do this by using a .incbin assembler directive, however in
	 *               order to do so safely,  the associated assembler source  file
	 *               needs to have an artificial dependency on rtld-flat.bin */
	BEGIN GROUP("kernel") {
			stepWithDependencies(COMPILE, {
				f"{TARGET_BINPATH}{DISK_LIBPATH}libdl.rtld-flat.bin"
			})
		};
		SET_LANGUAGE("assembler-with-cpp")
		SOURCE({ KENREL_LIBDL_BINARY_SOURCE })
	END
	if (TARGET_ARCH == "x86_64") {
		/* For compatibility-mode (include the 32-bit version of libdl.so) */
		BEGIN GROUP("kernel") {
				stepWithDependencies(COMPILE, {
					f"{TARGET_BINPATH}/lib/libdl.rtld-flat.bin"
				})
			};
			SET_LANGUAGE("assembler-with-cpp")
			SOURCE({ f"arch/{XARCH}/exec/libdl-compat.S.incbin" })
		END
	}

#define HAVE_KERNEL_EXPORTS_SYMLIST
	local final KERNEL_EXPORTS_SOURCE = f"/{config.BUILD_PATH}/kernel-symtab.S";
#ifdef HAVE_KERNEL_EXPORTS_SYMLIST
	local final KERNEL_EXPORTS_SYMLIST = f"/{config.BUILD_PATH}/kernel-symtab.txt";
#endif /* HAVE_KERNEL_EXPORTS_SYMLIST */
	BEGIN GROUP("kernel") { DILINK, MCOPY,
			enumerateSymbolsStep(
				elfBinary: KERNEL_BINARY,
				callback: [](self: BoundStep, symbols: {(Bytes, Bytes, Bytes, Bytes, Bytes)...}) {
					local symbols_map: {Bytes: int} = Dict();
					for (local symbolName, symbolType, symbolBind,
					           symbolVisi, symbolSize, none: symbols) {
						if (symbolVisi != "DEFAULT")
							continue;
						if (symbolBind != "GLOBAL" && symbolBind != "WEAK")
							continue;
						if (symbolType == "NOTYPE")
							continue;
						if (symbolName.startswith("kernel_") && (symbolName.endswith("_start") ||
						    symbolName.endswith("_end") || symbolName.endswith("_size")))
							print log: "WARNING: Potentially unwanted kernel export:", repr str symbolName;
						symbols_map[symbolName] = int(symbolSize);
					}
					if ("kernel_symbol_table" !in symbols_map) {
						/* Ensure that we're always exporting the kernel symbol table itself! */
						symbols_map["kernel_symbol_table"] = 0; /* Filled later */
					}
					local sorted_keys = symbols_map.keys.sorted();
					updateFileContents(self.group.fixFilename(KERNEL_EXPORTS_SOURCE), [](fp: File) {
						/* Generate the hash-vector */
						local U32 = try TARGET.AS_I32 catch (...) ".int32";
						local PTR = try TARGET.AS_PTR catch (...) ".pointer";
						local R_SIZE32 = try TARGET.R_TARGET_SIZE32 catch (...) none;
						local hash_mask = 1;
						while (hash_mask <= #symbols_map) {
							hash_mask = (hash_mask << 1) | 1;
						}
						if ((hash_mask - #symbols_map) < 64)
							hash_mask = (hash_mask << 1) | 1;
						symbols_map["kernel_symbol_table"] =
							TARGET.POINTER_SIZE + ((hash_mask + 1) * (8 + (2 * TARGET.POINTER_SIZE)));
						@@Hash-vector of (name,size,hash)
						local hash_vector: {(Bytes, int, int) | none...} = [none] * (hash_mask + 1);
						for (local name: sorted_keys) {
							local hash = ElfSymbolHash(name);
							local perturb, j;
							perturb = j = hash & hash_mask;
							for (;;) {
								local index = j & hash_mask;
								if (hash_vector[index] is none) {
									hash_vector[index] = pack(name, symbols_map[name], hash);
									break;
								}
								j = ((j << 2) + j + perturb + 1);
								perturb = perturb >> 5;
							}
						}
						fp << ".section .rodata.kernel_symtab\n"
							".global kernel_symbol_table\n"
							".type kernel_symbol_table, \"object\"\n"
							"kernel_symbol_table:\n"
							"\t" << PTR << " " << hash_mask.hex() << "\n"
							"\t/* Symbol table */\n";
						for (local i: [:#hash_vector]) {
							local data = hash_vector[i];
							if (data is none) {
								fp << "\t" << PTR << " 0 /* index: " << i << " */\n"
								      "\t" << PTR << " 0\n"
								      "\t" << U32 << " 0\n"
								      "\t" << U32 << " 0\n";
							} else {
								local symbol_name = data[0];
								/* Mark symbols as weak, so-as to allow them to-be removed without
								 * causing  a  linker  error   during  the  first  re-link   pass. */
								fp << "\t" << PTR << " .Lname" << i << " /* index: " << i << " */\n"
								      "\t.weak " << symbol_name << "; " << PTR << " " << symbol_name << "\n";
								if (R_SIZE32 !is none) {
									fp << "\t.reloc ., " << R_SIZE32 << ", " << symbol_name << "; " << U32 << " 0\n";
								} else {
									fp << "\t" << U32 << " " << data[1].hex() << "\n";
								}
								fp << "\t" << U32 << " " << data[2].hex() << "\n";
							}
						}
						fp << ".size kernel_symbol_table, . - kernel_symbol_table\n"
						      ".section .rodata.kernel_strtab\n";
						for (local i: [:#hash_vector]) {
							local data = hash_vector[i];
							if (data is none) continue;
							fp << ".Lname" << i << ":\n\t.string " << repr str data[0] << "\n";
						}
					});
#ifdef HAVE_KERNEL_EXPORTS_SYMLIST
					local final KERNEL_EXPORTS_SYMLIST = f"/{config.BUILD_PATH}/kernel-symtab.txt";
					updateFileContents(self.group.fixFilename(KERNEL_EXPORTS_SYMLIST), [](fp: File) {
						for (local key: sorted_keys)
							fp << key << "\n";
					});
#endif /* HAVE_KERNEL_EXPORTS_SYMLIST */
				}
			).withMaybeModifiedOutputFiles({
				KERNEL_EXPORTS_SOURCE
			})
#ifdef HAVE_KERNEL_EXPORTS_SYMLIST
			.withModifiedOutputFiles({
				KERNEL_EXPORTS_SYMLIST
			})
#endif /* HAVE_KERNEL_EXPORTS_SYMLIST */
			,
		};

		/* Bindings for compressed debug section data, and where to map them. */
		options[DILINK_PHYS2VIRT] = TARGET.KERNEL_CORE_BASE;
		options[DILINK_START]     = "__kernel_debug_start";
		options[DILINK_SIZE]      = "__kernel_debug_size";
		options[DILINK_BINDINGS]  = {
			(".debug_line",     "__kernel_debug_line_start",     "__kernel_debug_line_size"),
			(".debug_info",     "__kernel_debug_info_start",     "__kernel_debug_info_size"),
			(".debug_aranges",  "__kernel_debug_aranges_start",  "__kernel_debug_aranges_size"),
			(".debug_abbrev",   "__kernel_debug_abbrev_start",   "__kernel_debug_abbrev_size"),
			(".debug_str",      "__kernel_debug_str_start",      "__kernel_debug_str_size"),
			(".debug_line_str", "__kernel_debug_line_str_start", "__kernel_debug_line_str_size"),
			(".debug_rnglists", "__kernel_debug_rnglists_start", "__kernel_debug_rnglists_size"),
			(".debug_loclists", "__kernel_debug_loclists_start", "__kernel_debug_loclists_size"),
		};

		LINKER_SCRIPT("_kernel.ld")
		SET_OUTPUT("/os/kernel.bin")
		STATIC_USELIBGCC()
		SOURCE(KERNEL_ASM_SOURCES)
		SOURCE(KERNEL_C_SOURCES)
		SOURCE({ KENREL_LIBDL_BINARY_SOURCE })
		if (TARGET_ARCH == "x86_64") {
			/* For compatibility-mode (include the 32-bit version of libdl.so) */
			SOURCE({ f"arch/{XARCH}/exec/libdl-compat.S.incbin" })
		}
	END

END
