/***************************************************************************
**
**  This file is part of GeopsyGui.
**
**  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: 2006-04-21
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <SciFigs.h>
#include "PickLayer.h"
#include "SignalLayer.h"
#include "PicksProperties.h"
#include "PickToPick.h"

namespace GeopsyGui {

const QString PickLayer::xmlPickLayerTag="PickLayer";

REGISTER_GRAPHCONTENTLAYER(PickLayer, "PickLayer")

PickLayer::PickLayer(SignalLayer * sig)
    : GraphContentLayer(sig->graph())
{
  TRACE;
  _sig=sig;

  initMembers();
}

PickLayer::PickLayer(AxisWindow * parent)
    : GraphContentLayer(parent)
{
  TRACE;
  _sig=nullptr;

  initMembers();
}

PickLayer::~PickLayer()
{
  TRACE;}

void PickLayer::initMembers()
{
  TRACE;
  _iSigPick=-1;
  _freePickNames=true;
}

Rect PickLayer::boundingRect() const
{
  TRACE;
  if(_sig) return _sig->boundingRect(); else return Rect();
}

void PickLayer::paintText(const LayerPainterRequest& lp, double dotpercm)
{
  if(_iSigPick>-1) {
    QMutexLocker ml(&_textPathsMutex);
    _textPaths.clear();
    const GraphContentOptions& gc=lp.options();
    double coef=dotpercm/SciFigsGlobal::screenResolution();
    QFont scaledFont=lp.graphContent()->font();
    if(coef!=1.0 && gc.printBitmap()) {
      Font::scale(scaledFont, coef);
    }
    scaledFont.setBold(true);
    QPainterPath p;
    p.addText(0, 0, scaledFont, tr("Setting \"%1\"").arg(_pickName));
    _textPaths.append(p);
  }
}

void PickLayer::paintData (const LayerPainterRequest& lp, QPainter& p, double dotpercm) const
{
  TRACE;
  if(_sig && _sig->subPool()) {
    const GraphContentOptions& gc=lp.options();
    int isigmin, isigmax;
    SAFE_UNINITIALIZED(isigmin, 0);
    SAFE_UNINITIALIZED(isigmax, 0);
    _sig->visibleIndexRange(gc, isigmin, isigmax);
    int w=lp.size().width();
    int h=lp.size().height();
    // If time picking, add a comment in the bottom right corner
    if(_iSigPick>-1) {
      QMutexLocker ml(&_textPathsMutex);
      p.setPen(Qt::red);
      QList<QPainterPath>::const_iterator itText=_textPaths.begin();
      QRectF r=itText->boundingRect();
      int baseLine=(int) round(-r.top());
      p.save();
      p.setRenderHints(QPainter::Antialiasing, true);
      p.setPen(Qt::NoPen);
      p.setBrush(Qt::red);
      p.translate(w - r.width() -10, h - r.height() - 10 + baseLine);
      p.setOpacity(0.7);
      p.fillRect( -4, (int)r.top()-4, (int)r.width()+8, (int)r.height()+8, Qt::white);
      p.fillRect(r, Qt::white);
      p.setOpacity(1);
      p.drawPath( *itText);
      p.setRenderHints(QPainter::Antialiasing, false);
      p.restore();
      _sig->highlightSignal(gc, p, w, _iSigPick, isigmin, isigmax);
    }
    double pickSize;;
    if(_sig->subPool()->count()==1) {
      pickSize=0.75*_sig->subPool()->at(0)->maximumAmplitude();
    } else {
      pickSize=0.375;
    }
    for(int iSig=isigmin; iSig<isigmax; ++iSig) {
      if(lp.terminated()) return;
      const Signal * sig=_sig->subPool()->at(iSig);
      if(!sig->isSpectrum()) {
        const DateTime& tref=_sig->timeReference(sig);
        QStringList picks=sig->metaData<TimePick>().names();
        for(QStringList::iterator it=picks.begin(); it!=picks.end(); it++) {
          QString pn=*it;
          if(_format.isVisible(pn)) {
            p.setPen(QPen(_format.color(pn), 2));
            double y0=_sig->signalY(iSig);
            double t=tref.secondsTo(sig->metaData<TimePick>().value(pn));
            if(t > gc.xVisMin() && t < gc.xVisMax()) {
              QPoint p1=gc.r2s(t, y0 - pickSize);
              QPoint p2(p1.x(), 0);
              p2.setY(gc.yr2s(y0 + pickSize));
              p.drawLine(p1, p2);
            }
          }
        }
      }
    }
    if(isMouseTracking(Pick)) {
      p.setPen(QPen( Qt::blue, 1, Qt::DotLine) );
      int m=graph() ->xAxis()->lastMousePosition();
      p.drawLine(m, 0 , m, h);
    }
    emit dataPainted(p, dotpercm, w, h);
  }
}

void PickLayer::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 Pick:
        if(setPick()) {
          mt.setRectangle(false);
          mt.setIdleCursor(QPixmap(":timepickcursor.png"), 4, 4);
        } else {
          return;
        }
        break;
      case DeletePicks:
        mt.setIdleCursor(QPixmap(":timepickcursor.png"), 4, 4);
        mt.setRectangle(true);
        mt.showRectangle();
        break;
      default:
        return;
      }
      beginMouseTracking(mt);
    }
  } else {
    if(isMouseTracking(id)) {
      switch (id) {
      case Pick:
        _iSigPick=-1;
        emit pickEnd();
        deepUpdate();
        break;
      case DeletePicks:
        break;
      default:
        break;
      }
      endMouseTracking(id);
    }
  }
}

bool PickLayer::trackRectangle(int id, double rx1, double ry1, double rx2, double ry2,
                               Qt::KeyboardModifiers)
{
  TRACE;
  LayerLocker ll(this);
  if(id==DeletePicks) {
    Rect r(rx1, ry1, rx2, ry2);
    const SubSignalPool * subPool=_sig->subPool();
    for(int iSig=subPool->count()-1; iSig>=0; --iSig) {
      Signal * sig=_sig->subPool()->at(iSig);
      if(!sig->isSpectrum()) {
        const DateTime& tref=_sig->timeReference(sig);
        double y0=_sig->signalY(iSig);
        QStringList picks=sig->metaData<TimePick>().names();
        for(QStringList::iterator it=picks.begin(); it!=picks.end(); it++) {
          QString pn=*it;
          if(_format.isVisible(pn) && !TimePick::pickLocked(pn)) {
            double t=tref.secondsTo(sig->metaData<TimePick>().value(pn));
            if(r.includes(Point2D(t, y0))) {
              App::log(1, tr("Deleting time pick '%1' for signal %2 (ID=%3)\n")
                                .arg(pn).arg(sig->nameComponent()).arg(sig->id()));
              TimePick& tp=sig->beginModifyMetaData<TimePick>();
              tp.removeValue(pn);
              sig->endModifyMetaData(tp);
              emit pickChanged(sig, pn);
            }
          }
        }
      }
    }
    deepUpdate();
  }
  return true;
}

bool PickLayer::setPick(const QString& pickName)
{
  TRACE;
  if(!_sig) return false;
  bool magnifier;
  int context;
  double time;
  if(pickName.isEmpty()) {
    // Let the user select the pick to pick
    PickToPick * d=new PickToPick(QApplication::activeWindow());
    if(!_freePickNames) {
      d->lockPickNames();
    }
    Settings::getWidget(d);
    if(d->exec()==QDialog::Rejected) {
      delete d;
      return false;
    }
    Settings::setWidget(d);
    _pickName=d->pickName();
    magnifier=d->magnifier();
    context=d->context();
    time=d->time();
    delete d;
  } else {
    _pickName=pickName;
    magnifier=false;
    context=0;
    time=0.0;
  }
  _format.setVisible(_pickName, true);
  _iSigPick=0;
  emit pickBegin(magnifier, context, time, _sig->subPool()->at(_iSigPick), _iSigPick);
  deepUpdate();
  return true;
}

bool PickLayer::mousePressEvent(QMouseEvent * e, int)
{
  TRACE;
  if(!_sig) return false;
  if(e->buttons() & Qt::LeftButton &&
     !(e->modifiers() & (Qt::ControlModifier || Qt::ShiftModifier))) {
    if(_iSigPick>-1) {
      Signal& sig=*_sig->subPool()->at(_iSigPick);
      TimePick& tp=sig.beginModifyMetaData<TimePick>();
      DateTime refTime=graph()->xAxis()->timeReference();
      tp.setValue(_pickName, refTime.shifted(graphContent()->options().xs2r(e->x())));
      sig.endModifyMetaData(tp);
      emit pickChanged(&sig, _pickName);
      deepUpdate();
      return false;
    }
  }
  return true;
}

bool PickLayer::mouseReleaseEvent (QMouseEvent *, int)
{
  TRACE;
  return _sig;
}

void PickLayer::mouseMoveEvent (const QPoint& pt, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
{
  TRACE;
  if(!_sig) return;
  if(isMouseTracking(Pick)) {
    const GraphContentOptions& gc=graphContent()->options();
    deepUpdate();
    double m=gc.xs2r(pt.x());
    if(_iSigPick>-1) {
      Signal * sig=_sig->subPool()->at(_iSigPick);
      if(buttons & Qt::LeftButton &&  modifiers & Qt::ShiftModifier) {  // Quick pick with continiously pressed mouse
        double d;
        int newISigPick=_sig->signalAt(pt.y(), d);
        if(_iSigPick!=newISigPick) { // switch to other signal
          _iSigPick=newISigPick;
          sig=_sig->subPool()->at(_iSigPick);
          emit currentSignalChanged(sig, _iSigPick);
        }
        if(d<0.2) {
          TimePick& tp=sig->beginModifyMetaData<TimePick>();
          DateTime refTime=graph()->xAxis()->timeReference();
          tp.setValue(_pickName, refTime.shifted(gc.xs2r(pt.x())));
          sig->endModifyMetaData(tp);
          sig->setHeaderModified(true);
          emit pickChanged(sig, _pickName);
        }
      }
      if(sig->isSpectrum()) {
        GeopsyCoreEngine::instance()->showMessage(sig->database(), tr("f=%1 Hz, Amplitude=%2")
                                                  .arg(m, 0, 'f', 4)
                                                  .arg(sig->DoubleSignal::amplitudeAt(m), 0, 'g', 4));
      } else {
        const DateTime& tref=graph()->xAxis()->timeReference();
        DateTime t=tref.shifted(m);
        GeopsyCoreEngine::instance()->showMessage(sig->database(), tr("t=%1 (%2 from start time), Amplitude=%3")
                                .arg(t.toString())
                                .arg(sig->startTime().secondsTo(t))
                                .arg(sig->amplitudeAt(t), 0, 'g', 4));
      }
      emit mouseMoved(m);
    } else {
      DateTime t=graph()->xAxis()->time(m);
      GeopsyCoreEngine::instance()->showMessage(_sig->subPool()->database(),
                                                tr("t=%1").arg(t.toString(DateTime::defaultUserFormat)));
    }
  }
}

bool PickLayer::keyPressEvent (QKeyEvent * e)
{
  TRACE;
  if(!_sig) return false;
  if(_iSigPick>-1) {
    switch (e->key()) {
    case Qt::Key_Up:
      if(graphContent()->options().ay() < 0) {
        _iSigPick++;
        if(_iSigPick >= (int) _sig->subPool()->count()) _iSigPick=0;
      } else {
        if(_iSigPick==0) _iSigPick=_sig->subPool()->count() - 1; else _iSigPick--;
      }
      emit currentSignalChanged(_sig->subPool()->at(_iSigPick), _iSigPick);
      deepUpdate();
      return false;
    case Qt::Key_Down:
      if(graphContent()->options().ay() > 0) {
        _iSigPick++;
        if(_iSigPick >= (int) _sig->subPool()->count()) _iSigPick=0;
      } else {
        if(_iSigPick==0) _iSigPick=_sig->subPool()->count() - 1; else _iSigPick--;
      }
      emit currentSignalChanged(_sig->subPool()->at(_iSigPick), _iSigPick);
      deepUpdate();
      return false;
    case Qt::Key_Left:
      _pickName=TimePick::previousUnlockedPick(_pickName);
      GeopsyCoreEngine::instance()->showMessage(_sig->subPool()->database(), tr("Switching to \"%1\"").arg(_pickName));
      _format.setVisible(_pickName, true);
      deepUpdate();
      return false;
    case Qt::Key_Right:
      _pickName=TimePick::nextUnlockedPick(_pickName);
      GeopsyCoreEngine::instance()->showMessage(_sig->subPool()->database(), tr("Switching to \"%1\"").arg(_pickName));
      _format.setVisible(_pickName, true);
      deepUpdate();
      return false;
    default:
      break;
    }
  }
  e->ignore();
  return true;
}

/*!
  This function eats all keyevents. This is usefull if this object is used as an event filter for another
  widget. Used in magnifier signal window (see geopsy).
*/
bool PickLayer::eventFilter(QObject *obj, QEvent *event)
{
  TRACE;
  if(event->type()==QEvent::KeyPress) {
    return keyPressEvent(static_cast<QKeyEvent *>(event));
  } else {
    return QObject::eventFilter(obj, event);
  }
}

