//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
//
#ifndef COLOR_H
#define COLOR_H
/**
 * \file Color.hpp
 *
 * Defines the Color class template
 */

#include <Config.hpp>

#include <Clamp.hpp>
#include <math.h>
#include <QColor>
#include <Vector.hpp>
#include <vector>
#include <limits>

namespace mgx 
{
  /**
   * \class Color Color.hpp <Color.hpp>
   * \brief A utility class to encapsulate color data.
   */
  template <class T> class Color : public Vector<4, T> 
  {
  public:
    /**
     * Constructor to convert from one color type to another
     */
    template <typename T1>
    explicit Color(const Color<T1> &color)
      : Vector<4,T>(color[0],color[1],color[2],color[3]) {}
  
    template <typename T1>
    Color(const Vector<4, T1>& color, const T& scale)
      : Vector<4, T>(color[0] * scale, color[1] * scale, color[2] * scale, color[3] * scale) {}
  
    Color(const Vector<4, T>& copy) : Vector<4, T>(copy) {}
  
    Color(const QColor& c) { convertFromQColor(*this, c); }
  
    /**
     * \brief Constructor.
     * \param r Value for red.
     * \param g Value for green.
     * \param b Value for blue.
     * \param a Value for alpha.
     */
    Color(const T& r = T(), const T& g = T(), const T& b = T(), const T& a = T())
      : Vector<4, T>(r, g, b, a) {}
  
    /**
     * \brief Return the red component.
     */
    T& r() { return this->x(); }
  
    /**
     * \brief Return the green component.
     */
    T& g() { return this->y(); }
  
    /**
     * \brief Return the blue component.
     */
    T& b() { return this->z(); }
  
    /**
     * \brief Return the alpha component.
     */
    T& a() { return this->t(); }
  
    /**
     * \brief Return the red component.
     */
    const T& r() const { return this->x(); }
  
    /**
     * \brief Return the green component.
     */
    const T& g() const { return this->y(); }
  
    /**
     * \brief Return the blue component.
     */
    const T& b() const { return this->z(); }
  
    /**
     * \brief Return the alpha component.
     */
    const T& a() const { return this->t(); }
  
    /**
     * \brief Set the red component.
     */
    void r(const T& val) { this->x(val); }
  
    /**
     * \brief Set the green component.
     */
    void g(const T& val) { this->y(val); }
  
    /**
     * \brief Set the blue component.
     */
    void b(const T& val) { this->z(val); }
  
    /**
     * \brief Set the alpha component.
     */
    void a(const T& val) { this->t(val); }
  
    Color<T>& operator=(const Color<T>& c);
    Color<T>& operator=(const Vector<4, T>& c);
    Color<T>& operator=(const T& val);
    Color<T>& operator=(const QColor& c)
    {
      convertFromQColor(*this, c);
      return (*this);
    }
  
    operator QColor() const { return convertToQColor(*this); }
  };
  
  mgx_EXPORT QColor convertToQColor(const Color<float>& c);
  mgx_EXPORT QColor convertToQColor(const Color<double>& c);
  mgx_EXPORT QColor convertToQColor(const Color<long double>& c);
  mgx_EXPORT QColor convertToQColor(const Color<unsigned char>& c);
  mgx_EXPORT QColor convertToQColor(const Color<unsigned short>& c);
  
  mgx_EXPORT void convertFromQColor(Color<float>& c, const QColor& col);
  mgx_EXPORT void convertFromQColor(Color<double>& c, const QColor& col);
  mgx_EXPORT void convertFromQColor(Color<long double>& c, const QColor& col);
  mgx_EXPORT void convertFromQColor(Color<unsigned char>& c, const QColor& col);
  mgx_EXPORT void convertFromQColor(Color<unsigned short>& c, const QColor& col);
  
  /** \brief Assignment of color data. */
  template <class T> Color<T>& Color<T>::operator=(const Color<T>& c)
  {
    this->Vector<4, T>::operator=(c);
    return *this;
  }
  
  template <class T> Color<T>& Color<T>::operator=(const T& val)
  {
    this->Vector<4, T>::operator=(val);
    return *this;
  }
  
  template <class T> Color<T>& Color<T>::operator=(const Vector<4, T>& c)
  {
    this->Vector<4, T>::operator=(c);
    return *this;
  }
  
