using UnityEngine;
using System.Collections;
using System.Security.Cryptography;
using System.Runtime.CompilerServices;

/****
 * 	Magic Class By Kcm :)
 */

[System.Serializable]
public class ZOColor
{
    float _r;
    float _g;
    float _b;

    float _h;
    float _s;
    float _v;

    float _H;
    float _S;
    float _L;

    float _X;
    float _Y;
    float _Z;

    float _CieL;
    float _Ciea;
    float _Cieb;

    static public readonly float DEFAUTL_SAT = 0.33f;
    static public readonly float DEFAUTL_BLACK = 0.20f;

    static public float precisionBlack = DEFAUTL_BLACK;         // Image plus ou moins sombre 0.0f -> 1.0f
    static public float precisionSaturation = DEFAUTL_SAT;      // différence de force des couleurs => min : tout noir
    static public bool detectColors = true;

	// Accessors -------------------------------------------------------------

	public float r
    {
        get { if (!rgb) ComputeRGB(); return _r; }
        set { if (!rgb) ComputeRGB(); _r = value; hsv = false; hsl = false; xyz = false; cieLab = false; }
    }
    public float g
    {
        get { if (!rgb) ComputeRGB(); return _g; }
        set { if (!rgb) ComputeRGB(); _g = value; hsv = false; hsl = false; xyz = false; cieLab = false; }
    }
    public float b
    {
        get { if (!rgb) ComputeRGB(); return _b; }
        set { if (!rgb) ComputeRGB(); _b = value; hsv = false; hsl = false; xyz = false; cieLab = false; }
    }

    public float h
    {
        get { if (!hsv) ComputeHSV(); return _h; }
        set
        {
            if (!hsv)
            {
                ComputeHSV();
            }
            rgb = false;
            hsl = false;
            xyz = false;
            cieLab = false;
            _h = value;
        }
    }
    public float s
    {
        get { if (!hsv) ComputeHSV(); return _s; }
        set { if (!hsv) ComputeHSV(); _s = value; rgb = false; hsl = false; xyz = false; cieLab = false; }
    }
    public float v
    {
        get { if (!hsv) ComputeHSV(); return _v; }
        set { if (!hsv) ComputeHSV(); _v = value; rgb = false; hsl = false; xyz = false; cieLab = false; }
    }

    public float H
    {
        get { if (!hsl) ComputeHSL(); return _H; }
        set { if (!hsl) ComputeHSL(); _H = value; rgb = false; hsv = false; xyz = false; cieLab = false; }
    }
    public float S
    {
        get { if (!hsl) ComputeHSL(); return _S; }
        set { if (!hsl) ComputeHSL(); _S = value; rgb = false; hsv = false; xyz = false; cieLab = false; }
    }
    public float L
    {
        get { if (!hsl) ComputeHSL(); return _L; }
        set { if (!hsl) ComputeHSL(); _L = value; rgb = false; hsv = false; xyz = false; cieLab = false; }
    }

    public float X
    {
        get { if (!xyz) ComputeXYZ(); return _X; }
        set { if (!xyz) ComputeXYZ(); _X = value; rgb = false; hsv = false; hsl = false; cieLab = false; }
    }
    public float Y
    {
        get { if (!xyz) ComputeXYZ(); return _Y; }
        set
        {
            if (!xyz)
            {
                ComputeXYZ();
            }
            _Y = value;
            rgb = false;
            hsv = false;
            hsl = false;
            cieLab = false;
        }
    }
    public float Z
    {
        get { if (!xyz) ComputeXYZ(); return _Z; }
        set { if (!xyz) ComputeXYZ(); _Z = value; rgb = false; hsv = false; hsl = false; cieLab = false; }
    }

    public float CieL
    {
        get { if (!cieLab) ComputeCieLab(); return _CieL; }
    }
    public float Ciea
    {
        get { if (!cieLab) ComputeCieLab(); return _Ciea; }
    }
    public float Cieb
    {
        get { if (!cieLab) ComputeCieLab(); return _Cieb; }
    }

