/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  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-11-08
**  Copyright: 2016-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "ColorPalette.h"
#include "Point.h"
#include "DoubleMatrix.h"

namespace QGpCoreMath {

  /*!
    \class ColorPalette ColorPalette.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  ColorPalette::ColorPalette()
  {
    TRACE;
    _n=0;
    _colors=nullptr;
  }

  ColorPalette::ColorPalette(const ColorPalette& o)
  {
    TRACE;
    _colors=nullptr;
    *this=o;
  }

  /*!
    Description of destructor still missing
  */
  ColorPalette::~ColorPalette()
  {
    TRACE;
    delete [] _colors;
  }

  void ColorPalette::operator=(const ColorPalette& o)
  {
    TRACE;
    _n=o._n;
    delete [] _colors;
    _colors=new Color[_n];
    for(int i=0; i<_n; i++) {
      _colors[i]=o._colors[i];
    }
  }

  bool ColorPalette::operator==(const ColorPalette& o) const
  {
    TRACE;
    if(_n!=o._n) {
      return false;
    }
    for(int i=0; i<_n; i++) {
      if(_colors[i]!=o._colors[i]) {
        return false;
      }
    }
    return true;
  }

  /*!
    All current colors are lost.
  */
  void ColorPalette::resize(int n)
  {
    TRACE;
    if(n<2) {
      n=2;
    }
    if(!_colors || n!=_n) {
      if(_colors) {
        delete [] _colors;
      }
      _colors=new Color[n];
      _n=n;
    }
  }

  void ColorPalette::generateColorScale(int n, Model m, bool reversed, int transparency)
  {
    TRACE;
    switch(m) {
    case Hobi:
      hobi(n, transparency);
      break;
    case McNames:
      mcNames(n, n, transparency);
      break;
    case McNamesClip: // Avoid too dark colors
      mcNames(n, n*5/4, transparency);
      break;
    case Hsv:
      hsv(n, 255, 255, transparency);
      break;
    case Sardine:
      sardineColorScale(n, transparency);
      break;
    case ColdHot:
      coldHot(n, transparency);
      break;
    }
    if(reversed) {
      reverse();
    }
  }

  void ColorPalette::generateGrayScale(int n, Model m, bool reversed, int transparency)
  {
    TRACE;
    sardineGrayScale(n, transparency);
    // HSV or McNames do not have a linear monotonic luminance
    // McNames was supposed to have this monotonic luminance, but not observed
    /*switch(m) {
    case McNames:
      mcNames(n, transparency);
      toGray();
      break;
    case Hsv:
      hsv(n, 255, 255, transparency);
      toGray();
      break;
    case Sardine:
      sardineGrayScale(n, transparency);
      break;
    }*/
    if(reversed) {
      reverse();
    }
  }

  /*!
    if \a b is true, all white colors are set transparent. If false,
    all white colors are set opaque.
  */
  void ColorPalette::setWhiteTransparent(bool b)
  {
    TRACE;
    int white=0x00FFFFFF;
    for(int i=0; i<_n; i++) {
      Color& c=_colors[i];
      if(c.rgb()==white) {
        c.setAlpha(b ? 0 : 0xFF);
      }
    }
  }

  void ColorPalette::toGray()
  {
    TRACE;
    for(int i=0; i<_n; i++) {
      _colors[i].toGray();
    }
  }

  void ColorPalette::reverse()
  {
    TRACE;
    Color * nc=new Color[_n];
    int n1=_n-1;
    for(int i=0; i<_n; i++) {
      nc[i]=_colors[n1-i];
    }
    delete [] _colors;
    _colors=nc;
  }