  /** \brief Return a color based on HSV values. */
  template <class T> Color<T> convertHSVtoRGB(T h, T s, T v)
  {
    // based on Jo's code in medit
  
    Color<T> rgb;
    rgb.a(1.0);
  
    while(h > 360.0)
      h -= 360.0;
    while(h < 0.0)
      h += 360.0;
  
    h /= 60.0;
  
    int i = int(h);
  
    double f = h - i;
    double p = v * (1 - s);
    double q = v * (1 - (s * f));
    double t = v * (1 - (s * (1 - f)));
  
    switch(i) {
    case 0:
      rgb.r(v);
      rgb.g(t);
      rgb.b(p);
      break;
    case 1:
      rgb.r(q);
      rgb.g(v);
      rgb.b(p);
      break;
    case 2:
      rgb.r(p);
      rgb.g(v);
      rgb.b(t);
      break;
    case 3:
      rgb.r(p);
      rgb.g(q);
      rgb.b(v);
      break;
    case 4:
      rgb.r(t);
      rgb.g(p);
      rgb.b(v);
      break;
    case 5:
      rgb.r(v);
      rgb.g(p);
      rgb.b(q);
      break;
    }
  
    return rgb;
  }
  
  /** \brief Return a color based on HSV values. */
  template <class T> Color<T> convertHSVtoRGB(const Color<T>& hsv)
  {
    // based on Jo's code in medit
    Color<T> rgb;
    rgb.a() = hsv.a();
  
    T h = hsv[0];
    T s = hsv[1];
    T v = hsv[2];
  
    while(h > 360.0)
      h -= 360.0;
    while(h < 0.0)
      h += 360.0;
  
    h /= 60.0;
  
    int i = (int)floor(h);
  
    double f = h - i;
    double p = v * (1 - s);
    double q = v * (1 - (s * f));
    double t = v * (1 - (s * (1 - f)));
  
    switch(i) {
    case 0:
      rgb.r(v);
      rgb.g(t);
      rgb.b(p);
      break;
    case 1:
      rgb.r(q);
      rgb.g(v);
      rgb.b(p);
      break;
    case 2:
      rgb.r(p);
      rgb.g(v);
      rgb.b(t);
      break;
    case 3:
      rgb.r(p);
      rgb.g(q);
      rgb.b(v);
      break;
    case 4:
      rgb.r(t);
      rgb.g(p);
      rgb.b(v);
      break;
    case 5:
      rgb.r(v);
      rgb.g(p);
      rgb.b(q);
      break;
    }
  
    return rgb;
  }
  
  /** \brief Return a color based on HSV values. */
  template <class T> Color<T> convertRGBtoHSV(const Color<T>& rgb)
  {
    // based on Wikipedia's page
  
    T r = rgb.r();
    T g = rgb.g();
    T b = rgb.b();
  
    T M = std::max(std::max(r, g), b);
    T m = std::min(std::min(r, g), b);
    T c = M - m;
  
    T h;
    if(c == 0)
      h = 0;
    else if(M == r)
      h = fmod((g - b) / c, 6);
    else if(M == g)
      h = (b - r) / c + 2;
    else
      h = (r - g) / c + 4;
    h *= 60;
    T v = M;
    T s;
    if(c == 0)
      s = 0;
    else
      s = c / v;
  
    return Color<T>(h, s, v, rgb.a());
  }

  typedef Color<float> Colorf;
  typedef Color<uchar> Colorb;
  typedef std::vector<Colorb> ColorbVec;

  template <> template <>
  inline Colorb::Color(const Colorf &color)
    : Vector<4,uchar>(color[0] * 255,color[1] * 255,color[2] * 255,color[3] * 255) {}

  template <> template <>
  inline Colorf::Color(const Colorb &color)
    : Vector<4,float>(color[0] / 255.,color[1] / 255.,color[2] / 255.,color[3] / 255.) {}

  namespace comp
  {
    /// Color composition modes
    enum CompositeMode
    {
      SOURCE, DEST,
      OVER, DEST_OVER,
      XOR, ADD, SATURATE,
      LastSimpleComposite = SATURATE,

      MULTIPLY, SCREEN, OVERLAY,
      DARKEN, LIGHTEN,
      LINEAR_DODGE, COLOR_DODGE, LINEAR_BURN, COLOR_BURN,
      HARD_LIGHT, SOFT_LIGHT,
// Windows requires this
#undef DIFFERENCE
      DIFFERENCE, EXCLUSION,
      LastSeparableComposite = EXCLUSION,
      LastComposite = EXCLUSION
    };
  }

  /// Compose a source with dest
  Colorf composite(const Colorf &dest, const Colorf &source,
                   comp::CompositeMode mode = comp::OVER);

  /// Compose a source with dest
  inline Colorb composite(const Colorb &dest, const Colorb &source,
                          comp::CompositeMode mode = comp::OVER)
  {
    return Colorb(composite(Colorf(dest),Colorf(source),mode));
  }

  /// Get the list of composition modes
  QStringList compModes();

  /// Map Gui input string to Composition mode
  comp::CompositeMode stringToCompMode(const QString &s);

  /// Map Gui input string to Colorb
  Colorb stringToColorb(const QString &s);
}
#endif
