/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  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 file 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2016-09-02
**  Copyright: 2016-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Color.h"
#include "Trace.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

  /*!
    \class Color Color.h
    \brief A rgba color

    QColor belongs to QtGui which prevent its usage (e.g. for storage) in Core libraries.
    This color just ensure an equivalent storage.
    QGpGuiTools::GuiGlobal provides the global functions to transform to a QColor
  */

  Color::Color(Qt::GlobalColor c)
  {
    TRACE;
    switch(c) {
    case Qt::white:
      _rgba=0xffffffff;
      break;
    case Qt::black:
      _rgba=0xff000000;
      break;
    case Qt::red:
      _rgba=0xffff0000;
      break;
    case Qt::darkRed:
      _rgba=0xff800000;
      break;
    case Qt::green:
      _rgba=0xff00ff00;
      break;
    case Qt::darkGreen:
      _rgba=0xff008000;
      break;
    case Qt::blue:
      _rgba=0xff0000ff;
      break;
    case Qt::darkBlue:
      _rgba=0xff000080;
      break;
    case Qt::cyan:
      _rgba=0xff00ffff;
      break;
    case Qt::darkCyan:
      _rgba=0xff008080;
      break;
    case Qt::magenta:
      _rgba=0xffff00ff;
      break;
    case Qt::darkMagenta:
      _rgba=0xff800080;
      break;
    case Qt::yellow:
      _rgba=0xffffff00;
      break;
    case Qt::darkYellow:
      _rgba=0xff808000;
      break;
    case Qt::gray:
      _rgba=0xffa0a0a4;
      break;
    case Qt::darkGray:
      _rgba=0xff808080;
      break;
    case Qt::lightGray:
      _rgba=0xffc0c0c0;
      break;
    case Qt::transparent:
      _rgba=0x00000000;
      break;
    case Qt::color0:
      _rgba=0x00000000;
      break;
    case Qt::color1:
      _rgba=0xffffffff;
      break;
    }
  }

  QString Color::name() const
  {
    TRACE;
    return QString("#%1%2%3%4")
        .arg(alpha(), 2, 16, QChar('0'))
        .arg(red(), 2, 16, QChar('0'))
        .arg(green(), 2, 16, QChar('0'))
        .arg(blue(), 2, 16, QChar('0'));
  }

  bool Color::setNamedColor(const QString& n)
  {
    TRACE;
    bool ok[4];
    memset(ok, true, 4);
    Color c;
    switch(n.count()) {
    case 4:
      c=Color(n.mid(1, 1).toInt(&ok[0], 16),
              n.mid(2, 1).toInt(&ok[1], 16),
              n.mid(3, 1).toInt(&ok[2], 16));
      break;
    case 5:
      c=Color(n.mid(2, 1).toInt(&ok[0], 16),
              n.mid(3, 1).toInt(&ok[1], 16),
              n.mid(4, 1).toInt(&ok[2], 16),
              n.mid(1, 1).toInt(&ok[3], 16));
      break;
    case 7:
      c=Color(n.mid(1, 2).toInt(&ok[0], 16),
              n.mid(3, 2).toInt(&ok[1], 16),
              n.mid(5, 2).toInt(&ok[2], 16));
      break;
    case 9:
      c=Color(n.mid(3, 2).toInt(&ok[0], 16),
              n.mid(5, 2).toInt(&ok[1], 16),
              n.mid(7, 2).toInt(&ok[2], 16),
              n.mid(1, 2).toInt(&ok[3], 16));
      break;
    case 10: // 3 digits does not support alpha, cannot discriminate with 3 channels with 4 digits
      c=Color(n.mid(1, 3).toInt(&ok[0], 16),
              n.mid(4, 3).toInt(&ok[1], 16),
              n.mid(7, 3).toInt(&ok[2], 16));
      break;
    case 13:
      c=Color(n.mid(1, 4).toInt(&ok[0], 16),
              n.mid(5, 4).toInt(&ok[1], 16),
              n.mid(9, 4).toInt(&ok[2], 16));
      break;
    case 17:
      c=Color(n.mid(5, 4).toInt(&ok[0], 16),
              n.mid(9, 4).toInt(&ok[1], 16),
              n.mid(13, 4).toInt(&ok[2], 16),
              n.mid(1, 4).toInt(&ok[3], 16));
      break;
    default:
      ok[0]=false;
      break;
    }
    for(int i=0; i<0; i++) {
      if(!ok[i]) {
        App::log(tr("Bad color name '%1'\n").arg(n) );
        return false;
      }
    }
    *this=c;
    return true;
  }

  /*!
    Same code as in QColor
  */
  void Color::setHsva(int h, int s, int v, int a)
  {
    TRACE;
    if(s==0 || h==360) { // achromatic case
      setRgba(v, v, v, a);
    }
    const double hp=h/60.0;
    const double sp=s/255.0;
    const double vp=v/255.0;
    const int i=hp;
    const double f=hp-i;
    const double p=vp*(1.0-sp);
    double rp, gp, bp;
    if (i & 1) {
      const double q=vp*(1.0-(sp*f));
      switch (i) {
      case 1:
        rp=q;
        gp=vp;
        bp=p;
        break;
      case 3:
        rp=p;
        gp=q;
        bp=vp;
        break;
      case 5:
        rp=vp;
        gp=p;
        bp=q;
        break;
      default:
        rp=0.0;
        gp=0.0;
        bp=0.0;
        break;
      }
    } else {
      const double t=vp*(1.0-(sp*(1.0-f)));
      switch (i) {
      case 0:
        rp=vp;
        gp=t;
        bp=p;
        break;
      case 2:
        rp=p;
        gp=vp;
        bp=t;
        break;
      case 4:
        rp=t;
        gp=p;
        bp=vp;
        break;
      default:
        rp=0.0;
        gp=0.0;
        bp=0.0;
        break;
      }
    }
    setRgba(qRound(rp*USHRT_MAX) >> 8, qRound(gp*USHRT_MAX) >> 8, qRound(bp*USHRT_MAX) >> 8, a);
  }

  /*!
    Same code as in QColor. Qcolor stores unsigned short values and
    scale them to return integers from 0 to 255 (saturation and value) and 360 (hue). This function
    has exactly the same rounding errors as QColor ouput functions.
  */
  void Color::hsv(int& h, int& s, int& v) const
  {
    TRACE;
    const double rp=red()/255.0;
    const double gp=green()/255.0;
    const double bp=blue()/255.0;
    double cmax, cmin;
    minMax(rp, gp, bp, cmin, cmax);
    const double delta=cmax-cmin;
    if(cmax==0) {
      s=0;
    } else {
      s=qRound((delta/cmax)*USHRT_MAX) >> 8;
    }
    v=qRound(cmax*USHRT_MAX) >> 8;
    double hp;
    if(delta==0) {
      h=-1;  // achromatic case, hue is undefined
      s=0;
      return;
    } else if(cmax==rp) {
      hp=(gp-bp)/delta;
    } else if(cmax==gp) {
      hp=(bp-rp)/delta+2.0;
    } else {
      hp=(rp-gp)/delta+4.0;
    }
    if(hp<0.0) {
      hp+=6.0;
    }
    h=qRound(6000.0*hp)/100;
  }

  double Color::gammaExpansion(double csrgb)
  {
    if(csrgb>0.04045) {
      return pow((csrgb+0.0555)/1.055, 2.4);
    } else {
      return csrgb/12.92;
    }
  }

  double Color::gammaCompression(double clinear)
  {
    if(clinear>0.0031308) {
      return 1.055*pow(clinear, 1.0/2.4)-0.055;
    } else {
      return 12.92*clinear;
    }
  }

  /*!
    Source: https://en.wikipedia.org/wiki/Grayscale
  */
  void Color::toGray()
  {
    TRACE;
    double rLinear=gammaExpansion(red()/255.0);
    double gLinear=gammaExpansion(green()/255.0);
    double bLinear=gammaExpansion(blue()/255.0);
    double luminanceLinear=0.2126*rLinear+0.7152*gLinear+0.0722*bLinear;
    int luminanceSrgb=qRound(gammaCompression(luminanceLinear)*255.0);
    //qDebug() << luminanceSrgb/255.0;
    setRgba(luminanceSrgb, luminanceSrgb, luminanceSrgb, alpha());
  }

} // namespace QGpCoreTools