uint PickLayer::_tab=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void PickLayer::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(!_sig) return;
  if(pp->setCurrentTab(_tab)) {
    pp->addReference(this);
  } else {
    PicksProperties * w=new PicksProperties;
    pp->addTab(_tab, tr("Picks"), w, this);
  }
}

/*!
  Clean property editor
*/
void PickLayer::removeProperties(PropertyProxy * pp)
{
  TRACE;
  if(!_sig) return;
  pp->removeTab(_tab, this);
}

void PickLayer::properties(PropertyWidget * w) const
{
  TRACE;
  if(w->id()==_tab) {
    QStringList names=TimePick::registeredNames();
    for(int i=0; i<names.count(); i++) {
      int index=3*i;
      w->setValue(index++, names.at(i));
      QString pn=names.at(i);
      w->setValue(index++, _format.isVisible(pn));
      w->setValue(index, _format.color(pn));
    }
  }
}

void PickLayer::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  if(wid==_tab) {
    // Three properties per TimePick
    int pickIndex=pid/3;
    int pickAttribute=pid-3*pickIndex;
    QStringList names=TimePick::registeredNames();
    ASSERT(pickIndex>=0 && pickIndex<names.count());
    QString pickName=names.at(pickIndex);
    switch(pickAttribute) {
    case 0:
      // The color column is not editable, hence the active column is
      // often the first one, generating a setData for the first column
      // without any modification.
      if(val.toString()!=pickName) {
        if(Message::question(MSG_ID, tr("Renaming time picks"),
                             tr("Do you want to rename time pick '%1' to '%2' for all signals?")
                             .arg(pickName).arg(val.toString()),
                             Message::yes(), Message::no())==Message::Answer0) {
          TimePick::renamePick(pickName, val.toString());
        }
      }
      break;
    case 1:
      _format.setVisible(pickName, val.toBool());
      break;
    default:
      _format.setColor(pickName, val.value<QColor>());
      break;
    }
  }
  deepUpdate();
}

void PickLayer::xml_writeChildren(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->makeUp()) {
    _format.xml_save(s, context);
  }
  GraphContentLayer::xml_writeProperties(s, context);
}

XMLMember PickLayer::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->makeUp()) {
    if(tag=="PickFormat") return XMLMember(&_format);
  }
  return GraphContentLayer::xml_member(tag, attributes, context);
}

// TODO: connect to signal layer if it exists
/*XMLMember PickLayer::xml_polish(XML_POLISH_ARGS)
{
  TRACE;

}*/

} // namespace GeopsyGui
