#region PDFsharp - A .NET library for processing PDF
//
// Authors:
//   Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
//
// Copyright (c) 2005-2007 empira Software GmbH, Cologne (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;
#if Gdip
using System.Drawing;
#endif
#if Wpf
using System.Windows.Media;
#endif
using PdfSharp.Internal;

namespace PdfSharp.Drawing
{
  ///<summary>
  /// Represents a RGB, CMYK, or gray scale color.
  /// </summary>
  [DebuggerDisplay("(A={A}, R={R}, G={G}, B={B})")]
  public struct XColor
  {
    XColor(uint argb)
    {
      this.cs = XColorSpace.Rgb;
      this.a = (byte)((argb >> 24) & 0xff) / 255f;
      this.r = (byte)((argb >> 16) & 0xff);
      this.g = (byte)((argb >> 8) & 0xff);
      this.b = (byte)(argb & 0xff);
      this.c = 0;
      this.m = 0;
      this.y = 0;
      this.k = 0;
      this.gs = 0;
      RgbChanged();
      cs.GetType(); // Suppress warning
    }

    XColor(byte alpha, byte red, byte green, byte blue)
    {
      this.cs = XColorSpace.Rgb;
      this.a = alpha / 255f;
      this.r = red;
      this.g = green;
      this.b = blue;
      this.c = 0;
      this.m = 0;
      this.y = 0;
      this.k = 0;
      this.gs = 0;
      RgbChanged();
      cs.GetType(); // Suppress warning
    }

    XColor(double cyan, double magenta, double yellow, double black)
    {
      this.cs = XColorSpace.Cmyk;
      this.a = 1;
      this.c = (float)(cyan > 1 ? 1 : (cyan < 0 ? 0 : cyan));
      this.m = (float)(magenta > 1 ? 1 : (magenta < 0 ? 0 : magenta));
      this.y = (float)(yellow > 1 ? 1 : (yellow < 0 ? 0 : yellow));
      this.k = (float)(black > 1 ? 1 : (black < 0 ? 0 : black));
      this.r = 0;
      this.g = 0;
      this.b = 0;
      this.gs = 0f;
      CmykChanged();
    }

    XColor(double gray)
    {
      this.cs = XColorSpace.GrayScale;
      if (gray < 0)
        this.gs = 0;
      else if (gray > 1)
        this.gs = 1;
      this.gs = (float)gray;

      this.a = 1;
      this.r = 0;
      this.g = 0;
      this.b = 0;
      this.c = 0;
      this.m = 0;
      this.y = 0;
      this.k = 0;
      GrayChanged();
    }

    XColor(Color color)
      : this(color.A, color.R, color.G, color.B)
    { }

#if Gdip
    XColor(KnownColor knownColor)
      : this(Color.FromKnownColor((System.Drawing.KnownColor)knownColor))
    { }
#endif

    internal XColor(XKnownColor knownColor)
      : this(XKnownColorTable.KnownColorToArgb(knownColor))
    { }

    public static XColor FromArgb(int argb)
    {
      return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
    }

    public static XColor FromArgb(uint argb)
    {
      return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
    }

    // from System.Drawing.Color
    //public static XColor FromArgb(int alpha, Color baseColor);
    //public static XColor FromArgb(int red, int green, int blue);
    //public static XColor FromArgb(int alpha, int red, int green, int blue);
    //public static XColor FromKnownColor(KnownColor color);
    //public static XColor FromName(string name);

    public static XColor FromArgb(int red, int green, int blue)
    {
      CheckByte(red, "red");
      CheckByte(green, "green");
      CheckByte(blue, "blue");
      return new XColor((byte)255, (byte)red, (byte)green, (byte)blue);
    }

    public static XColor FromArgb(int alpha, int red, int green, int blue)
    {
      CheckByte(alpha, "alpha");
      CheckByte(red, "red");
      CheckByte(green, "green");
      CheckByte(blue, "blue");
      return new XColor((byte)alpha, (byte)red, (byte)green, (byte)blue);
    }

    public static XColor FromArgb(Color color)
    {
      return new XColor(color);
    }

    public static XColor FromArgb(int alpha, XColor color)
    {
      color.A = ((byte)alpha) / 255.0;
      return color;
    }

    public static XColor FromArgb(int alpha, Color color)
    {
      return new XColor(alpha, color.R, color.G, color.B);
    }

