//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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 for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * 四要素実数カラーヘッダ
 * @author Junpee
 */

#ifndef COLOR_4F_H_
#define COLOR_4F_H_

#include <Core/System/Math.h>

namespace Lamp{

class Color3c;
class Color4c;
class Color3f;

//------------------------------------------------------------------------------
/**
 * 四要素実数カラー
 *
 * このクラスは継承しないで下さい。
 */
class Color4f{
public:
	//--------------------------------------------------------------------------
	// メンバ変数
	//--------------------------------------------------------------------------
	/// メンバ変数
	union{
		/// 各要素
		struct{
			/// 赤
			float r;
			/// 緑
			float g;
			/// 青
			float b;
			/// アルファ
			float a;
		};

		/// 各要素
		struct{
			/// 色相
			float h;
			/// 彩度
			float s;
			/// 明度
			float v;
			/// アルファ
			float a;
		};

		/// 配列
		float array[4];
	};

	//--------------------------------------------------------------------------
	// 定数
	//--------------------------------------------------------------------------
	/// 白
	static const Color4f white;

	/// 灰色
	static const Color4f gray;

	/// 黒
	static const Color4f black;

	/// 赤
	static const Color4f red;

	/// 緑
	static const Color4f green;

	/// 青
	static const Color4f blue;

	/// 黄
	static const Color4f yellow;

	/// 青緑
	static const Color4f cyan;

	/// 赤紫
	static const Color4f magenta;

	//--------------------------------------------------------------------------
	// コンストラクタ
	//--------------------------------------------------------------------------
	/**
	 * コンストラクタ
	 *
	 * このコンストラクタは初期値の設定を行わないため値は不定です。
	 */
	Color4f(){}

	/**
	 * コンストラクタ
	 * @param sourceR 赤の初期値
	 * @param sourceG 緑の初期値
	 * @param sourceB 青の初期値
	 * @param sourceA アルファの初期値
	 */
	inline Color4f(float sourceR, float sourceG, float sourceB,
		float sourceA = 1.f) :
		r(sourceR), g(sourceG), b(sourceB), a(sourceA){
	}

	/**
	 * コンストラクタ
	 * @param source 設定する色
	 */
	explicit Color4f(const Color3c& source);

	/**
	 * コンストラクタ
	 * @param source 設定する色
	 */
	explicit Color4f(const Color4c& source);

	/**
	 * コンストラクタ
	 * @param source 設定する色
	 */
	explicit Color4f(const Color3f& source);

	//--------------------------------------------------------------------------
	// 値の設定
	//--------------------------------------------------------------------------
	/**
	 * 値の設定
	 * @param sourceR 赤の設定値
	 * @param sourceG 緑の設定値
	 * @param sourceB 青の設定値
	 * @param sourceA アルファの設定値
	 */
	inline void set(float sourceR, float sourceG, float sourceB,
		float sourceA = 1.f){
		r = sourceR;
		g = sourceG;
		b = sourceB;
		a = sourceA;
	}

	/**
	 * 三要素キャラクタカラーの設定
	 * @param source 設定する色
	 */
	void set(const Color3c& source);

	/**
	 * 四要素キャラクタカラーの設定
	 * @param source 設定する色
	 */
	void set(const Color4c& source);

	/**
	 * 三要素実数カラーの設定
	 * @param source 設定する色
	 */
	void set(const Color3f& source);

	//--------------------------------------------------------------------------
	// 演算
	//--------------------------------------------------------------------------
	/**
	 * 加算
	 * @param addColor 加算する色
	 * @return 加算された色
	 */
	inline Color4f operator +(const Color4f& addColor) const{
		return Color4f(
			r + addColor.r, g + addColor.g, b + addColor.b, a + addColor.a);
	}

	/**
	 * 減算
	 * @param subColor 減算する色
	 * @return 減算された色
	 */
	inline Color4f operator -(const Color4f& subColor) const{
		return Color4f(
			r - subColor.r, g - subColor.g, b - subColor.b, a - subColor.a);
	}

	/**
	 * 乗算
	 * @param mulColor 乗算する色
	 * @return 乗算された色
	 */
	inline Color4f operator *(const Color4f& mulColor) const{
		return Color4f(
			r * mulColor.r, g * mulColor.g, b * mulColor.b, a * mulColor.a);
	}

	/**
	 * 乗算
	 * @param mulValue 乗算する値
	 * @return 乗算された色
	 */
	inline Color4f operator *(float mulValue) const{
		return Color4f(
			r * mulValue, g * mulValue, b * mulValue, a * mulValue);
	}

	/**
	 * 乗算
	 * @param mulValue 乗算する値
	 * @param mulColor 乗算される色
	 * @return 乗算された色
	 */
	inline friend Color4f operator *(float mulValue, const Color4f& mulColor){
		return Color4f(
			mulColor.r * mulValue, mulColor.g * mulValue,
			mulColor.b * mulValue, mulColor.a * mulValue);
	}