  /*!
    Implementation inherited from Sardine (D. Demanet).
    It was the default color palette under Sardine
  */
  void ColorPalette::sardineColorScale(int n, int transparency)
  {
    TRACE;
    resize(n);
    if(_n==2) {
      _colors[0]=Color(0, 0, 255, transparency);
      _colors[1]=Color(255, 0, 0, transparency);
    } else if(_n==3) {
      _colors[0]=Color(0, 0, 255, transparency);
      _colors[1]=Color(0, 255, 0, transparency);
      _colors[2]=Color(255, 0, 0, transparency);
    } else {
      double x, r, g, b;

      int n1=_n/3;
      int n2=n1+(_n-n1)/2;
      int i;
      for(i=0; i<n1; i++) {
        x=static_cast<double>(i)/static_cast<double>(n1);
        r=0.;
        g=pow(x, 0.3);
        b=1.0-x*x;
        _colors[i].setRgba(qRound(r*255.0), qRound(g*255.0), qRound(b*255.0), transparency);
      }
      for(i=n1; i<n2; i++) {
        x=static_cast<double>(i-n1)/static_cast<double>(n2-n1);
        r=pow(x, 0.3);
        g=1.0;
        b=0.0;
        _colors[i].setRgba(qRound(r*255.0), qRound(g*255.0), qRound(b*255.0), transparency);
      }
      for(i=n2; i < _n; i++ ) {
        x=static_cast<double>(i-n2)/static_cast<double>(n-n2-1);
        r=1.0;
        g=1.0-x*x;
        b=0.0;
        _colors[i].setRgba(qRound(r*255.0), qRound(g*255.0), qRound(b*255.0), transparency);
      }
    }
  }

  /*!
    Implementation inherited from Sardine (D. Demanet).
    It was the default gray palette under Sardine
  */
  void ColorPalette::sardineGrayScale(int n, int transparency)
  {
    TRACE;
    resize(n);
    double x, g;

    for(int i=0; i<_n; i++) {
      x=static_cast<double>(i)/static_cast<double>(n-1);
      g=1.0-x*x;
      _colors[i]=Color(qRound(g*255.0), qRound(g*255.0), qRound(g*255.0), transparency);
    }
  }

  void ColorPalette::hsv(int n, int sat, int value, int transparency)
  {
    TRACE;
    resize(n);
    for(int i=0; i<_n; i++) {
      _colors[i].setHsva(qRound(static_cast<double>(i)*255.0/static_cast<double>(_n)), sat, value, transparency);
    }
  }

  void ColorPalette::hobi(int n, int transparency)
  {
    TRACE;
    resize(n);
    int n2=_n >> 1;
    _colors[0].setRgba(128, 128, 128, transparency);
    _colors[n2].setRgba(255, 255, 0, transparency);
    _colors[_n-1].setRgba(170, 0, 0, transparency);
    hsvInterpole(0, n2);
    hsvInterpole(n2, _n-1);
  }

  void ColorPalette::coldHot(int n, int transparency)
  {
    TRACE;
    resize(n);
    int n2=_n >> 1;
    _colors[n2-1].setRgba(175, 175, 255, transparency);
    _colors[0].setRgba(0, 0, 255, transparency);
    _colors[n2].setRgba(255, 175, 175, transparency);
    _colors[_n-1].setRgba(255, 0, 0, transparency);
    hsvInterpole(0, n2-1);
    hsvInterpole(n2, _n-1);
  }

  /*!
    Color palette proposed by
    "An Effective Color Scale for Simultaneous Color and Gray-Scale Publications"
    in IEEE SIGNAL PROCESSING MAGAZINE, JANUARY 2006, pp 82-87

    To avoid last dark colors, n0 can be higher than n. The color palette is calculated on \a n0.
  */
  void ColorPalette::mcNames(int n, int n0, int transparency)
  {
    TRACE;
    ASSERT(n0>=n);

    resize(n);
    Point rgb;
    double p=2.0; // Number of cycles that occur over the range
                  // Between 1.5 and 3.0, 2.0 used in paper
    double t;
    double f=sqrt(3)/(n0-1);
    double fw=2.0/(n0-2);
    double nw=(n0-3)*0.5+1.0;
    double c1=2*M_PI/sqrt(3);
    double c2=sqrt(3)*0.5;
    double w;

    Matrix3x3 blueRotation(3), greenRotation(3);
    Angle blueAngle, greenAngle;
    blueAngle.setRadians(asin(1/sqrt(3)));
    greenAngle.setRadians(-M_PI*0.25);
    blueRotation.rotation(ZAxis, blueAngle);
    greenRotation.rotation(YAxis, greenAngle);

    int di=n0-n;
    for(int i=0; i<_n; i++) {
      int i0=i+di;
      t=f*i0;
      w=(i==0 || i==_n-1) ? 0.0 : sqrt(3.0/8.0)*(1.0-fabs((i0-nw)*fw)); // TODO: use hyperbolic function
      rgb=Point(t, w*cos(p*c1*(t-c2)), w*sin(p*c1*(t-c2)));
      rgb=blueRotation*rgb;
      rgb=greenRotation*rgb;
      _colors[i].setRgba(qRound(rgb.x()*255.0), qRound(rgb.y()*255.0), qRound(rgb.z()*255.0), transparency);
    }
  }

