/* 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. *
 */

/* To  modify  options   on  a  per-source   pasis,  paste   the
 * following  comment at  the top  of the  specific source file.
 * The contained code is executed using the deemon JIT compiler,
 * where `options' is  the same  dict as used  below to  specify
 * compiler options. */
/*[[[magic
local opt = options.setdefault("GCC.options", []);
opt.removeif(e -> e.startswith("-O"));
opt.append("-O2");
]]]*/

/* The core component group constructor. */
#define GROUP(name_)                       \
	groups.append pack                     \
		Group pack                         \
			name: name_,                   \
			options: options,              \
			defPath: getDefPath(__FILE__), \
			steps:

function getDefPath(filename: string) -> fs.relpath(fs.headof(filename),ROOTDIR);

/* A whole bunch of useful helpers to quickly describe compilation rules. */
#define SOURCE(...)              options.setdefault(OPT_COMPILE_SOURCES, []).extend(__VA_ARGS__);
#define SET_LINK_OUTPUT(name)    options[OPT_LINK_OUTPUT] = name;
#define SET_ARCHIVE_OUTPUT(name) options[OPT_ARCHIVE_OUTPUT] = name;
#define SET_STATIC_OUTPUT(name)  options[OPT_ARCHIVE_OUTPUT] = name;
#define SET_LANGUAGE(name)       options[OPT_COMPILE_LANGUAGE] = name;
#define GCC_FLAGS(...)           options.setdefault(OPT_GCC_OPTIONS, []).extend(__VA_ARGS__);
#define CC_FLAGS(...)            options.setdefault(OPT_COMPILE_CCFLAGS, []).extend(__VA_ARGS__);
#define PP_FLAGS(...)            options.setdefault(OPT_COMPILE_PPFLAGS, []).extend(__VA_ARGS__);
#define AS_FLAGS(...)            options.setdefault(OPT_COMPILE_ASFLAGS, []).extend(__VA_ARGS__);
#define LD_FLAGS(...)            options.setdefault(OPT_LINK_LDFLAGS, []).extend(__VA_ARGS__);
#define REMOVE_GCC_FLAGS(...)    options.get(OPT_GCC_OPTIONS).removeif(x -> x in __VA_ARGS__);
#define REMOVE_CC_FLAGS(...)     options.get(OPT_COMPILE_CCFLAGS).removeif(x -> x in __VA_ARGS__);
#define REMOVE_PP_FLAGS(...)     options.get(OPT_COMPILE_PPFLAGS).removeif(x -> x in __VA_ARGS__);
#define REMOVE_AS_FLAGS(...)     options.get(OPT_COMPILE_ASFLAGS).removeif(x -> x in __VA_ARGS__);
#define REMOVE_LD_FLAGS(...)     options.get(OPT_LINK_LDFLAGS).removeif(x -> x in __VA_ARGS__);
#define OBJECT(...)              options.setdefault(OPT_LINK_OBJECTS,[]).extend(__VA_ARGS__);
#define LINKER_SCRIPT(name)      options[OPT_LINK_SCRIPT] = name;
#define SET_DISKFILE(name)       options[OPT_MCOPY_OUTPUT] = name;
#define SET_OUTPUT(filename)                               \
	options[OPT_LINK_OUTPUT]  = TARGET_BINPATH + filename; \
	options[OPT_MCOPY_OUTPUT] = filename;
#define SET_OUTPUT_DLL(filename)                                     \
	options[OPT_LINK_OUTPUT]    = TARGET_BINPATH + filename + ".so"; \
	options[OPT_ARCHIVE_OUTPUT] = TARGET_BINPATH + filename + ".a";  \
	options[OPT_MCOPY_OUTPUT]   = filename + ".so";
#define SET_OUTPUT_LIBDLL(name)     SET_OUTPUT_DLL(DISK_LIBPATH + name)
#define SET_OUTPUT_USRLIBDLL(name)  SET_OUTPUT_DLL(DISK_USRLIBPATH + name)
#define DEFINE(...)              options.setdefault(OPT_COMPILE_MACROS, Dict()).update(__VA_ARGS__);
#define INCLUDE(...)             options.setdefault(OPT_COMPILE_INCPATH, []).extend(__VA_ARGS__);
#define LIBPATH(...)             options.setdefault(OPT_LINK_LIBPATH, []).extend(__VA_ARGS__);
#define LIB(x)                   options.setdefault(OPT_LINK_LIBRARIES, []).append(x);
#define SET_SHARED()             GCC_FLAGS({ "-fPIC", "-shared" })
#define SET_NOSTDLIB()           options[OPT_LINK_NOSTDLIB] = true;

SET_LANGUAGE("c++")
DEFINE({ "__ELF__" : "1" })