	/**
	 * +演算子
	 * @return 色のコピー
	 */
	inline Color4f operator +() const{ return *this; }

	/**
	 * -演算子
	 * @return 値の符号が反転した色
	 */
	inline Color4f operator -() const{ return Color4f(-r, -g, -b, -a); }

	//--------------------------------------------------------------------------
	// 代入演算
	//--------------------------------------------------------------------------
	/**
	 * 代入加算
	 * @param addColor 加算する色
	 * @return 加算された色
	 */
	inline Color4f& operator +=(const Color4f& addColor){
		r += addColor.r;
		g += addColor.g;
		b += addColor.b;
		a += addColor.a;
		return (*this);
	}

	/**
	 * 代入減算
	 * @param subColor 減算する色
	 * @return 減算された色
	 */
	inline Color4f& operator -=(const Color4f& subColor){
		r -= subColor.r;
		g -= subColor.g;
		b -= subColor.b;
		a -= subColor.a;
		return (*this);
	}

	/**
	 * 代入乗算
	 * @param mulColor 乗算する色
	 * @return 乗算された色
	 */
	inline Color4f& operator *=(const Color4f& mulColor){
		r *= mulColor.r;
		g *= mulColor.g;
		b *= mulColor.b;
		a *= mulColor.a;
		return (*this);
	}

	/**
	 * 代入乗算
	 * @param mulValue 乗算する値
	 * @return 乗算された色
	 */
	inline Color4f& operator *=(float mulValue){
		r *= mulValue;
		g *= mulValue;
		b *= mulValue;
		a *= mulValue;
		return (*this);
	}

	//--------------------------------------------------------------------------
	// 色演算
	//--------------------------------------------------------------------------
	/**
	 * クランプ
	 * @param lower クランプ下限値
	 * @param upper クランプ上限値
	 * @return クランプされた色
	 */
	inline Color4f& clamp(float lower = 0.f, float upper = 1.f){
		Assert(upper > lower);
		if(r > upper){ r = upper; }
		else if(r < lower){ r = lower; }
		if(g > upper){ g = upper; }
		else if(g < lower){ g = lower; }
		if(b > upper){ b = upper; }
		else if(b < lower){ b = lower; }
		if(a > upper){ a = upper; }
		else if(a < lower){ a = lower; }
		return (*this);
	}

	/**
	 * 下限クランプ
	 * @param lower クランプ下限値
	 * @return クランプされた色
	 */
	inline Color4f& lowerClamp(float lower = 0.f){
		if(r < lower){ r = lower; }
		if(g < lower){ g = lower; }
		if(b < lower){ b = lower; }
		if(a < lower){ a = lower; }
		return (*this);
	}

	/**
	 * 上限クランプ
	 * @param upper クランプ上限値
	 * @return クランプされた色
	 */
	inline Color4f& upperClamp(float upper = 1.f){
		if(r > upper){ r = upper; }
		if(g > upper){ g = upper; }
		if(b > upper){ b = upper; }
		if(a > upper){ a = upper; }
		return (*this);
	}

	/**
	 * 反対色
	 *
	 * アルファ値は変化しません。
	 * @return 反転された色
	 */
	inline Color4f& negative(){
		set(1.f - r, 1.f - g, 1.f - b, a);
		return (*this);
	}

	//--------------------------------------------------------------------------
	/**
	 * HSVの設定
	 * @param hsv HSVカラー
	 */
	inline void setHSV(const Color4f& hsv){
		float alpha = hsv.a;
		float value = hsv.v;
		if(hsv.s <= Math::epsilon){
			set(value, value, value, alpha);
			return;
		}
		float hue = hsv.h * 6.f;
		float integer = Math::floor(hue);
		float decimal = hue - integer;
		float temp0 = value * (1.f - hsv.s);
		float temp1 = value * (1.f - (hsv.s * decimal));
		float temp2 = value * (1.f - (hsv.s * (1.f - decimal)));
		if(integer < 1.f){
			set(value, temp2, temp0, alpha);
		}else if(integer < 2.f){
			set(temp1, value, temp0, alpha);
		}else if(integer < 3.f){
			set(temp0, value, temp2, alpha);
		}else if(integer < 4.f){
			set(temp0, temp1, value, alpha);
		}else if(integer < 5.f){
			set(temp2, temp0, value, alpha);
		}else{
			set(value, temp0, temp1, alpha);
		}
	}

