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

#if (defined(OF_DRAGONFLYBSD) && defined(__LP64__)) || defined(OF_NINTENDO_3DS)
# define TOO_BIG (SIZE_MAX / 3)
#else
# define TOO_BIG (SIZE_MAX - 128)
#endif

@interface MyObject: OFObject
{
	id _objectValue;
	Class _classValue;
	bool _boolValue;
	char _charValue;
	short _shortValue;
	int _intValue;
	long _longValue;
	long long _longLongValue;
	unsigned char _unsignedCharValue;
	unsigned short _unsignedShortValue;
	unsigned int _unsignedIntValue;
	unsigned long _unsignedLongValue;
	unsigned long long _unsignedLongLongValue;
	float _floatValue;
	double _doubleValue;
}

@property (nonatomic, retain) id objectValue;
@property (nonatomic) Class classValue;
@property (nonatomic, getter=isBoolValue) bool boolValue;
@property (nonatomic) char charValue;
@property (nonatomic) short shortValue;
@property (nonatomic) int intValue;
@property (nonatomic) long longValue;
@property (nonatomic) long long longLongValue;
@property (nonatomic) unsigned char unsignedCharValue;
@property (nonatomic) unsigned short unsignedShortValue;
@property (nonatomic) unsigned int unsignedIntValue;
@property (nonatomic) unsigned long unsignedLongValue;
@property (nonatomic) unsigned long long unsignedLongLongValue;
@property (nonatomic) float floatValue;
@property (nonatomic) double doubleValue;
@end

@interface OFObjectTests: OTTestCase
{
	MyObject *_myObject;
}
@end

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

	_myObject = [[MyObject alloc] init];
}

- (void)dealloc
{
	[_myObject release];

	[super dealloc];
}

- (void)testClassDescription
{
	OTAssertEqualObjects([OFObject description], @"OFObject");
	OTAssertEqualObjects([MyObject description], @"MyObject");
}

- (void)testInstanceDescription
{
	OFObject *object = [[[OFObject alloc] init] autorelease];

	OTAssertEqualObjects(object.description, @"<OFObject>");
	OTAssertEqualObjects(_myObject.description, @"<MyObject>");
}

- (void)testValueForKey
{
	_myObject.objectValue = @"Hello";
	_myObject.classValue = _myObject.class;

	OTAssertEqualObjects([_myObject valueForKey: @"objectValue"], @"Hello");
	OTAssertEqualObjects([_myObject valueForKey: @"classValue"],
	    _myObject.class);
	OTAssertEqualObjects([_myObject valueForKey: @"class"],
	    _myObject.class);
}

- (void)testValueForKeyWithUndefinedKeyThrows
{
	OTAssertThrowsSpecific([_myObject valueForKey: @"undefined"],
	   OFUndefinedKeyException);
}

- (void)testSetValueForKey
{
	[_myObject setValue: @"World" forKey: @"objectValue"];
	[_myObject setValue: [OFObject class] forKey: @"classValue"];

	OTAssertEqualObjects(_myObject.objectValue, @"World");
	OTAssertEqualObjects(_myObject.classValue, [OFObject class]);
}

- (void)testSetValueWithUndefinedKeyThrows
{
	OTAssertThrowsSpecific([_myObject setValue: @"x" forKey: @"undefined"],
	    OFUndefinedKeyException);
}

- (void)testAutoWrappingOfValueForKey
{
	_myObject.boolValue = 1;
	_myObject.charValue = 2;
	_myObject.shortValue = 3;
	_myObject.intValue = 4;
	_myObject.longValue = 5;
	_myObject.longLongValue = 6;
	_myObject.unsignedCharValue = 7;
	_myObject.unsignedShortValue = 8;
	_myObject.unsignedIntValue = 9;
	_myObject.unsignedLongValue = 10;
	_myObject.unsignedLongLongValue = 11;
	_myObject.floatValue = 12;
	_myObject.doubleValue = 13;

	OTAssertEqualObjects([_myObject valueForKey: @"boolValue"],
	    [OFNumber numberWithBool: 1]);
	OTAssertEqualObjects([_myObject valueForKey: @"charValue"],
	    [OFNumber numberWithChar: 2]);
	OTAssertEqualObjects([_myObject valueForKey: @"shortValue"],
	    [OFNumber numberWithShort: 3]);
	OTAssertEqualObjects([_myObject valueForKey: @"intValue"],
	    [OFNumber numberWithInt: 4]);
	OTAssertEqualObjects([_myObject valueForKey: @"longValue"],
	    [OFNumber numberWithLong: 5]);
	OTAssertEqualObjects([_myObject valueForKey: @"longLongValue"],
	    [OFNumber numberWithLongLong: 6]);
	OTAssertEqualObjects([_myObject valueForKey: @"unsignedCharValue"],
	    [OFNumber numberWithUnsignedChar: 7]);
	OTAssertEqualObjects([_myObject valueForKey: @"unsignedShortValue"],
	    [OFNumber numberWithUnsignedShort: 8]);
	OTAssertEqualObjects([_myObject valueForKey: @"unsignedIntValue"],
	    [OFNumber numberWithUnsignedInt: 9]);
	OTAssertEqualObjects([_myObject valueForKey: @"unsignedLongValue"],
	    [OFNumber numberWithUnsignedLong: 10]);
	OTAssertEqualObjects([_myObject valueForKey: @"unsignedLongLongValue"],
	    [OFNumber numberWithUnsignedLongLong: 11]);
	OTAssertEqualObjects([_myObject valueForKey: @"floatValue"],
	    [OFNumber numberWithFloat: 12]);
	OTAssertEqualObjects([_myObject valueForKey: @"doubleValue"],
	    [OFNumber numberWithDouble: 13]);
}