    public static XColor FromCmyk(double cyan, double magenta, double yellow, double black)
    {
      return new XColor(cyan, magenta, yellow, black);
    }

    public static XColor FromGrayScale(double grayScale)
    {
      return new XColor(grayScale);
    }

#if Gdip
    /// <summary>
    /// Creates an XColor from the specified pre-defined color.
    /// </summary>
    public static XColor FromKnownColor(KnownColor color)
    {
      return new XColor(color);
    }
#endif

    /// <summary>
    /// Creates an XColor from the specified name of a pre-defined color.
    /// </summary>
    public static XColor FromName(string name)
    {
#if Gdip
      // The implementation in System.Drawing.dll is interesting. It uses a ColorConverter
      // with hash tables, locking mechanisms etc. I'm not sure what problems that solves.
      // So I don't use the source, but the reflection.
      try
      {
        return new XColor((KnownColor)Enum.Parse(typeof(KnownColor), name, true));
      }
      catch { }
#endif
      return XColor.Empty;
    }

    /// <summary>
    /// Gets or sets the color space to be used for PDF generation.
    /// </summary>
    public XColorSpace ColorSpace
    {
      get { return this.cs; }
      set
      {
        if (!Enum.IsDefined(typeof(XColorSpace), value))
          throw new InvalidEnumArgumentException("value", (int)value, typeof(XColorSpace));
        this.cs = value;
      }
    }

    /// <summary>
    /// Indicates whether this XColor structure is uninitialized.
    /// </summary>
    public bool IsEmpty
    {
      get { return this == XColor.Empty; }
    }

#if Gdip
#if GdipUseGdiObjects
    public static implicit operator XColor(Color color)
    {
      return new XColor(color);
    }
#endif

    ///<summary>
    /// Creates a System.Drawing.Color object from this color.
    /// </summary>
    public Color ToGdiColor()
    {
      return Color.FromArgb((int)(this.a * 255), this.r, this.g, this.b);
    }
#endif

#if Wpf
    ///<summary>
    /// Creates a System.Windows.Media.Color object from this color.
    /// </summary>
    public Color ToWpfColor()
    {
      return Color.FromArgb((byte)(this.a * 255), this.r, this.g, this.b);
    }
#endif

    public override bool Equals(object obj)
    {
      if (obj is XColor)
      {
        XColor color = (XColor)obj;
        if (this.r == color.r && this.g == color.g && this.b == color.b &&
          this.c == color.c && this.m == color.m && this.y == color.y && this.k == color.k &&
          this.gs == color.gs)
        {
          return this.a == color.a;
        }
      }
      return false;
    }

    public override int GetHashCode()
    {
      return ((byte)(this.a * 255)) ^ this.r ^ this.g ^ this.b;
      // ^ *(int*)&this.c ^ *(int*)&this.m ^ *(int*)&this.y ^ *(int*)&this.k;
    }

    public static bool operator ==(XColor left, XColor right)
    {
      if (left.r == right.r && left.g == right.g && left.b == right.b &&
          left.c == right.c && left.m == right.m && left.y == right.y && left.k == right.k &&
          left.gs == right.gs)
      {
        return left.a == right.a;
      }
      return false;
    }

    public static bool operator !=(XColor left, XColor right)
    {
      return !(left == right);
    }

    //public bool IsKnownColor { get; }
    //public bool IsNamedColor { get; }
    //public bool IsSystemColor { get; }

    /// <summary>
    /// Gets the hue-saturation-brightness (HSB) hue value, in degrees, for this color.
    /// </summary>
    /// <returns>The hue, in degrees, of this color. The hue is measured in degrees, ranging from 0 through 360, in HSB color space.</returns>
    public double GetHue()
    {
      if ((this.r == this.g) && (this.g == this.b))
        return 0;

      double value1 = this.r / 255.0;
      double value2 = this.g / 255.0;
      double value3 = this.b / 255.0;
      double value7 = 0;
      double value4 = value1;
      double value5 = value1;
      if (value2 > value4)
        value4 = value2;

      if (value3 > value4)
        value4 = value3;

      if (value2 < value5)
        value5 = value2;

      if (value3 < value5)
        value5 = value3;

      double value6 = value4 - value5;
      if (value1 == value4)
        value7 = (value2 - value3) / value6;
      else if (value2 == value4)
        value7 = 2f + ((value3 - value1) / value6);
      else if (value3 == value4)
        value7 = 4f + ((value1 - value2) / value6);

      value7 *= 60;
      if (value7 < 0)
        value7 += 360;
      return value7;
    }