    bool hsv = false;
    bool rgb = false;
    bool hsl = false;
    bool xyz = false;
    bool cieLab = false;

    // -------------------------------------------------------------

    public ZOColor()
    {
        _r = 0.0f;
        _g = 0.0f;
        _b = 0.0f;

        _h = 0.0f;
        _s = 0.0f;
        _v = 0.0f;

        _H = 0.0f;
        _S = 0.0f;
        _L = 0.0f;

        rgb = true;
        hsv = true;
        hsl = true;
        xyz = false;
        cieLab = false;
    }

    // -------------------------------------------------------------

    public ZOColor(Color32 color32)
    {
        _r = ((float)color32.r) / 255.0f;
        _g = ((float)color32.g) / 255.0f;
        _b = ((float)color32.b) / 255.0f;

        rgb = true;
        hsv = false;
        hsl = false;
        xyz = false;
        cieLab = false;
	}

    // -------------------------------------------------------------

    public ZOColor(float r_, float g_, float b_)
    {
        _r = r_;
        _g = g_;
        _b = b_;

        rgb = true;
        hsv = false;
        hsl = false;
        xyz = false;
        cieLab = false;
    }

    // -------------------------------------------------------------

    public ZOColor(string hex)
    {
        Set(hex);
    }

    // -------------------------------------------------------------

    public void Set(UnityEngine.Color color)
    {
        _r = color.r;
        _g = color.g;
        _b = color.b;

        rgb = true;
        hsv = false;
        hsl = false;
        xyz = false;
        cieLab = false;
    }

    // -------------------------------------------------------------

    public void Set(string hex)
    {
        if (hex == null || hex.Length != 7 || hex[0] != '#')
        {
            SetBlack();
        }
        else
        {
            _r = byte.Parse(hex.Substring(1, 2), System.Globalization.NumberStyles.HexNumber) / 255.0f;
            _g = byte.Parse(hex.Substring(3, 2), System.Globalization.NumberStyles.HexNumber) / 255.0f;
            _b = byte.Parse(hex.Substring(5, 2), System.Globalization.NumberStyles.HexNumber) / 255.0f;

            rgb = true;
            hsv = false;
            hsl = false;
            xyz = false;
            cieLab = false;
        }
    }

    // -------------------------------------------------------------

    public ZOColor(ZOColor _t)
    {
        rgb = _t.rgb;
        _r = _t._r;
        _g = _t._g;
        _b = _t._b;

        hsv = _t.hsv;
        _h = _t._h;
        _s = _t._s;
        _v = _t._v;

        hsl = _t.hsl;
        _H = _t._H;
        _S = _t._S;
        _L = _t._L;

        xyz = _t.xyz;
        _X = _t._X;
        _Y = _t._Y;
        _Z = _t._Z;

		cieLab = _t.cieLab;
		_CieL = _t._CieL;
		_Ciea = _t._Ciea;
		_Cieb = _t._Cieb;
    }

    // -------------------------------------------------------------

    void ComputeHSV()
    {
        if (hsv) return;

        if (hsl)
        {
            // Error here ?
            _h = _H;
            float L = _L;
            float S = _S;
            L *= 2.0f;
            S *= (L <= 1.0f) ? L : 2.0f - L;
            _v = (L + S) / 2.0f;

            if (L + S > 0.0f)
            {
                _s = (2.0f * S) / (L + S);
            }
            else
            {
                _s = 0.0f;
            }
        }
        else
        {
            if (!rgb)
            {
                ComputeRGB();
            }

            float max = Mathf.Max(_r, Mathf.Max(_g, _b));
            float min = Mathf.Min(_r, Mathf.Min(_g, _b));
            float delta = max - min;

            _v = max;

            if (max != 0.0f)
                _s = delta / max;
            else
                _s = 0.0f;

            if (_s == 0.0f)
            {
                _h = 0.0f;
            }
            else
            {
                if (_r == max) _h = (_g - _b) / delta;
                else if (_g == max) _h = 2.0f + (_b - _r) / delta;
                else if (_b == max) _h = 4.0f + (_r - _g) / delta;

                _h /= 6.0f;

                if (_h < 0.0f) _h += 1.0f;
            }

        }

        hsv = true;

    }