- (void)testAutoWrappingOfSetValueForKey
{
	[_myObject setValue: [OFNumber numberWithBool: 0]
		     forKey: @"boolValue"];
	[_myObject setValue: [OFNumber numberWithChar: 10]
		     forKey: @"charValue"];
	[_myObject setValue: [OFNumber numberWithShort: 20]
		     forKey: @"shortValue"];
	[_myObject setValue: [OFNumber numberWithInt: 30]
		     forKey: @"intValue"];
	[_myObject setValue: [OFNumber numberWithLong: 40]
		     forKey: @"longValue"];
	[_myObject setValue: [OFNumber numberWithLongLong: 50]
		     forKey: @"longLongValue"];
	[_myObject setValue: [OFNumber numberWithUnsignedChar: 60]
		     forKey: @"unsignedCharValue"];
	[_myObject setValue: [OFNumber numberWithUnsignedShort: 70]
		     forKey: @"unsignedShortValue"];
	[_myObject setValue: [OFNumber numberWithUnsignedInt: 80]
		     forKey: @"unsignedIntValue"];
	[_myObject setValue: [OFNumber numberWithUnsignedLong: 90]
		     forKey: @"unsignedLongValue"];
	[_myObject setValue: [OFNumber numberWithUnsignedLongLong: 100]
		     forKey: @"unsignedLongLongValue"];
	[_myObject setValue: [OFNumber numberWithFloat: 110]
		     forKey: @"floatValue"];
	[_myObject setValue: [OFNumber numberWithDouble: 120]
		     forKey: @"doubleValue"];

	OTAssertEqual(_myObject.isBoolValue, 0);
	OTAssertEqual(_myObject.charValue, 10);
	OTAssertEqual(_myObject.shortValue, 20);
	OTAssertEqual(_myObject.intValue, 30);
	OTAssertEqual(_myObject.longValue, 40);
	OTAssertEqual(_myObject.longLongValue, 50);
	OTAssertEqual(_myObject.unsignedCharValue, 60);
	OTAssertEqual(_myObject.unsignedShortValue, 70);
	OTAssertEqual(_myObject.unsignedIntValue, 80);
	OTAssertEqual(_myObject.unsignedLongValue, 90);
	OTAssertEqual(_myObject.unsignedLongLongValue, 100);
	OTAssertEqual(_myObject.floatValue, 110);
	OTAssertEqual(_myObject.doubleValue, 120);
}

- (void)testSetValueForKeyWithNilThrows
{
	OTAssertThrowsSpecific(
	    [_myObject setValue: (id _Nonnull)nil forKey: @"intValue"],
	    OFInvalidArgumentException);
}

- (void)testValueForKeyPath
{
	_myObject.objectValue = [[[MyObject alloc] init] autorelease];
	[_myObject.objectValue setObjectValue:
	    [[[MyObject alloc] init] autorelease]];
	[[_myObject.objectValue objectValue] setDoubleValue: 0.5];

	OTAssertEqual([[_myObject valueForKeyPath:
	    @"objectValue.objectValue.doubleValue"] doubleValue], 0.5);
}

- (void)testSetValueForKeyPath
{
	_myObject.objectValue = [[[MyObject alloc] init] autorelease];
	[_myObject.objectValue setObjectValue:
	    [[[MyObject alloc] init] autorelease]];
	[_myObject setValue: [OFNumber numberWithDouble: 0.75]
		 forKeyPath: @"objectValue.objectValue.doubleValue"];

	OTAssertEqual([[_myObject.objectValue objectValue] doubleValue], 0.75);
}
@end

@implementation MyObject
@synthesize objectValue = _objectValue, classValue = _classValue;
@synthesize boolValue = _boolValue, charValue = _charValue;
@synthesize shortValue = _shortValue, intValue = _intValue;
@synthesize longValue = _longValue, longLongValue = _longLongValue;
@synthesize unsignedCharValue = _unsignedCharValue;
@synthesize unsignedShortValue = _unsignedShortValue;
@synthesize unsignedIntValue = _unsignedIntValue;
@synthesize unsignedLongValue = _unsignedLongValue;
@synthesize unsignedLongLongValue = _unsignedLongLongValue;
@synthesize floatValue = _floatValue, doubleValue = _doubleValue;

- (void)dealloc
{
	[_objectValue release];

	[super dealloc];
}
@end
