/***************************************************************************
**
**  This file is part of QGpGuiMath.
**
**  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: 2003-10-20
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "ImageLayer.h"
#include "ImageLayerProperties.h"

namespace QGpGuiMath {

  /*!
    \class ImageLayer ImageLayer.h
    \brief A ImageLayer is a layer to plot a scaled bitmap

  */

  const QString ImageLayer::xmlImageLayerTag="ImageLayer";

  REGISTER_GRAPHCONTENTLAYER(ImageLayer, "ImageLayer")

  ImageLayer::ImageLayer(AxisWindow * parent) :
      GraphContentLayer(parent)
  {
    TRACE;
    _scale.setX(1.0); // 1 pixel/cm
    _scale.setY(-1.0); // 1 pixel/cm, reversed
  }

  ImageLayer::~ImageLayer()
  {
    TRACE;
  }

  void ImageLayer::paintData(const LayerPainterRequest& lp, QPainter& p, double) const
  {
    TRACE;
    if(_trueImage.isNull()) {
      return;
    }
    const GraphContentOptions& gc=lp.options();
    // visRect in user units (i.e. inversed real for inversed scales)
    Rect visRect=gc.visibleRect();
    visRect=visRect.intersect(boundingRect());
    if(visRect.width()>0 && visRect.height()>0) {
      if(lp.terminated()) return;
      QRect imRect(r2i(visRect.topLeft()), r2i(visRect.bottomRight()));
      QImage tmp=_trueImage.copy(imRect.normalized());
      if(lp.terminated()) return;
      QMatrix matrix;
      matrix.scale(gc.ax()*_scale.x(), gc.ay()*_scale.y());
      if(lp.terminated()) return;
      if(!tmp.isNull()) {
        int x, y;
        SAFE_UNINITIALIZED(x, 0.0)
        SAFE_UNINITIALIZED(y, 0.0)
        // We must be back to real units (i.e. inverse for inversed scales)
        switch(gc.scaleX().type()) {
        case Scale::Linear:
        case Scale::Log:
        case Scale::AbsoluteTime:
        case Scale::RelativeTime:
          if(gc.scaleX().isReversed()) {
            x=gc.xr2s(visRect.x2());
          } else {
            x=gc.xr2s(visRect.x1());
          }
          break;
        case Scale::Inversed:
        case Scale::InversedLog:
          if(gc.scaleX().isReversed()) {
            x=gc.xr2s(1.0/visRect.x2());
          } else {
            x=gc.xr2s(1.0/visRect.x1());
          }
          break;
        }
        switch(gc.scaleY().type()) {
        case Scale::Linear:
        case Scale::Log:
        case Scale::AbsoluteTime:
        case Scale::RelativeTime:
          if(gc.scaleY().isReversed()) {
            y=gc.yr2s(visRect.y1());
          } else {
            y=gc.yr2s(visRect.y2());
          }
          break;
        case Scale::Inversed:
        case Scale::InversedLog:
          if(gc.scaleX().isReversed()) {
            y=gc.yr2s(1.0/visRect.y1());
          } else {
            y=gc.yr2s(1.0/visRect.y2());
          }
          break;
        }
        p.drawImage(x, y, tmp.transformed(matrix, Qt::SmoothTransformation));
      }
    }
  }

  Rect ImageLayer::boundingRect() const
  {
    TRACE;
    double iWidth=_trueImage.width()*_scale.x();
    double iHeight=_trueImage.height()*_scale.y();
    return Rect(_origin.x(), _origin.y(), _origin.x()+iWidth, _origin.y()+iHeight);
  }

  void ImageLayer::loadImage(QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      fileName=Message::getOpenFileName(tr("Open an existing image"), GraphicObject::pixelImageFilter);
      QFileInfo finfo(fileName);
      fileName=finfo.absoluteFilePath();
    }
    if(fileName.length() && QFileInfo(fileName).exists()) {
      setFileName(fileName);
      loadImage();
    }
  }

  void ImageLayer::loadImage()
  {
    TRACE;
    if(!_fileName.isEmpty()) {
      _trueImage.load(_fileName);
    } else {
      _trueImage=QImage();
    }
    graphContent()->deepUpdate();
  }

  /*!
    \fn QPoint ImageLayer::r2i(const Point2D& p)
    Return the point in image at real coordinates \a p
  */

  void ImageLayer::setScaling()
  {
    TRACE;
    scaling(_references, _origin, _scale);
  }

  void ImageLayer::scaling(const QList<ReferencePoint> ref, Point2D& origin, Point2D& scale)
  {
    int n=ref.count();
    if(n>1) {
      Curve<Point2D> c(n);
      double a, b;
      for(int i=0; i<n; i++) {
        const ReferencePoint& p=ref.at(i);
        c[i]=Point2D(p.image().x(), p.real().x());
      }
      c.leastSquare(a, b);
      origin.setX(b);
      scale.setX(a);
      for(int i=0; i<n; i++) {
        const ReferencePoint& p=ref.at(i);
        c[i]=Point2D(p.image().y(), p.real().y());
      }
      c.leastSquare(a, b);
      origin.setY(b);
      scale.setY(a);
    }
  }

  bool ImageLayer::mouseReleaseEvent(QMouseEvent * e, int id)
  {
    TRACE;
    switch(id) {
    case Scale:
      emit pointPicked(r2i(graphContent()->options().s2r(e->pos())));
      return true;
    default:
      return false;
    }
  }

  void ImageLayer::toggleTrackingAction(bool checked, int id)
  {
    TRACE;
    if(id==-1) {
      QAction * a=qobject_cast<QAction *>(sender());
      if(!a) return;
      id=a->data().toInt();
    }
    if(checked) {
      if(!isMouseTracking(id)) {
        LayerMouseTracking mt(this);
        mt.setId(id);
        switch (id) {
        case Scale:
          mt.setIdleCursor(QPixmap(":scalecursor.png"), 4, 4);
          break;
        case Histogram:
          mt.setIdleCursor(QPixmap(":histogramcursor.png"), 4, 4);
          mt.setRectangle(true);
          mt.showRectangle();
          break;
        default:
          return; // Do not start mouse tracking
        }
        beginMouseTracking(mt);
      }
    } else {
      if(isMouseTracking(id)) {
        endMouseTracking(id);
      }
    }
  }

  bool ImageLayer::trackRectangle(int id, double rx1, double ry1, double rx2, double ry2,
                                      Qt::KeyboardModifiers)
  {
    TRACE;
    if(id==Histogram) {
      Rect r(rx1, ry1, rx2, ry2);
      colorHistogram(r);
    }
    return true;
  }

  void ImageLayer::colorHistogram(Rect r)
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32)
      return ;
    // Extract a copy of main image inside r
    Rect imageRect=r.intersect(boundingRect());
    double imageWidth=imageRect.x2() - imageRect.x1();
    double imageHeight=imageRect.y2() - imageRect.y1();
    QImage tmp=_trueImage.copy(( int) ((imageRect.x1()-_origin.x())*_scale.x()),
                                  _trueImage.height()
                                  - (int) ((imageRect.y2()-_origin.y())*_scale.y()),
                                  (int) (imageWidth * _scale.x()),
                                  (int) (imageHeight * _scale.y()) );
    int n=1 << 24;
    int * colors=new int[n];
    int i;
    for(i=0; i<n; i++) {
      colors[i]=0;
    }
    QRgb * image=(QRgb *)tmp.bits();
    n=tmp.sizeInBytes() >> 2;
    int index;
    printf( "Number pixels: %i\n", n);
    printf( "Width: %i Height: %i\n", tmp.width(), tmp.height());
    for(i=0;i < n;i++, image++ ) {
      index=*image & 0x00FFFFFF;
      colors[ index ] ++;
    }
    // Count the number of non-null values
    n=1 << 24;
    int count=0;
    for(i=0;i < n;i++ ) {
      if(colors[ i ]!=0)
        count++;
    }
    printf( "Number different colors: %i\n", count);
    QRgb * diffColors=new QRgb[ count ];
    int * diffCount=new int[ count ];
    count=0;
    for(i=0;i < n;i++, image++ ) {
      if(colors[ i ]!=0) {
        diffColors[ count ]=i | 0xFF000000;
        diffCount[ count ]=colors[ i ];
        count++;
      }
    }
    delete [] colors;
    // Plot the histogram
    emit editedColors(diffColors, count);
    ColorHistogram * w =
      new ColorHistogram(diffColors, diffCount, count, graph() ->window());
    w->show();
  }

  QRgb * ImageLayer::image(int& n)
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32) {
      return nullptr;
    }
    n=_trueImage.sizeInBytes() >> 2;
    return (QRgb * ) _trueImage.bits();
  }

  void ImageLayer::grayFilter(int tol, int threshold)
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32)
      return ;
    QRgb * image=(QRgb * ) _trueImage.bits();
    int n=_trueImage.sizeInBytes() >> 2;
    for(int i=0;i < n;i++, image++ ) {
      QRgb& pix=*image;
      if(abs( qRed(pix) - qGreen(pix) ) < tol &&
           abs(qRed( pix) - qBlue(pix) ) < tol &&
           abs(qGreen( pix) - qBlue(pix) ) < tol &&
           qRed(pix) > threshold) {
        pix=0xFFFFFFFF;
      }
    }
    graphContent() ->deepUpdate();
  }

  void ImageLayer::lowPassFilter(int hThres, int sThres, int vThres)
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32)
      return ;
    QRgb * image=(QRgb * ) _trueImage.bits();
    int n=_trueImage.sizeInBytes() >> 2;
    int h, v, s;
    for(int i=0;i < n;i++, image++ ) {
      if( *image!=0xFFFFFFFF) {
        QColor pix( *image);
        pix.getHsv(&h, &s, &v);
        if(h >= hThres || s >= sThres || v >= vThres)
          * image=0xFFFFFFFF;
      }
    }
    graphContent() ->deepUpdate();
  }

  void ImageLayer::highPassFilter(int hThres, int sThres, int vThres)
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32)
      return ;
    QRgb * image=(QRgb * ) _trueImage.bits();
    int n=_trueImage.sizeInBytes() >> 2;
    int h, v, s;
    for(int i=0;i < n;i++, image++ ) {
      if( *image!=0xFFFFFFFF) {
        QColor pix( *image);
        pix.getHsv(&h, &s, &v);
        if(h <= hThres || s <= sThres || v <= vThres)
          * image=0xFFFFFFFF;
      }
    }
    graphContent() ->deepUpdate();
  }

  void ImageLayer::redPassFilter()
  {
    TRACE;
    if(_trueImage.isNull() || _trueImage.depth()!=32)
      return ;
    QRgb * image=(QRgb * ) _trueImage.bits();
    int n=_trueImage.sizeInBytes() >> 2;
    for(int i=0;i < n;i++, image++ ) {
      QRgb& pix=*image;
      if(qRed( pix) < qGreen(pix) ||
           qRed(pix) < qBlue(pix) )
        pix=0xFFFFFFFF;
    }
    graphContent() ->deepUpdate();
  }

  uint ImageLayer::_tabImage=PropertyProxy::uniqueId();

  /*!
    Setup property editor
  */
  void ImageLayer::addProperties(PropertyProxy * pp)
  {
    TRACE;
    if(pp->setCurrentTab(_tabImage)) {
      pp->addReference(this);
      ImageLayerProperties * w=static_cast<ImageLayerProperties *>(pp->currentTabWidget());
      w->addLayer(this);
    } else {
      ImageLayerProperties * w=new ImageLayerProperties;
      pp->addTab(_tabImage, tr("Image"), w, this);
      w->addLayer(this);
    }
  }

  /*!
    Clean property editor
  */
  void ImageLayer::removeProperties(PropertyProxy * pp)
  {
    TRACE;
    if(pp->setCurrentTab(_tabImage)) {
      ImageLayerProperties * w=static_cast<ImageLayerProperties *>(pp->currentTabWidget());
      w->removeLayer(this);
      pp->removeTab(_tabImage, this);
    }
  }

  void ImageLayer::properties(PropertyWidget * w) const
  {
    TRACE;
    w->setValue(ImageLayerProperties::XOrigin, _origin.x());
    w->setValue(ImageLayerProperties::YOrigin, _origin.y());
    w->setValue(ImageLayerProperties::XScale, _scale.x());
    w->setValue(ImageLayerProperties::YScale, _scale.y());
    w->setValue(ImageLayerProperties::ImageFile, fileName());
  }

  void ImageLayer::setProperty(uint, int pid, QVariant val)
  {
    TRACE;
    switch(pid) {
    case ImageLayerProperties::XOrigin:
      _origin.setX(val.toDouble());
      break;
    case ImageLayerProperties::YOrigin:
      _origin.setY(val.toDouble());
      break;
    case ImageLayerProperties::XScale:
      _scale.setX(val.toDouble());
      break;
    case ImageLayerProperties::YScale:
      _scale.setY(val.toDouble());
      break;
    case ImageLayerProperties::ImageFile:
      setFileName(val.toString());
      loadImage();
      break;
    }
    graphContent()->deepUpdate();
  }

  /*!
    Q PROPERTIES are part of data, unlike other types of layer
  */
  void ImageLayer::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    TRACE;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    if(scifigsContext->data()) {
      qobject_writeProperties(this, this, s, context);
    }
  }

  void ImageLayer::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
  {
    TRACE;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    if(scifigsContext->data()) {
      for(QList<ReferencePoint>::const_iterator it=_references.begin(); it!=_references.end(); it++) {
        it->xml_save(s, context);
      }
    }
  }

  /*!
    Q PROPERTIES are part of data, unlike other types of layer
  */
  XMLMember ImageLayer::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    if(scifigsContext->data()) {
      if(tag=="ReferencePoint") {
        _references.append(ReferencePoint());
        return XMLMember(&_references.last());
      } else {
        return qobject_member(this, tag, attributes, context);
      }
    }
    return XMLMember(XMLMember::Unknown);
  }

  /*!
    \class ImageLayer::ReferencePoint ImageLayer.h
    \brief Reference point to fix scale of an ImageLayer

  */

  const QString ImageLayer::ReferencePoint::xmlReferencePointTag="ReferencePoint";

  void ImageLayer::ReferencePoint::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    TRACE;
    Q_UNUSED(context)
    writeProperty(s, "name", _name);
    writeProperty(s, "image", Point2D(_image).toString(5));
    writeProperty(s, "real", _real.toString(20));
  }

  XMLMember ImageLayer::ReferencePoint::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    Q_UNUSED(context)
    Q_UNUSED(attributes)
    if(tag=="name") {
      return XMLMember(0);
    } else if(tag=="image") {
      return XMLMember(1);
    } else if(tag=="real") {
      return XMLMember(2);
    } else {
      return XMLMember(XMLMember::Unknown);
    }
  }

  bool ImageLayer::ReferencePoint::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    Q_UNUSED(context)
    Q_UNUSED(tag)
    Q_UNUSED(attributes)
    switch(memberID) {
    case 0:
      _name=content.toString();
      return true;
    case 1: {
        Point2D p;
        p.fromString(content.toString());
        _image.setX((int)round(p.x()));
        _image.setY((int)round(p.y()));
      }
      return true;
    case 2:
      _real.fromString(content.toString());
      return true;
    default:
      return false;
    }
  }

  bool ImageLayer::xml_polish(XML_POLISH_ARGS)
  {
    Q_UNUSED(context)
    loadImage();
    return true;
  }

} // namespace QGpGuiMath
