/*
 * Copyright (c) 2008-2026 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#import "OFArray.h"
#import "OFXMLAttribute.h"

#import "GlueGenerator.h"

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

#import "copyright.h"

@implementation GlueGenerator
- (instancetype)initWithLibrary: (OFXMLElement *)library
			 header: (OFStream *)header
		 implementation: (OFStream *)impl
{
	self = [super init];

	@try {
		OFXMLAttribute *version;

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

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

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

		_library = objc_retain(library);
		_header = objc_retain(header);
		_impl = objc_retain(impl);
	} @catch (id e) {
		objc_release(self);
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	objc_release(_library);
	objc_release(_header);
	objc_release(_impl);

	[super dealloc];
}

- (void)generate
{
	size_t includes = 0;

	[_header writeString: COPYRIGHT];
	[_impl writeString: COPYRIGHT];

	[_header writeString:
	    @"/* This file is automatically generated from amiga-library.xml */"
	    @"\n\n"];

	[_impl writeString:
	    @"/* This file is automatically generated from amiga-library.xml */"
	    @"\n\n"
	    @"#include \"config.h\"\n"
	    @"\n"
	    @"#import \"amiga-library-glue.h\"\n"];

	if (includes > 0)
		[_header writeString: @"\n"];

	for (OFXMLElement *include in [_library elementsForName: @"include"]) {
		[_header writeFormat: @"#import \"%@\"\n",
				      include.stringValue];
		includes++;
	}

	if (includes > 0)
		[_header writeString: @"\n"];

	[_impl writeString:
	    @"\n"
	    @"__asm__ (\n"
	    @"    \".section .text\\n\"\n"
	    @"    \".align 2\\n\"\n"
	    @"    \"__restore_r13:\\n\"\n"
	    @"    \"	lwz	%r13, 44(%r12)\\n\"\n"
	    @"    \"	blr\\n\"\n"
	    @");\n"];

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

		[_impl writeString: @"\n"];

		if (condition != nil) {
			[_header writeFormat: @"#if %@\n", condition];
			[_impl writeFormat: @"#if %@\n", condition];
		}

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

		[_header writeFormat:
		    @"extern %@%@glue_%@",
		    returnType,
		    (![returnType hasSuffix: @"*"] ? @" " : @""),
		    name];

		[_impl writeFormat: @"%@ __saveds\n"
				    @"glue_%@",
				    returnType, name];

		if (arguments.count > 0) {
			[_header writeString: @"("];
			[_impl writeString: @"("];
		} else {
			[_header writeString: @"(void"];
			[_impl writeString: @"(void"];
		}

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

			if (argumentIndex++ > 0) {
				[_header writeString: @", "];
				[_impl writeString: @", "];
			}

			[_header writeString: argType];
			[_impl writeString: argType];

			if (![argType hasSuffix: @"*"]) {
				[_header writeString: @" "];
				[_impl writeString: @" "];
			}

			[_header writeString: argName];
			[_impl writeString: argName];
		}

		[_header writeString: @");\n"];
		[_impl writeString: @")\n{\n"];

		if (![returnType isEqual: @"void"])
			[_impl writeString: @"\treturn "];
		else
			[_impl writeString: @"\t"];

		[_impl writeFormat: @"%@(", name];

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

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

			[_impl writeString: argName];
		}

		[_impl writeString: @");\n"
				    @"}\n"];

		if (condition != nil) {
			[_header writeString: @"#endif\n"];
			[_impl writeString: @"#endif\n"];
		}
	}
}
@end
