/*
 * Copyright (c) 2008-2025 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 "ObjFW.h"
#import "ObjFWTest.h"

static void *testKey = &testKey;

@interface RuntimeTestClass: OFObject
{
	OFString *_foo, *_bar;
}

@property (nonatomic, copy) OFString *foo;
@property (retain) OFString *bar;

- (id)nilSuperTest;
@end

@interface RuntimeTests: OTTestCase
{
	RuntimeTestClass *_test;
}
@end

@interface OFObject (SuperTest)
- (id)superTest;
@end

@implementation RuntimeTests
- (void)setUp
{
	[super setUp];

	_test = [[RuntimeTestClass alloc] init];
}

- (void)dealloc
{
	[_test release];

	[super dealloc];
}

- (void)testCallNonExistentMethodViaSuper
{
	OTAssertThrowsSpecific([_test superTest], OFNotImplementedException);
}

- (void)testCallMethodViaSuperWithNilSelf
{
	OTAssertNil([_test nilSuperTest]);
}

- (void)testPropertyCopyNonatomic
{
	OFMutableString *string = [OFMutableString stringWithString: @"foo"];
	OFString *foo = @"foo";

	_test.foo = string;
	OTAssertEqualObjects(_test.foo, foo);
	OTAssertNotEqual(_test.foo, foo);
	OTAssertEqual(_test.foo.retainCount, 1);
}

- (void)testPropertyRetainAtomic
{
	OFMutableString *string = [OFMutableString stringWithString: @"foo"];

	_test.bar = string;
	OTAssertEqual(_test.bar, string);
	OTAssertEqual(string.retainCount, 3);
}

- (void)testAssociatedObjects
{
	objc_setAssociatedObject(self, testKey, _test, OBJC_ASSOCIATION_ASSIGN);
	OTAssertEqual(_test.retainCount, 1);

	objc_setAssociatedObject(self, testKey, _test, OBJC_ASSOCIATION_RETAIN);
	OTAssertEqual(_test.retainCount, 2);

	OTAssertEqual(objc_getAssociatedObject(self, testKey), _test);
	OTAssertEqual(_test.retainCount, 3);

	objc_setAssociatedObject(self, testKey, _test, OBJC_ASSOCIATION_ASSIGN);
	OTAssertEqual(_test.retainCount, 2);

	objc_setAssociatedObject(self, testKey, _test,
	    OBJC_ASSOCIATION_RETAIN_NONATOMIC);
	OTAssertEqual(_test.retainCount, 3);

	OTAssertEqual(objc_getAssociatedObject(self, testKey), _test);
	OTAssertEqual(_test.retainCount, 3);

	objc_removeAssociatedObjects(self);
	OTAssertEqual(_test.retainCount, 2);
}

#ifdef OF_OBJFW_RUNTIME
- (void)testTaggedPointers
{
	int classID;
	uintmax_t value;
	id object;

	if (sizeof(uintptr_t) == 8)
		value = 0xDEADBEEFDEADBEF;
	else if (sizeof(uintptr_t) == 4)
		value = 0xDEADBEF;
	else
		OTAssert(sizeof(uintptr_t) == 8 || sizeof(uintptr_t) == 4);

	OTAssertNotEqual(objc_registerTaggedPointerClass([OFString class]), -1);

	classID = objc_registerTaggedPointerClass([OFNumber class]);
	OTAssertNotEqual(classID, -1);

	object = objc_createTaggedPointer(classID, (uintptr_t)value);
	OTAssertNotNil(object);
	OTAssertEqual(object_getClass(object), [OFNumber class]);
	OTAssertEqual([object class], [OFNumber class]);
	OTAssertEqual(object_getTaggedPointerValue(object), value);
	OTAssertNotNil(objc_createTaggedPointer(classID, UINTPTR_MAX >> 4));
	OTAssertNil(objc_createTaggedPointer(classID, (UINTPTR_MAX >> 4) + 1));
}
#endif
@end

@implementation RuntimeTestClass
@synthesize foo = _foo;
@synthesize bar = _bar;

- (void)dealloc
{
	[_foo release];
	[_bar release];

	[super dealloc];
}

- (id)superTest
{
	return [super superTest];
}

- (id)nilSuperTest
{
	self = nil;

	return [self superTest];
}
@end
