/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
 *               2018, 2019, 2020
 *   Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#import "OFApplication.h"
#import "OFArray.h"
#import "OFFile.h"
#import "OFXMLAttribute.h"
#import "OFXMLElement.h"

#import "OFInvalidFormatException.h"
#import "OFUnsupportedVersionException.h"

#import "copyright.h"

@interface LibraryGenerator: OFObject <OFApplicationDelegate>
@end

OF_APPLICATION_DELEGATE(LibraryGenerator)

@implementation LibraryGenerator
- (void)applicationDidFinishLaunching
{
	[self generateLinkLibInDirectory: @"../../src/runtime"];

	[OFApplication terminate];
}

- (void)generateLinkLibInDirectory: (OFString *)directory
{
	OFXMLElement *library = [OFXMLElement elementWithFile:
	    [directory stringByAppendingPathComponent: @"library.xml"]];
	OFString *linklibPath = [[directory
	    stringByAppendingPathComponent: @"linklib"]
	    stringByAppendingPathComponent: @"linklib.m"];
	OFFile *linklib = [OFFile fileWithPath: linklibPath
					  mode: @"w"];
	OFArray OF_GENERIC(OFXMLElement *) *functions;
	size_t funcIndex = 0;
	OFString *libBase;

	if (![library.name isEqual: @"amiga-library"] ||
	    library.namespace != nil)
		@throw [OFInvalidFormatException exception];

	OFXMLAttribute *version = [library attributeForName: @"version"];
	if (version == nil)
		@throw [OFInvalidFormatException exception];

	if (![version.stringValue isEqual: @"1.0"])
		@throw [OFUnsupportedVersionException
		    exceptionWithVersion: version.stringValue];

	libBase = [library attributeForName: @"base"].stringValue;

	[linklib writeString: COPYRIGHT];
	[linklib writeString:
	    @"/* This file is automatically generated from library.xml */\n"
	    @"\n"
	    @"#include \"config.h\"\n"
	    @"\n"];

	for (OFXMLElement *include in [library elementsForName: @"include"])
		[linklib writeFormat: @"#import \"%@\"\n", include.stringValue];

	[linklib writeFormat: @"\n"
			      @"extern struct Library *%@;\n"
			      @"\n",
			      libBase];

	functions = [library elementsForName: @"function"];
	for (OFXMLElement *function in functions) {
		OFString *name =
		    [function attributeForName: @"name"].stringValue;
		OFString *returnType =
		    [function attributeForName: @"return-type"].stringValue;
		OFArray OF_GENERIC(OFXMLElement *) *arguments =
		    [function elementsForName: @"argument"];
		size_t argumentIndex;

		if (returnType == nil)
			returnType = @"void";

		[linklib writeFormat: @"%@\n%@(", returnType, name];

		argumentIndex = 0;
		for (OFXMLElement *argument in
		    [function elementsForName: @"argument"]) {
			OFString *argName =
			    [argument attributeForName: @"name"].stringValue;
			OFString *argType =
			    [argument attributeForName: @"type"].stringValue;

			if (argumentIndex++ > 0)
				[linklib writeString: @", "];

			[linklib writeString: argType];
			if (![argType hasSuffix: @"*"])
				[linklib writeString: @" "];
			[linklib writeString: argName];
		}

		[linklib writeFormat:
		    @")\n"
		    @"{\n"
		    @"#if defined(OF_AMIGAOS_M68K)\n"
		    @"\tregister struct Library *a6 __asm__(\"a6\") = %@;\n"
		    @"\t(void)a6;\n"
		    @"\t", libBase];

		if (![returnType isEqual: @"void"])
			[linklib writeString: @"return "];

		[linklib writeString: @"(("];
		[linklib writeString: returnType];
		if (![returnType hasSuffix: @"*"])
			[linklib writeString: @" "];
		[linklib writeString: @"(*)("];

		argumentIndex = 0;
		for (OFXMLElement *argument in arguments) {
			OFString *argType =
			    [argument attributeForName: @"type"].stringValue;
			OFString *m68kReg = [argument
			    attributeForName: @"m68k-reg"].stringValue;

			if (argumentIndex++ > 0)
				[linklib writeString: @", "];

			[linklib writeString: argType];
			if (![argType hasSuffix: @"*"])
				[linklib writeString: @" "];
			[linklib writeFormat: @"__asm__(\"%@\")", m68kReg];
		}

		[linklib writeFormat: @"))(((uintptr_t)%@) - %zu))(",
				      libBase, 30 + funcIndex * 6];

		argumentIndex = 0;
		for (OFXMLElement *argument in
		    [function elementsForName: @"argument"]) {
			OFString *argName =
			    [argument attributeForName: @"name"].stringValue;

			if (argumentIndex++ > 0)
				[linklib writeString: @", "];

			[linklib writeString: argName];
		}

		[linklib writeFormat:
		    @");\n"
		    @"#elif defined(OF_MORPHOS)\n"
		    @"\t__asm__ __volatile__ (\n"
		    @"\t    \"mr\t\t%%%%r12, %%0\"\n"
		    @"\t    :: \"r\"(%@) : \"r12\"\n"
		    @"\t);\n"
		    @"\n"
		    @"\t",
		    libBase, libBase];

		if (![returnType isEqual: @"void"])
			[linklib writeString: @"return "];

		[linklib writeString: @"(("];
		[linklib writeString: returnType];
		if (![returnType hasSuffix: @"*"])
			[linklib writeString: @" "];
		[linklib writeString: @"(*)("];

		argumentIndex = 0;
		for (OFXMLElement *argument in arguments) {
			OFString *argType =
			    [argument attributeForName: @"type"].stringValue;

			if (argumentIndex++ > 0)
				[linklib writeString: @", "];

			[linklib writeString: argType];
		}

		[linklib writeFormat: @"))*(void **)(((uintptr_t)%@) - %zu))(",
				      libBase, 28 + funcIndex * 6];

		argumentIndex = 0;
		for (OFXMLElement *argument in
		    [function elementsForName: @"argument"]) {
			OFString *argName =
			    [argument attributeForName: @"name"].stringValue;

			if (argumentIndex++ > 0)
				[linklib writeString: @", "];

			[linklib writeString: argName];
		}

		[linklib writeString: @");\n"
				      @"#endif\n"];

		if ([function attributeForName: @"noreturn"] != nil)
			[linklib writeString: @"\n\tOF_UNREACHABLE\n"];

		[linklib writeString: @"}\n"];

		if (++funcIndex < functions.count)
			[linklib writeString: @"\n"];
	}
}
@end