GCC_FLAGS({
	/* TODO: Add support for dwarf-5 in `libdebuginfo' */
	"-g",    /* Always generate debug information. */
	"-pipe", /* Use pipes to speed up compilation */
})



//GCC_FLAGS({ "-gz" })                   /* TODO: Always enable? */
//GCC_FLAGS({ "-fdebug-types-section" }) /* TODO: Play around with this */

/* TODO: Make use of `-fsanitize=alignment' */
/* TODO: Also enable  `-fsanitize-recover'  (which  would  allow  the
 *       `ignore' option of the kernel debugger to function properly) */
/* TODO: Look into `-fsanitize=kernel-address' */


CC_FLAGS({
	//"-fanalyzer",
	//"-Wno-analyzer-malloc-leak",    // Way too many false positives
	//"-Wno-analyzer-write-to-const", // We do this a bunch for self-modifying code

	"-Wall",
	"-Wextra",
	"-Wduplicated-cond",
	"-Wno-nonnull-compare",
	"-Wno-missing-field-initializers",
	"-Wno-type-limits",

	/* gcc doesn't understand KOS's printf extensions, so disable Wformat
	 * Maybe   one  day  I'll  add  this  support  to  the  gcc  patch... */
	"-Wno-format",

	/* For some reason, "Wno-format" disables this, so turn it back on... */
	"-Wnonnull",

	/* "-Warray-bounds" is broken in gcc-12:
	 * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105684 */
	"-Wno-array-bounds",

	"-Wsuggest-attribute=const",
	"-Wsuggest-attribute=malloc",

	/* -Wsuggest-attribute=pure is broken in gcc-12:
	 * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105676 */
//	"-Wsuggest-attribute=pure",

	/* gcc-9:
	 *    cold attribute suggestions are broken at the moment:
	 *    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93087
	 * gcc-12:
	 *    Still broken (but in a slightly different way) :/
	 *    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105685
	 * If (and once) this gets fixed for real, and I've updated
	 * the  toolchain (once again),  this'll get enabled again. */
//	"-Wsuggest-attribute=cold",

	/* Causes warnings about functions with `kernel_panic("TO" "DO")', so nope! */
//	"-Wsuggest-attribute=noreturn",

	/* KOS  never assumes _signed_  overflow (only unsigned overflow),
	 * so  turn this warning  to max! (most  warnings that this causes
	 * should be fixed by adding appropriate casts to unsigned  types,
	 * since it's usually related to array indices being scaled,  with
	 * some kind of integer promotion previously causing those indices
	 * to become signed) */
	"-Wstrict-overflow=5",

	/* XXX: Maybe configure GCC to turn this on by default? */
	"-fnon-call-exceptions",
	"-feliminate-unused-debug-symbols",
	"-feliminate-unused-debug-types",
})
LD_FLAGS({
	"--no-demangle"
})

/* Enable support for inline source flags. */
options[OPT_COMPILE_INLINE_SOURCE_FLAGS] = true;

#include "src/.sources"

if (!OPT_ONLY_BUILD_LIBRARIES) {
/* Define the DISK */
BEGIN_ANONYMOUS GROUP("disk") { MFORMAT };
	options[OPT_NOFORCE] = true; /* Don't re-create the disk when doing a forced re-build. */

	/* Set disk format parameters */
	options[OPT_MFORMAT_TOTAL_SECTORS]    = 0x200000;
	options[OPT_MFORMAT_HEADS]            = 2;
	options[OPT_MFORMAT_SECTORS_PER_HEAD] = 128;
	options[OPT_MFORMAT_VOLUME_LABEL]     = "KOS";
END
}


/* Copy libgcc.so.1 and libstdc++.so.1 to the disk */
{
	local libgcc_s_filename = f"bin/{TARGET_ARCH}-kos-common{DISK_LIBPATH}libgcc_s.so.1";
	if (fs.stat.exists(libgcc_s_filename)) {
		BEGIN_ANONYMOUS GROUP("libs.libgcc") { MCOPY };
			options[OPT_MCOPY_OUTPUT] = DISK_LIBPATH + "libgcc_s.so.1";
			options[OPT_MCOPY_INPUT]  = "/" + libgcc_s_filename;
		END
	}
}

BEGIN_ANONYMOUS GROUP("libs.libstdc++") { MCOPY };
	options[OPT_MCOPY_OUTPUT] = DISK_LIBPATH + "libstdc++.so.6";
	options[OPT_MCOPY_INPUT]  = f"/bin/{TARGET_ARCH}-kos-common{DISK_LIBPATH}libstdc++.so.6";
END

#if __has_include("ammend.sources")
#include "ammend.sources"
#endif