    // -------------------------------------------------------------

    void ComputeRGB()
    {
        if (rgb) return;

        if (hsv == true || xyz == false)
        {
            if (hsv == false)
            {
                ComputeHSV();
            }
            int i = (int)Mathf.Floor(_h * 6.0f);
            float f = _h * 6.0f - i;
            float p = _v * (1.0f - _s);
            float q = _v * (1.0f - f * _s);
            float t = _v * (1.0f - (1.0f - f) * _s);

            switch (i % 6)
            {
                case 0: _r = _v; _g = t; _b = p; break;
                case 1: _r = q; _g = _v; _b = p; break;
                case 2: _r = p; _g = _v; _b = t; break;
                case 3: _r = p; _g = q; _b = _v; break;
                case 4: _r = t; _g = p; _b = _v; break;
                case 5: _r = _v; _g = p; _b = q; break;
            }			
		}
		else if (xyz == true)
        {
            //var_X = X / 100        //X from 0 to  95.047      (Observer = 2°, Illuminant = D65)
            //var_Y = Y / 100        //Y from 0 to 100.000
            //var_Z = Z / 100        //Z from 0 to 108.883

            _r = _X * 3.2406f + _Y * -1.5372f + _Z * -0.4986f;
            _g = _X * -0.9689f + _Y * 1.8758f + _Z * 0.0415f;
            _b = _X * 0.0557f + _Y * -0.2040f + _Z * 1.0570f;

            if (_r > 0.0031308f) _r = 1.055f * (Mathf.Pow(_r, (1.0f / 2.4f))) - 0.055f;
            else _r = 12.92f * _r;
            if (_g > 0.0031308f) _g = 1.055f * (Mathf.Pow(_g, (1.0f / 2.4f))) - 0.055f;
            else _g = 12.92f * _g;
            if (_b > 0.0031308f) _b = 1.055f * (Mathf.Pow(_b, (1.0f / 2.4f))) - 0.055f;
            else _b = 12.92f * _b;

            _r = Mathf.Clamp01(_r);
            _g = Mathf.Clamp01(_g);
            _b = Mathf.Clamp01(_b);
        }

        rgb = true;
	}

    // -------------------------------------------------------------

    void ComputeHSL()
    {
        if (hsl) return;
        if (hsv == false) ComputeHSV();

        _H = _h;
        _L = (2.0f - _s) * _v;
        _S = _s * _v;

        if (_L > 0.0f)
        {
            _S /= (_L <= 1) ? _L : 2.0f - _L;
        }
        else
        {
            _S = 0.0f;
        }
        _L /= 2.0f;

        hsl = true;
    }

    // -------------------------------------------------------------
    /*
    public Shape.SHAPE_TYPE GetShapeColor()
    {
        if (!hsl) ComputeHSL();

        float precisionH = 0.2f;
        float precisionS = 0.4f;

        if (_L < 0.25f || (_S < 0.3f && _L < 0.5f))
        {
            //Debug.Log(ToString());
            return Shape.SHAPE_TYPE.BLACK;
        }

        if (_S < 0.25f || _L > 0.7f) return Shape.SHAPE_TYPE.NONE;

      //
        if (0.5f - Mathf.Abs(_H - 0.5f) < precisionH)
        {

            return Shape.SHAPE_TYPE.RED;
        }
        if (Mathf.Abs(_H - 0.3333f) < precisionH) return Shape.SHAPE_TYPE.GREEN;
        if (Mathf.Abs(_H - 0.6666f) < precisionH) return Shape.SHAPE_TYPE.BLUE;

        return Shape.SHAPE_TYPE.BLACK;
    }

    // -------------------------------------------------------------

    public bool IsColor(Shape.SHAPE_TYPE colorType, float precision)
    {
        if (!hsl) ComputeHSL();

        if (colorType == Shape.SHAPE_TYPE.BLACK)
        {
            return _L < 0.25f || (_S < 0.3f && _L < 0.5f);
        }

        if (_S < 0.25f || _L > 0.8f || _L < 0.3f) return false;

        precision = 0.22f;
        switch (colorType)
        {
            case Shape.SHAPE_TYPE.RED: return 0.5f - Mathf.Abs(_H - 0.5f) < precision; // other solution : return Mathf.Abs(((_h + 0.5f) % 1.0f) - 0.5f) < precision;
            case Shape.SHAPE_TYPE.GREEN: return Mathf.Abs(_H - 0.3333f) < precision;
            case Shape.SHAPE_TYPE.BLUE: return Mathf.Abs(_H - 0.6666f) < precision;
        }

        return false;
    }

    */

