//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 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.
// 
#include "Color.hpp"
#include "Util.hpp"

namespace mgx 
{
  Colorb stringToColorb(const QString &s)
  {
    QStringList list = s.split(" ");
    int sz = trim(list.size(), 0, 4);
    Colorb result(0,0,0,255);
    for(int i = 0; i < sz; i++)
      result[i] = trim(list[i].toUInt(), 0u, 255u);
    return result;
  }

  QColor convertToQColor(const Color<float>& c) 
	{
    return QColor::fromRgbF(c.r(), c.g(), c.b(), c.a());
  }
  
  QColor convertToQColor(const Color<double>& c) 
	{
    return QColor::fromRgbF(c.r(), c.g(), c.b(), c.a());
  }
  
  QColor convertToQColor(const Color<long double>& c) 
	{
    return QColor::fromRgbF(c.r(), c.g(), c.b(), c.a());
  }
  
  QColor convertToQColor(const Color<unsigned char>& c) 
	{
    return QColor(c.r(), c.g(), c.b(), c.a());
  }
  
  QColor convertToQColor(const Color<unsigned short>& c) 
	{
    return QColor(c.r(), c.g(), c.b(), c.a());
  }
  
  void convertFromQColor(Color<float>& c, const QColor& col)
  {
    c.r() = col.redF();
    c.g() = col.greenF();
    c.b() = col.blueF();
    c.a() = col.alphaF();
  }
  
  void convertFromQColor(Color<double>& c, const QColor& col)
  {
    c.r() = col.redF();
    c.g() = col.greenF();
    c.b() = col.blueF();
    c.a() = col.alphaF();
  }
  
  void convertFromQColor(Color<long double>& c, const QColor& col)
  {
    c.r() = col.redF();
    c.g() = col.greenF();
    c.b() = col.blueF();
    c.a() = col.alphaF();
  }
  
  void convertFromQColor(Color<unsigned char>& c, const QColor& col)
  {
    c.r() = col.red();
    c.g() = col.green();
    c.b() = col.blue();
    c.a() = col.alpha();
  }
  
  void convertFromQColor(Color<unsigned short>& c, const QColor& col)
  {
    c.r() = col.red();
    c.g() = col.green();
    c.b() = col.blue();
    c.a() = col.alpha();
  }


/*
  namespace comp
  {
    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,
      DIFFERENCE, EXCLUSION,
      LastComposite = EXCLUSION
    };
  }
*/

  // Map Gui string to Composition mode
  comp::CompositeMode stringToCompMode(const QString &s)
  {
    if(s == "Normal")
      return comp::OVER;
    else if(s == "Multiply")
      return comp::MULTIPLY;
    else if(s == "Screen")
      return comp::SCREEN;
    else if(s == "Overlay")
      return comp::OVERLAY;
    else if(s == "Darken")
      return comp::DARKEN;
    else if(s == "Lighten")
      return comp::LIGHTEN;
    else if(s == "Linear Dodge")
      return comp::LINEAR_DODGE;
    else if(s == "Linear Burn")
      return comp::LINEAR_BURN;
    else if(s == "Color Dodge")
      return comp::COLOR_DODGE;
    else if(s == "Color Burn")
      return comp::COLOR_BURN;
    else if(s == "Hard Light")
      return comp::HARD_LIGHT;
    else if(s == "Soft Light")
      return comp::SOFT_LIGHT;
    else if(s == "Difference")
      return comp::DIFFERENCE;
    else if(s == "Exclusion")
      return comp::EXCLUSION;
    else
      return comp::OVERLAY;
  }

  QStringList compModes()
  {
    return QStringList() << "Normal" << "Multiply" << "Screen" << "Overlay" << "Darken" << "Lighten" 
      << "Linear Dodge" << "Linear Burn" << "Color Dodge" << "Color Burn" << "Hard Light" 
      << "Soft Light" << "Difference" << "Exclusion";
  }

