/*
 * Copyright (c) 2008-2024 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 "OFTaggedPointerNumber.h"

#import "OFInvalidFormatException.h"

enum Tag {
	tagChar,
	tagShort,
	tagInt,
	tagLong,
	tagLongLong,
	tagUnsignedChar,
	tagUnsignedShort,
	tagUnsignedInt,
	tagUnsignedLong,
	tagUnsignedLongLong,
};
static const uintptr_t tagMask = (1 << OFTaggedPointerNumberTagBits) - 1;
static int numberTag;

@implementation OFTaggedPointerNumber
+ (void)initialize
{
	if (self == [OFTaggedPointerNumber class])
		numberTag = objc_registerTaggedPointerClass(self);
}

+ (OFTaggedPointerNumber *)numberWithChar: (signed char)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)(unsigned char)value << OFTaggedPointerNumberTagBits) |
	    tagChar);
}

+ (OFTaggedPointerNumber *)numberWithShort: (short)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)(unsigned short)value << OFTaggedPointerNumberTagBits) |
	    tagShort);
}

+ (OFTaggedPointerNumber *)numberWithInt: (int)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)(unsigned int)value << OFTaggedPointerNumberTagBits) |
	    tagInt);
}

+ (OFTaggedPointerNumber *)numberWithLong: (long)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)(unsigned long)value << OFTaggedPointerNumberTagBits) |
	    tagLong);
}

+ (OFTaggedPointerNumber *)numberWithLongLong: (long long)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)(unsigned long long)value <<
	    OFTaggedPointerNumberTagBits) | tagLongLong);
}

+ (OFTaggedPointerNumber *)numberWithUnsignedChar: (unsigned char)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)value << OFTaggedPointerNumberTagBits) |
	    tagUnsignedChar);
}

+ (OFTaggedPointerNumber *)numberWithUnsignedShort: (unsigned short)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)value << OFTaggedPointerNumberTagBits) |
	    tagUnsignedShort);
}

+ (OFTaggedPointerNumber *)numberWithUnsignedInt: (unsigned int)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)value << OFTaggedPointerNumberTagBits) |
	    tagUnsignedInt);
}

+ (OFTaggedPointerNumber *)numberWithUnsignedLong: (unsigned long)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)value << OFTaggedPointerNumberTagBits) |
	    tagUnsignedLong);
}

+ (OFTaggedPointerNumber *)numberWithUnsignedLongLong: (unsigned long long)value
{
	return objc_createTaggedPointer(numberTag,
	    ((uintptr_t)value << OFTaggedPointerNumberTagBits) |
	    tagUnsignedLongLong);
}

- (const char *)objCType
{
	uintptr_t value = object_getTaggedPointerValue(self);

	switch (value & tagMask) {
	case tagChar:
		return @encode(signed char);
	case tagShort:
		return @encode(short);
	case tagInt:
		return @encode(int);
	case tagLong:
		return @encode(long);
	case tagLongLong:
		return @encode(long long);
	case tagUnsignedChar:
		return @encode(unsigned char);
	case tagUnsignedShort:
		return @encode(unsigned short);
	case tagUnsignedInt:
		return @encode(unsigned int);
	case tagUnsignedLong:
		return @encode(unsigned long);
	case tagUnsignedLongLong:
		return @encode(unsigned long long);
	default:
		@throw [OFInvalidFormatException exception];
	}
}

#define RETURN_VALUE						\
	uintptr_t value = object_getTaggedPointerValue(self);	\
								\
	switch (value & tagMask) {				\
	case tagChar:						\
		return (signed char)(unsigned char)		\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagShort:						\
		return (short)(unsigned short)			\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagInt:						\
		return (int)(unsigned int)			\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagLong:						\
		return (long)(unsigned long)			\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagLongLong:					\
		return (long long)(unsigned long long)		\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagUnsignedChar:					\
		return (unsigned char)				\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagUnsignedShort:					\
		return (unsigned short)				\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagUnsignedInt:					\
		return (unsigned int)				\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagUnsignedLong:					\
		return (unsigned long)				\
		    (value >> OFTaggedPointerNumberTagBits);	\
	case tagUnsignedLongLong:				\
		return (unsigned long long)			\
		    (value >> OFTaggedPointerNumberTagBits);	\
	default:						\
		@throw [OFInvalidFormatException exception];	\
	}
- (long long)longLongValue
{
	RETURN_VALUE
}

- (unsigned long long)unsignedLongLongValue
{
	RETURN_VALUE
}

- (double)doubleValue
{
	RETURN_VALUE
}
#undef RETURN_VALUE

OF_SINGLETON_METHODS
@end