    /*public Shape.SHAPE_TYPE GetShapeColor()
    {
        if (!hsv) ComputeHSV();

        float precisionH = 0.1666666667f;

        // Check white
        if (_s < 0.25f && _v > 0.6f) return Shape.SHAPE_TYPE.INVALID;

        // v: La brillance de la couleur
        if (_v < precisionBlack) return Shape.SHAPE_TYPE.BLACK;

        // s: La pureté de la couleur
        if (Mathf.Abs(_h - 0.3333f) < precisionH && _s > precisionSaturation) return Shape.SHAPE_TYPE.GREEN;
        if (Mathf.Abs(_h - 0.6666f) < precisionH && _s > precisionSaturation) return Shape.SHAPE_TYPE.BLUE;

        if (_v < precisionBlack + 0.17f) return Shape.SHAPE_TYPE.BLACK;

        if (0.5f - Mathf.Abs(_h - 0.5f) < precisionH && _s > precisionSaturation) return Shape.SHAPE_TYPE.RED;

        // s: La pureté de la couleur
        if (Mathf.Abs(_h - 0.3333f) < precisionH && _s > precisionSaturation * 0.5f) return Shape.SHAPE_TYPE.GREEN;
        if (Mathf.Abs(_h - 0.6666f) < precisionH && _s > precisionSaturation * 0.5f) return Shape.SHAPE_TYPE.BLUE;

        return Shape.SHAPE_TYPE.INVALID;
    }*/

    // -------------------------------------------------------------
    /*
    public bool IsColor(DYG.SHAPE_TYPE colorType, float _precision)
    {
        if (!rgb) ComputeRGB();
        if (!hsv) ComputeHSV();

        switch (colorType)
        {
            case Shape.SHAPE_TYPE.BLACK: return _v < precisionBlack + 0.15f || (_v < precisionBlack + 0.35f && _s < 0.4f);  
            case Shape.SHAPE_TYPE.BLUE: return _b > _r && _b > _g && _s > precisionSaturation * 0.5f && _v > precisionBlack - 0.1f; //s > 35 ?
            case Shape.SHAPE_TYPE.GREEN: return _g > _r && _g > _b && _s > precisionSaturation * 0.5f && _v > precisionBlack - 0.1f;
            case Shape.SHAPE_TYPE.RED: return _r > _b && _r > _g && _s > precisionSaturation * 0.5f && _v > precisionBlack - 0.1f;
        }

        return false;
    }
    */

    /*
    return (dist < 10.0f);



    if (!hsv) ComputeHSV();

    float precisionColor = 0.22f;       //couleur
    float precisionSaturation = 0.22f;  // pureté de la couleur
    float precisionVtruc = 0.25f;       // brillance

    switch (colorType)
    {
        case Shape.SHAPE_TYPE.BLACK: return _v < 0.30f || ( _v < 0.5f && _s < 0.4f);
        case Shape.SHAPE_TYPE.RED: return 0.5f - Mathf.Abs(_h - 0.5f) < precisionColor && _s > precisionSaturation && _v > precisionVtruc; // other solution : return Mathf.Abs(((_h + 0.5f) % 1.0f) - 0.5f) < precision;
        case Shape.SHAPE_TYPE.GREEN: return Mathf.Abs(_h - 0.3333f) < precisionColor && _s > precisionSaturation && _v > precisionVtruc;
        case Shape.SHAPE_TYPE.BLUE: return Mathf.Abs(_h - 0.6666f) < precisionColor && _s > precisionSaturation && _v > precisionVtruc;
    }

    return false;
}
*/