    /// <summary>
    /// Gets the hue-saturation-brightness (HSB) saturation value for this color.
    /// </summary>
    /// <returns>The saturation of this color. The saturation ranges from 0 through 1, where 0 is grayscale and 1 is the most saturated.</returns>
    public double GetSaturation()
    {
      double value1 = this.r / 255.0;
      double value2 = this.g / 255.0;
      double value3 = this.b / 255.0;
      double value7 = 0;
      double value4 = value1;
      double value5 = value1;
      if (value2 > value4)
        value4 = value2;

      if (value3 > value4)
        value4 = value3;

      if (value2 < value5)
        value5 = value2;

      if (value3 < value5)
        value5 = value3;

      if (value4 == value5)
        return value7;

      double value6 = (value4 + value5) / 2;
      if (value6 <= 0.5)
        return (value4 - value5) / (value4 + value5);
      return (value4 - value5) / ((2f - value4) - value5);
    }

    /// <summary>
    /// Gets the hue-saturation-brightness (HSB) brightness value for this color.
    /// </summary>
    /// <returns>The brightness of this color. The brightness ranges from 0 through 1, where 0 represents black and 1 represents white.</returns>
    public double GetBrightness()
    {
      double value1 = this.r / 255.0;
      double value2 = this.g / 255.0;
      double value3 = this.b / 255.0;
      double value4 = value1;
      double value5 = value1;
      if (value2 > value4)
        value4 = value2;

      if (value3 > value4)
        value4 = value3;

      if (value2 < value5)
        value5 = value2;

      if (value3 < value5)
        value5 = value3;

      return (value4 + value5) / 2;
    }

    ///<summary>
    /// One of the RGB values changed; recalculate other color representations.
    /// </summary>
    void RgbChanged()
    {
      this.cs = XColorSpace.Rgb;
      int c = 255 - this.r;
      int m = 255 - this.g;
      int y = 255 - this.b;
      int k = Math.Min(c, Math.Min(m, y));
      if (k == 255)
        this.c = this.m = this.y = 0;
      else
      {
        float black = 255f - k;
        this.c = (c - k) / black;
        this.m = (m - k) / black;
        this.y = (y - k) / black;
      }
      this.k = this.gs = k / 255f;
    }

    ///<summary>
    /// One of the CMYK values changed; recalculate other color representations.
    /// </summary>
    void CmykChanged()
    {
      this.cs = XColorSpace.Cmyk;
      float black = this.k * 255;
      float factor = 255f - black;
      this.r = (byte)(255 - Math.Min(255f, this.c * factor + black));
      this.g = (byte)(255 - Math.Min(255f, this.m * factor + black));
      this.b = (byte)(255 - Math.Min(255f, this.y * factor + black));
      this.gs = (float)(1 - Math.Min(1.0, 0.3f * this.c + 0.59f * this.m + 0.11 * this.y + this.k));
    }

    ///<summary>
    /// The gray scale value changed; recalculate other color representations.
    /// </summary>
    void GrayChanged()
    {
      this.cs = XColorSpace.GrayScale;
      this.r = (byte)(this.gs * 255);
      this.g = (byte)(this.gs * 255);
      this.b = (byte)(this.gs * 255);
      this.c = 0;
      this.m = 0;
      this.y = 0;
      this.k = 1 - this.gs;
    }

    // Properties

    /// <summary>
    /// Gets or sets the alpha value the specifies the transparency. 
    /// The value is in the range from 1 (opaque) to 0 (completely transparent).
    /// </summary>
    public double A
    {
      get { return this.a; }
      set
      {
        if (value < 0)
          this.a = 0;
        else if (value > 1)
          this.a = 1;
        else
          this.a = (float)value;
      }
    }

    /// <summary>
    /// Gets or sets the red value.
    /// </summary>
    public byte R
    {
      get { return this.r; }
      set { this.r = value; RgbChanged(); }
    }

    /// <summary>
    /// Gets or sets the green value.
    /// </summary>
    public byte G
    {
      get { return this.g; }
      set { this.g = value; RgbChanged(); }
    }