	/**
	 * HSVの取得
	 * @return HSVカラー
	 */
	inline Color4f getHSV() const{
		Color4f hsv;
		hsv.a = a;
		// 明度の算出
		float maximum = Math::maximum(r, Math::maximum(g, b));
		hsv.v = maximum;
		// 彩度の算出
		float minimum = Math::minimum(r, Math::minimum(g, b));
		float chroma = maximum - minimum;
		if(maximum <= Math::epsilon){
			hsv.s = 0.f;
			hsv.h = 0.f;
			return hsv;
		}else{
			hsv.s = chroma / maximum;
		}
		if(chroma <= Math::epsilon){
			hsv.h = 0.f;
			return hsv;
		}
		float hueScale = (1 / chroma) * 0.1666667f;
		if(r == maximum){
			hsv.h = (g - b) * hueScale;
		}else if(g == maximum){
			hsv.h = (b - r) * hueScale + 0.3333333f;
		}else{
			hsv.h = (r - g) * hueScale + 0.6666667f;
		}
		if(hsv.h < 0.f){ hsv.h += 1.f; }
		return hsv;
	}

	//--------------------------------------------------------------------------
	/**
	 * 色相の取得
	 * @return 色相
	 */
	inline float getHue() const{
		float maximum = Math::maximum(r, Math::maximum(g, b));
		if(maximum <= Math::epsilon){ return 0.f; }
		float minimum = Math::minimum(r, Math::minimum(g, b));
		float chroma = maximum - minimum;
		if(chroma <= Math::epsilon){ return 0.f; }
		float hueScale = (1 / chroma) * 0.16666667f;
		float hue;
		if(r == maximum){
			hue = (g - b) * hueScale;
		}else if(g == maximum){
			hue = (b - r) * hueScale + 0.33333333f;
		}else{
			hue = (r - g) * hueScale + 0.66666667f;
		}
		if(hue < 0.f){ hue += 1.f; }
	}

	/**
	 * 彩度の取得
	 * @return 彩度
	 */
	inline float getSaturation() const{
		float maximum = Math::maximum(r, Math::maximum(g, b));
		if(maximum <= Math::epsilon){ return 0.f; }
		float minimum = Math::minimum(r, Math::minimum(g, b));
		float chroma = maximum - minimum;
		return chroma / maximum;
	}

	/**
	 * 明度の取得
	 * @return 明度
	 */
	inline float getValue() const{
		return Math::maximum(r, Math::maximum(g, b));
	}

	/**
	 * 輝度の取得
	 * @return 輝度
	 */
	inline float getLuminance() const{
		return (r * 0.298912f + g * 0.586611f + b * 0.114477f);
	}

	//--------------------------------------------------------------------------
	/**
	 * 色の線形補間
	 * @param source 開始色
	 * @param target 対象色
	 * @param alpha ブレンド係数
	 * @return 線形補間された色
	 */
	inline static Color4f lerp(
		const Color4f& source, const Color4f& target, float alpha){
		float beta = 1.f - alpha;
		Color4f result;
		result.r = source.r * beta + target.r * alpha;
		result.g = source.g * beta + target.g * alpha;
		result.b = source.b * beta + target.b * alpha;
		result.a = source.a * beta + target.a * alpha;
		return result;
	}

	//--------------------------------------------------------------------------
	// 論理演算
	//--------------------------------------------------------------------------
	/**
	 * 同じ値かどうか
	 * @param target 比較するカラー
	 * @return 同じ値であればtrueを返す
	 */
	inline bool operator ==(const Color4f& target) const{
		return ((r == target.r) && (g == target.g) &&
			(b == target.b) && (a == target.a));
	}

	/**
	 * 同じ値かどうか
	 * @param target 比較するカラー
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じ値であればtrueを返す
	 */
	inline bool epsilonEquals(const Color4f& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (
			(Math::abs(r - target.r) <= epsilon) &&
			(Math::abs(g - target.g) <= epsilon) &&
			(Math::abs(b - target.b) <= epsilon) &&
			(Math::abs(a - target.a) <= epsilon));
	}

	/**
	 * 同じ値でないかどうか
	 * @param target 比較するカラー
	 * @return 同じ値でなければtrueを返す
	 */
	inline bool operator !=(const Color4f& target) const{
		return ((r != target.r) || (g != target.g) ||
			(b != target.b) || (a != target.a));
	}

	/**
	 * 同じ値でないかどうか
	 * @param target 比較するカラー
	 * @param epsilon 誤差
	 * @return 誤差の範囲内で同じでない値であればtrueを返す
	 */
	inline bool notEpsilonEquals(const Color4f& target, float epsilon) const{
		Assert(epsilon >= 0.f);
		return (
			(Math::abs(r - target.r) > epsilon) ||
			(Math::abs(g - target.g) > epsilon) ||
			(Math::abs(b - target.b) > epsilon) ||
			(Math::abs(a - target.a) > epsilon));
	}

	//--------------------------------------------------------------------------
	// その他
	//--------------------------------------------------------------------------
	/**
	 * 文字列化
	 * @return カラーの文字列表記
	 */
	inline String toString() const{
		String returnString;
		returnString.format("( %.8f, %.8f, %.8f, %.8f )", r, g, b, a);
		return returnString;
	}

	//--------------------------------------------------------------------------
private:

};

//------------------------------------------------------------------------------
} // End of namespace Lamp
#endif // End of COLOR_4F_H_
//------------------------------------------------------------------------------