    // -------------------------------------------------------------

    public UnityEngine.Color GetColor()
    {
        if (!rgb) ComputeRGB();
        return new UnityEngine.Color(_r, _g, _b);
    }

    // -------------------------------------------------------------

    public bool IsClose(ZOColor color)
    {
        /*if (((r - color.r) * (r - color.r) + (g - color.g) * (g - color.g) + (b - color.b) * (b - color.b)) < 0.2f)
        {
            return true;
        }
        else
        {
            return false;
        }*/



        if ((Mathf.Abs(h - color.h) < 0.3f) &&
            (Mathf.Abs(s - color.s) < 0.2f) &&
            (Mathf.Abs(v - color.v) < 0.2f))
        {
            return true;
        }

        return false;

    }

    // -------------------------------------------------------------

    public void SetBlack()
    {
        rgb = true;
        _r = 0.0f;
        _g = 0.0f;
        _b = 0.0f;

        hsv = true;
        _h = 0.0f;
        _s = 0.0f;
        _v = 0.0f;

        hsl = true;
        _H = 0.0f;
        _S = 0.0f;
        _L = 0.0f;

        xyz = false;
        cieLab = false;
    }
    // -------------------------------------------------------------

    public void SetWhite()
    {
        rgb = true;
        _r = 1.0f;
        _g = 1.0f;
        _b = 1.0f;

        hsv = true;
        _h = 0.0f;
        _s = 0.0f;
        _v = 1.0f;

        hsl = true;
        _H = 0.0f;
        _S = 0.0f;
        _L = 1.0f;

        xyz = false;
        cieLab = false;
    }

    // -------------------------------------------------------------
    // TODO

    /*public void SetBlack()
    {
        rgb = true;
        _r = 0.0f;
        _g = 0.0f;
        _b = 0.0f;

        hsv = true;
        _h = 0.0f;
        _s = 0.0f;
        _v = 0.0f;

        hsl = true;
        _H = 0.0f;
        _S = 0.0f;
        _L = 0.0f;

        xyz = false;
        cieLab = false;
    }*/

    // -------------------------------------------------------------

    public override string ToString()
    {
        return (rgb ? "RGB(" + _r + " - " + _g + " - " + _b + ")" : "") +
               (hsv ? "HSV(" + _h + " - " + _s + " - " + _v + ")" : "") +
               (hsl ? "HSL(" + _H + " - " + _S + " - " + _L + ")" : "") +
               (xyz ? "XYZ(" + _X + " - " + _Y + " - " + _Z + ")" : "") +
               (cieLab ? "CieLab(" + _CieL + " - " + _Ciea + " - " + _Cieb + ")" : "");
    }


    // ------------------------------------------------------------------------------------------

    public static ZOColor red
    {
        get
        {
            return new ZOColor(1.0f, 0.0f, 0.0f);
        }
    }

    // ------------------------------------------------------------------------------------------

    public static ZOColor green
    {
        get
        {
            return new ZOColor(0.0f, 1.0f, 0.0f);
        }
    }

    // ------------------------------------------------------------------------------------------

    public static ZOColor white
    {
        get
        {
            return new ZOColor(1.0f, 1.0f, 1.0f);
        }
    }

    // -------------------------------------------------------------