    /// <summary>
    /// Gets or sets the blue value.
    /// </summary>
    public byte B
    {
      get { return this.b; }
      set { this.b = value; RgbChanged(); }
    }

    /// <summary>
    /// Gets the RGB part value of the color. Internal helper function.
    /// </summary>
    internal uint Rgb
    {
      get { return ((uint)this.r << 16) | ((uint)this.g << 8) | (uint)this.b; }
    }

    /// <summary>
    /// Gets the ARGB part value of the color. Internal helper function.
    /// </summary>
    internal uint Argb
    {
      get { return ((uint)(this.a * 255) << 24) | ((uint)this.r << 16) | ((uint)this.g << 8) | (uint)this.b; }
    }

    /// <summary>
    /// Gets or sets the cyan value.
    /// </summary>
    public double C
    {
      get { return this.c; }
      set
      {
        if (value < 0)
          this.c = 0;
        else if (value > 1)
          this.c = 1;
        else
          this.c = (float)value;
        CmykChanged();
      }
    }

    /// <summary>
    /// Gets or sets the magenta value.
    /// </summary>
    public double M
    {
      get { return this.m; }
      set
      {
        if (value < 0)
          this.m = 0;
        else if (value > 1)
          this.m = 1;
        else
          this.m = (float)value;
        CmykChanged();
      }
    }

    /// <summary>
    /// Gets or sets the yellow value.
    /// </summary>
    public double Y
    {
      get { return this.y; }
      set
      {
        if (value < 0)
          this.y = 0;
        else if (value > 1)
          this.y = 1;
        else
          this.y = (float)value;
        CmykChanged();
      }
    }

    /// <summary>
    /// Gets or sets the black (or key) value.
    /// </summary>
    public double K
    {
      get { return this.k; }
      set
      {
        if (value < 0)
          this.k = 0;
        else if (value > 1)
          this.k = 1;
        else
          this.k = (float)value;
        CmykChanged();
      }
    }

    /// <summary>
    /// Gets or sets the gray scale value.
    /// </summary>
    public double GS
    {
      get { return this.gs; }
      set
      {
        if (value < 0)
          this.gs = 0;
        else if (value > 1)
          this.gs = 1;
        else
          this.gs = (float)value;
        GrayChanged();
      }
    }

    /// <summary>
    /// Represents the null color.
    /// </summary>
    public static XColor Empty;

    ///<summary>
    /// Special property for XmlSerializer only.
    /// </summary>
    public string RgbCmykG
    {
      get
      {
        return String.Format(CultureInfo.InvariantCulture,
          "{0};{1};{2};{3};{4};{5};{6};{7};{8}", this.r, this.g, this.b, this.c, this.m, this.y, this.k, this.gs, this.a);
      }
      set
      {
        string[] values = value.Split(';');
        this.r = byte.Parse(values[0], CultureInfo.InvariantCulture);
        this.g = byte.Parse(values[1], CultureInfo.InvariantCulture);
        this.b = byte.Parse(values[2], CultureInfo.InvariantCulture);
        this.c = float.Parse(values[3], CultureInfo.InvariantCulture);
        this.m = float.Parse(values[4], CultureInfo.InvariantCulture);
        this.y = float.Parse(values[5], CultureInfo.InvariantCulture);
        this.k = float.Parse(values[6], CultureInfo.InvariantCulture);
        this.gs = float.Parse(values[7], CultureInfo.InvariantCulture);
        this.a = float.Parse(values[8], CultureInfo.InvariantCulture);
      }
    }

    static void CheckByte(int val, string name)
    {
      if (val < 0 || val > 0xFF)
        throw new ArgumentException(PSSR.InvalidValue(val, name, 0, 255));
    }

    //static void CheckZeroOne(int val, string name)
    //{
    //  if (val < 0 || val > 0xff)
    //    throw new ArgumentException(PSSR.InvalidColorByteValue(val, name, 0, 255));
    //}

    private XColorSpace cs;

    private float a;  // alpha

    private byte r;   // \
    private byte g;   // |--- RGB
    private byte b;   // /

    private float c;  // \
    private float m;  // |--- CMYK
    private float y;  // |
    private float k;  // /

    private float gs; // >--- gray scale
  }
}
