/*
 * 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"

#include <math.h>

#import "OFColor.h"
#import "OFColorSpace.h"
#import "OFConcreteColor.h"
#import "OFMatrix4x4.h"
#import "OFOnce.h"
#import "OFString.h"
#import "OFTaggedPointerColor.h"

@interface OFPlaceholderColor: OFColor
@end

@interface OFConcreteColorSingleton: OFConcreteColor
@end

static struct {
	Class isa;
} placeholder;

#ifdef OF_OBJFW_RUNTIME
static const float allowedImprecision = 0.0000001f;
#endif

@implementation OFPlaceholderColor
- (instancetype)initWithRed: (float)red
		      green: (float)green
		       blue: (float)blue
		      alpha: (float)alpha
		 colorSpace: (OFColorSpace *)colorSpace
{
#ifdef OF_OBJFW_RUNTIME
	if (red >= 0.0f && red <= 1.0f && green >= 0.0f && green <= 1.0f &&
	    blue >= 0.0f && blue <= 1.0f && alpha == 1.0f &&
	    [colorSpace isEqual: [OFColorSpace sRGBColorSpace]]) {
		uint8_t redInt8 = roundf(red * 255.0f);
		uint8_t greenInt8 = roundf(green * 255.0f);
		uint8_t blueInt8 = roundf(blue * 255.0f);

		if (fabsf(red * 255.0f - redInt8) < allowedImprecision &&
		    fabsf(green * 255.0f - greenInt8) < allowedImprecision &&
		    fabsf(blue * 255.0f - blueInt8) < allowedImprecision) {
			id ret = [OFTaggedPointerColor colorWithRed: redInt8
							      green: greenInt8
							       blue: blueInt8];

			if (ret != nil)
				return ret;
		}
	}
#endif

	return (id)[[OFConcreteColor alloc] initWithRed: red
						  green: green
						   blue: blue
						  alpha: alpha
					     colorSpace: colorSpace];
}

OF_SINGLETON_METHODS
@end

@implementation OFConcreteColorSingleton
OF_SINGLETON_METHODS
@end

@implementation OFColor
+ (void)initialize
{
	if (self == [OFColor class])
		object_setClass((id)&placeholder, [OFPlaceholderColor class]);
}

+ (instancetype)alloc
{
	if (self == [OFColor class])
		return (id)&placeholder;

	return [super alloc];
}

#define PREDEFINED_COLOR(name, redValue, greenValue, blueValue)		   \
	static OFColor *name##Color = nil;				   \
									   \
	static void							   \
	initPredefinedColor_##name(void)				   \
	{								   \
		name##Color = [[OFConcreteColorSingleton alloc]		   \
		    initWithRed: redValue				   \
			  green: greenValue				   \
			   blue: blueValue				   \
			  alpha: 1.0f];					   \
	}								   \
									   \
	+ (OFColor *)name						   \
	{								   \
		static OFOnceControl onceControl = OFOnceControlInitValue; \
		OFOnce(&onceControl, initPredefinedColor_##name);	   \
									   \
		return name##Color;					   \
	}

PREDEFINED_COLOR(black,   0.00f, 0.00f, 0.00f)
PREDEFINED_COLOR(silver,  0.75f, 0.75f, 0.75f)
PREDEFINED_COLOR(gray,    0.50f, 0.50f, 0.50f)
PREDEFINED_COLOR(white,   1.00f, 1.00f, 1.00f)
PREDEFINED_COLOR(maroon,  0.50f, 0.00f, 0.00f)
PREDEFINED_COLOR(red,     1.00f, 0.00f, 0.00f)
PREDEFINED_COLOR(purple,  0.50f, 0.00f, 0.50f)
PREDEFINED_COLOR(fuchsia, 1.00f, 0.00f, 1.00f)
PREDEFINED_COLOR(green,   0.00f, 0.50f, 0.00f)
PREDEFINED_COLOR(lime,    0.00f, 1.00f, 0.00f)
PREDEFINED_COLOR(olive,   0.50f, 0.50f, 0.00f)
PREDEFINED_COLOR(yellow,  1.00f, 1.00f, 0.00f)
PREDEFINED_COLOR(navy,    0.00f, 0.00f, 0.50f)
PREDEFINED_COLOR(blue,    0.00f, 0.00f, 1.00f)
PREDEFINED_COLOR(teal,    0.00f, 0.50f, 0.50f)
PREDEFINED_COLOR(aqua,    0.00f, 1.00f, 1.00f)

+ (OFColor *)grey
{
	return [self gray];
}

+ (instancetype)colorWithRed: (float)red
		       green: (float)green
			blue: (float)blue
		       alpha: (float)alpha
{
	return objc_autoreleaseReturnValue([[self alloc] initWithRed: red
							       green: green
								blue: blue
							       alpha: alpha]);
}

+ (instancetype)colorWithRed: (float)red
		       green: (float)green
			blue: (float)blue
		       alpha: (float)alpha
		  colorSpace: (OFColorSpace *)colorSpace
{
	return objc_autoreleaseReturnValue(
	    [[self alloc] initWithRed: red
				green: green
				 blue: blue
				alpha: alpha
			   colorSpace: colorSpace]);
}

- (instancetype)initWithRed: (float)red
		      green: (float)green
		       blue: (float)blue
		      alpha: (float)alpha
{
	return [self initWithRed: red
			   green: green
			    blue: blue
			   alpha: alpha
		      colorSpace: [OFColorSpace sRGBColorSpace]];
}

- (instancetype)initWithRed: (float)red
		      green: (float)green
		       blue: (float)blue
		      alpha: (float)alpha
		 colorSpace: (OFColorSpace *)colorSpace
{
	if ([self isMemberOfClass: [OFColor class]]) {
		@try {
			[self doesNotRecognizeSelector: _cmd];
		} @catch (id e) {
			objc_release(self);
			@throw e;
		}

		abort();
	}

	return [super init];
}

- (bool)isEqual: (id)object
{
	OFColor *other;
	float red, green, blue, alpha;
	float otherRed, otherGreen, otherBlue, otherAlpha;

	if (object == self)
		return true;

	if (![object isKindOfClass: [OFColor class]])
		return false;

	other = object;

	if (![other.colorSpace isEqual: self.colorSpace])
		return false;

	[self getRed: &red green: &green blue: &blue alpha: &alpha];
	[other getRed: &otherRed
		green: &otherGreen
		 blue: &otherBlue
		alpha: &otherAlpha];

	if (otherRed != red)
		return false;
	if (otherGreen != green)
		return false;
	if (otherBlue != blue)
		return false;
	if (otherAlpha != alpha)
		return false;

	return true;
}

- (unsigned long)hash
{
	float red, green, blue, alpha;
	unsigned long hash;
	float tmp;

	[self getRed: &red green: &green blue: &blue alpha: &alpha];

	OFHashInit(&hash);

	tmp = OFToLittleEndianFloat(red);
	for (uint_fast8_t i = 0; i < sizeof(float); i++)
		OFHashAddByte(&hash, ((char *)&tmp)[i]);

	tmp = OFToLittleEndianFloat(green);
	for (uint_fast8_t i = 0; i < sizeof(float); i++)
		OFHashAddByte(&hash, ((char *)&tmp)[i]);

	tmp = OFToLittleEndianFloat(blue);
	for (uint_fast8_t i = 0; i < sizeof(float); i++)
		OFHashAddByte(&hash, ((char *)&tmp)[i]);

	tmp = OFToLittleEndianFloat(alpha);
	for (uint_fast8_t i = 0; i < sizeof(float); i++)
		OFHashAddByte(&hash, ((char *)&tmp)[i]);

	OFHashFinalize(&hash);

	return hash;
}

- (void)getRed: (float *)red
	 green: (float *)green
	  blue: (float *)blue
	 alpha: (float *)alpha
{
	OF_UNRECOGNIZED_SELECTOR
}

- (OFColorSpace *)colorSpace
{
	OF_UNRECOGNIZED_SELECTOR
}

- (OFColor *)colorUsingColorSpace: (OFColorSpace *)colorSpace
{
	OFColorSpace *oldCS = self.colorSpace;
	OF_ALIGN(16) OFVector4D colorVec;

	if ([colorSpace isEqual: oldCS])
		return self;

	[self getRed: &colorVec.x
	       green: &colorVec.y
		blue: &colorVec.z
	       alpha: &colorVec.w];

	oldCS.EOTF(&colorVec, 1);
	[oldCS.RGBToXYZMatrix transformVectors: &colorVec count: 1];

	[colorSpace.XYZToRGBMatrix transformVectors: &colorVec count: 1];
	colorSpace.OETF(&colorVec, 1);

	return [OFColor colorWithRed: colorVec.x
			       green: colorVec.y
				blue: colorVec.z
			       alpha: colorVec.w];
}

- (OFString *)description
{
	float red, green, blue, alpha;

	[self getRed: &red green: &green blue: &blue alpha: &alpha];

	return [OFString stringWithFormat:
	    @"<%@ red=%f green=%f blue=%f alpha=%f>",
	    self.class, red, green, blue, alpha];
}
@end