    private void ComputeXYZ()
    {
        if (xyz) return;

        if (!rgb) ComputeRGB();

        // convert sRGB (_r,_g,_b) to linear-rgb (r,g,b)
        float r = (_r <= 0.04045f) ? _r / 12.92f : Mathf.Pow((_r + 0.055f) / 1.055f, 2.4f);
        float g = (_g <= 0.04045f) ? _g / 12.92f : Mathf.Pow((_g + 0.055f) / 1.055f, 2.4f);
        float b = (_b <= 0.04045f) ? _b / 12.92f : Mathf.Pow((_b + 0.055f) / 1.055f, 2.4f);

        // convert to XYZ (assuming sRGB was D65)
        _X = r * 0.4124564f + g * 0.3575761f + b * 0.1804375f;
        _Y = r * 0.2126729f + g * 0.7151522f + b * 0.0721750f;
        _Z = r * 0.0193339f + g * 0.1191920f + b * 0.9503041f;

        xyz = true;
    }

    // --------------------------------------------------------------------------------------------

    private void ComputeCieLab()
    {
        if (cieLab) return;

        if (!xyz) ComputeXYZ();

        // Rescale X/Y/Z relative to white point D65
        float Xr = 0.95047f, Yr = 1.0f, Zr = 1.08883f;
        float xr = _X / Xr;
        float yr = _Y / Yr;
        float zr = _Z / Zr;

        // tristimulus function
        float eps = 216.0f / 24389.0f, k = 24389.0f / 27.0f;
        float fx = (xr <= eps) ? (k * xr + 16.0f) / 116.0f : Mathf.Pow(xr, 1.0f / 3.0f);
        float fy = (yr <= eps) ? (k * yr + 16.0f) / 116.0f : Mathf.Pow(yr, 1.0f / 3.0f);
        float fz = (zr <= eps) ? (k * zr + 16.0f) / 116.0f : Mathf.Pow(zr, 1.0f / 3.0f);

        // tranform to LAB  
        _CieL = (116 * fy) - 16.0f;
        _Ciea = 500.0f * (fx - fy);
        _Cieb = 200.0f * (fy - fz);

        cieLab = true;
    }


    public float DistanceWith(ZOColor color)
    {
        ComputeCieLab();
        color.ComputeCieLab();

        float distance = Mathf.Sqrt((_CieL - color._CieL) * (_CieL - color._CieL) +
                            (_Ciea - color._Ciea) * (_Ciea - color._Ciea) +
                            (_Cieb - color._Cieb) * (_Cieb - color._Cieb));

        return distance;
    }

    // --------------------------------------------------------------------------------------------

    public string ToHex()
    {
        if (!rgb) ComputeRGB();

        return "#" + ((byte)(r * 255.0f)).ToString("X2") +
                     ((byte)(g * 255.0f)).ToString("X2") +
                     ((byte)(b * 255.0f)).ToString("X2");
    }

    // --------------------------------------------------------------------------------------------
    // Note that Color32 and Color implictly convert to each other. You may pass a Color object to this method without first casting it.

    public static string ColorToHex(Color32 color)
    {
        return "#" + color.r.ToString("X2") + color.g.ToString("X2") + color.b.ToString("X2");
    }

	// --------------------------------------------------------------------------------------------
	public static string ColorToHex(Color color)
	{
		return "#" + ((byte) (255 * color.r)).ToString("X2") + ((byte) (255 * color.g)).ToString("X2") + ((byte) (255 * color.b)).ToString("X2");
	}

	// --------------------------------------------------------------------------------------------

	public static Color HexToColor(string hex)
    {
        if (hex == null || hex[0] != '#' || hex.Length != 7) return Color.black;

		try
		{
            byte r = byte.Parse(hex.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
            byte g = byte.Parse(hex.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
            byte b = byte.Parse(hex.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);
            return new Color32(r, g, b, 255);
		}
        catch
		{
            Debug.LogError("Cannot parse hex value " + hex);
            return Color.black;
        }
    }

	// --------------------------------------------------------------------------------------------

    public void Verify()
    {
        if (rgb)
        {
            if (_r > 1.0f)
            {
                Debug.LogWarning("_r > 1.0f");
            }
		    if (_g > 1.0f) Debug.LogWarning("_g > 1.0f");
		    if (_b > 1.0f) Debug.LogWarning("_b > 1.0f");
        }
	}
}