  // Blend modes: from https://en.wikipedia.org/wiki/Blend_modes
  // and http://cairographics.org/operators
  inline float blendFunc(float dest, float source, comp::CompositeMode mode)
  {
    using namespace comp;
    switch(mode)
    {
    case MULTIPLY: return dest * source;
    case SCREEN: return dest + source - dest * source;
    case OVERLAY:
      return (dest <= 0.5)
        ? (2 * dest * source)
        : (1.f - 2 * (1.f - dest) * (1.f - source));
    case DARKEN: return std::min(dest,source);
    case LIGHTEN: return std::max(dest,source);
    case LINEAR_DODGE: return std::min(1.f, dest + source);
    case LINEAR_BURN: return std::max(0.f, dest + source - 1);
    case COLOR_DODGE:
      return (source < 1.f) ? std::min(1.f, dest / (1 - source)) : 1.f;
    case COLOR_BURN:
      return (source > 0.f) ? (1. - std::min(1.f, (1 - dest) / source)) : 0.f;
    case HARD_LIGHT:
      return (source <= 0.5)
        ? (2 * dest * source)
        : (1.f - 2 * (1.f - dest) * (1.f - source));
    case SOFT_LIGHT:
    {
      double g = (dest <= 0.25f) ? (((16 * dest - 12) * dest + 4) * dest) : sqrt(dest);
      return (source <= 0.5)
        ? (dest - (1.f - 2 * source) * dest * (1.f - dest))
        : (dest + (2 * source - 1.f) * (g - dest));
    }
    case DIFFERENCE:
      return fabs(dest - source);
    case EXCLUSION:
      return dest + source - 2 * dest * source;
    }
    return 0;
  }

  Colorf composite(const Colorf &dest, const Colorf &source, comp::CompositeMode mode)
  {
    using namespace comp;
    Colorf answer;
    if(mode <= LastSimpleComposite)
    // Porter-Duff blend modes
    {
      switch(mode)
      {
      case SOURCE: return source;
      case DEST: return dest;
      case OVER:
        answer.a() = source.a() + dest.a() * (1.f - source.a());
        for(int i = 0 ; i < 3 ; i++)
          answer[i] = (source[i] * source.a() +
                       dest[i] * dest.a() * (1.f - source.a())) / answer.a();
        break;
      case DEST_OVER:
        answer.a() = dest.a() + source.a() * (1.f - dest.a());
        for(int i = 0 ; i < 3 ; i++)
          answer[i] = (dest[i] * dest.a() +
                       source[i] * source.a() * (1.f - dest.a())) / answer.a();
        break;
      case XOR:
        answer.a() = dest.a() + source.a() - 2 * dest.a() * source.a();
        for(int i = 0 ; i < 3 ; i++)
          answer[i] = (source[i] * source.a() * (1.f - dest.a()) +
                       dest[i] * dest.a() * (1.f - source.a())) / answer.a();
        break;
      case ADD:
        answer.a() = std::min(1.f, dest.a() + source.a());
        for(int i = 0 ; i < 3 ; i++)
          answer[i] = (source[i] * source.a() + dest[i] * dest.a()) / answer.a();
        break;
      case SATURATE:
        answer.a() = std::min(1.f, dest.a() + source.a());
        for(int i = 0 ; i < 3 ; i++)
          answer[i] = (std::min(source.a() , 1.f - dest.a()) * source[i] +
                       dest[i] * dest.a()) / answer.a();
        break;
      }
    }
    else if(mode <= LastSeparableComposite)
    // Adobe separable blend modes
    {
      answer.a() = source.a() + dest.a() * (1.f - source.a());
      for(int i = 0 ; i < 3 ; i++)
        answer[i] = ((1.f - dest.a()) * source.a() * source[i] +
                     (1.f - source.a()) * dest.a() * dest[i] +
                     source.a() * dest.a() * blendFunc(source[i],dest[i],mode)) / answer.a();
    }
    return answer;
  }
}
