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

#include <math.h>

#import "OFColor.h"
#import "OFConcreteColor.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.0000001;
#endif

@implementation OFPlaceholderColor
- (instancetype)initWithRed: (float)red
		      green: (float)green
		       blue: (float)blue
		      alpha: (float)alpha
{
#ifdef OF_OBJFW_RUNTIME
	uint8_t redInt = roundf(red * 255);
	uint8_t greenInt = roundf(green * 255);
	uint8_t blueInt = roundf(blue * 255);

	if (fabsf(red * 255 - redInt) < allowedImprecision &&
	    fabsf(green * 255 - greenInt) < allowedImprecision &&
	    fabsf(blue * 255 - blueInt) < allowedImprecision && alpha == 1) {
		id ret = [OFTaggedPointerColor colorWithRed: redInt
						      green: greenInt
						       blue: blueInt];

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

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

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];					   \
	}								   \
									   \
	+ (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 [[[self alloc] initWithRed: red
				    green: green
				     blue: blue
				    alpha: alpha] autorelease];
}

- (instancetype)initWithRed: (float)red
		      green: (float)green
		       blue: (float)blue
		      alpha: (float)alpha
{
	if ([self isMemberOfClass: [OFColor class]]) {
		@try {
			[self doesNotRecognizeSelector: _cmd];
		} @catch (id e) {
			[self release];
			@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;
	[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
}

- (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