  void ColorPalette::rgbInterpole(int imin, int imax)
  {
    TRACE;
    double r, r2, dr, g, g2, dg, b, b2, db, a, a2, da;
    if(_n<=2) {
      return;
    }
    if(imin<0) {
      imin=0;
    }
    if(imax>=_n) {
      imax=_n-1;
    }

    Color& c1=_colors[imin];
    r=c1.red();
    g=c1.green();
    b=c1.blue();
    a=c1.alpha();

    Color& c2=_colors[imax];
    r2=c2.red();
    g2=c2.green();
    b2=c2.blue();
    a2=c2.alpha();

    dr=(r2-r)/static_cast<double>(imax-imin+1);
    dg=(g2-g)/static_cast<double>(imax-imin+1);
    db=(b2-b)/static_cast<double>(imax-imin+1);
    da=(a2-a)/static_cast<double>(imax-imin+1);

    for(int i=imin+1; i<imax; i++) {
      r+=dr;
      g+=dg;
      b+=db;
      a+=da;
      _colors[i].setRgba(qRound(r), qRound(g), qRound(b), qRound(a));
    }
  }

  void ColorPalette::hsvInterpole(int imin, int imax)
  {
    TRACE;
    int h1, h2, s1, s2, v1, v2, a1, a2;
    double h, s, v, a, dh, ds, dv, da;
    if(_n<=2) {
      return;
    }
    if(imin<0) {
      imin=0;
    }
    if(imax>=_n) {
      imax=_n-1;
    }

    Color& c1=_colors[imin];
    c1.hsv(h1, s1, v1);
    a1=c1.alpha();

    Color& c2=_colors[imax];
    c2.hsv(h2, s2, v2);
    a2=c2.alpha();

    dh=static_cast<double>(h2-h1)/static_cast<double>(imax-imin+1);
    ds=static_cast<double>(s2-s1)/static_cast<double>(imax-imin+1);
    dv=static_cast<double>(v2-v1)/static_cast<double>(imax-imin+1);
    da=static_cast<double>(a2-a1)/static_cast<double>(imax-imin+1);

    h=h1;
    s=s1;
    v=v1;
    a=a1;
    for(int i=imin+1; i<imax; i++) {
      h+=dh;
      s+=ds;
      v+=dv;
      a+=da;
      _colors[i].setHsva(qRound(h), qRound(s), qRound(v), qRound(a));
    }
  }

  QString ColorPalette::toString() const
  {
    TRACE;
    QString s=_colors[0].name();
    for(int i=1; i<_n; i++) {
      s+=",";
      s+=_colors[i].name();
    }
    return s;
  }

  bool ColorPalette::fromString(const QString& s)
  {
    TRACE;
    QStringList l=s.split(",");
    if(l.count()>=2) {
      resize(l.count());
      for(int i=0; i<_n; i++) {
        if(!_colors[i].setNamedColor(l.at(i))) {
          return false;
        }
      }
      return true;
    } else {
      return false;
    }
  }

  ColorPalette ColorPalette::preference(const QString& name, int n, Model m)
  {
    TRACE;
    QSettings& reg=CoreApplication::instance()->settings();
    reg.beginGroup("ColorPalettes");
    ColorPalette pal;
    if(!reg.contains(name) || !pal.fromString(reg.value(name).toString())) {
      pal.generateColorScale(n, m);
    }
    return pal;
  }

  void ColorPalette::setPreference(const QString& name, const ColorPalette& pal)
  {
    TRACE;
    QSettings& reg=CoreApplication::instance()->settings();
    reg.beginGroup("ColorPalettes");
    reg.setValue(name, pal.toString());
  }

} // namespace QGpCoreMath

