/***************************************************************************
**
**  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: 2009-06-09
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <SciFigs.h>

#include "ChronogramLayer.h"
#include "ChronogramProperties.h"
#include "GeopsyGuiEngine.h"

namespace GeopsyGui {

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

  Full description of class still missing
*/

const QString ChronogramLayer::xmlChronogramLayerTag="ChronogramLayer";
uint ChronogramLayer::_tab=PropertyProxy::uniqueId();

REGISTER_GRAPHCONTENTLAYER(ChronogramLayer, "ChronogramLayer")

/*!
  Description of constructor still missing
*/
ChronogramLayer::ChronogramLayer(AxisWindow * parent)
    : GraphContentLayer(parent)
{
  TRACE;
  // Required for viewing the layer in figue (through geopsyfigs plugin)
  if(!GeopsyGuiEngine::instance()) {
    CoreApplication::addGlobalObject(new GeopsyGuiEngine);
  }
  _subPoolOwner=false;
  _minimumGap=0.0;
  _subPool=nullptr;
}

/*!
  Description of destructor still missing
*/
ChronogramLayer::~ChronogramLayer()
{
  TRACE;
  if(_subPoolOwner) {
    delete _subPool;
  }
}

void ChronogramLayer::subPoolUpdate(SubSignalPool * subPool)
{
  TRACE;
  LayerLocker ll(this);
  if(subPool) {
    _subPool=subPool;
    _subPoolOwner=false;
  }
  qDeleteAll(_stations);
  _stations.clear();
  if(!_subPool) {
    return;
  }
  StationSignals::organizeSubPool(_subPool);
  if(_stations.addSignals(_subPool)) {
    rangeUpdate();
  } else {
    qDeleteAll(_stations);
    _stations.clear();
    _subPool=nullptr;
  }
}

void ChronogramLayer::rangeUpdate()
{
  TRACE;
  _ranges.clear();
  int nStations=_stations.count();
  int nComp=_stations.nComponents();
  for(int iStat=0; iStat<nStations; iStat++) {
    StationSignals * stat=_stations.at(iStat);
    for(int iComp=0; iComp<nComp; iComp++) {
      const SubSignalPool& subPool=stat->originals(iComp);
      StationRange ranges;
      // Computes available range (green)
      int nSigs=subPool.count();
      for (int iSig=0; iSig<nSigs; iSig++) {
        ranges._available.add(subPool.at(iSig)->timeRange(), false);
      }
      // Computes overlapping range (blue)
      for (int iSig=1; iSig<nSigs; iSig++) {
        Signal * sig0=subPool.at(iSig-1);
        Signal * sig1=subPool.at(iSig);
        ranges._overlap.add(sig0->timeRange().intersection(sig1->timeRange()));
      }
      _ranges.append(ranges);
    }
  }
}

QString ChronogramLayer::name(int index) const
{
  TRACE;
  int nComp=_stations.nComponents();
  int iStat=index/nComp;
  int iComp=index-iStat*nComp;
  QString compString=Signal::componentLetter(_stations.component(iComp));
  return _stations.at(iStat)->name()+" "+compString;
}

Rect ChronogramLayer::boundingRect() const
{
  TRACE;
  if(_subPool) {
    DateTime minTime=DateTime::maximumTime;
    DateTime maxTime=DateTime::minimumTime;
    DateTime t;
    for(StationList::const_iterator it=_stations.begin();it!=_stations.end();it++) {
      StationSignals * stat=*it;
      t=stat->minTime();
      if(t<minTime) minTime=t;
      t=stat->maxTime();
      if(t>maxTime) maxTime=t;
    }    
    const DateTime& tref=graph()->xAxis()->timeReference();
    Rect r(tref.secondsTo(minTime), 0.5, tref.secondsTo(maxTime), _stations.nComponents()*_stations.count()+0.5);
    return r;
  } else {
    return Rect();
  }
}

/*!
  Export gaps from the visible window to \a fileName
*/
void ChronogramLayer::exportGaps(const QString& fileName)
{
  TRACE;
  QFile f(fileName);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr("Export gaps"), tr("Cannot write to file '%1'.").arg(fileName));
    return;
  }
  QTextStream s(&f);
  const GraphContentOptions& gc=graphContent()->options();
  const DateTime& tref=graph()->xAxis()->timeReference();
  TimeRange gcLimits(tref.shifted(gc.xVisMin()), tref.shifted(gc.xVisMax()));
  s << tr("Gaps from %1 to %2").arg(gcLimits.start().toString())
                               .arg(gcLimits.end().toString()) << endl;
  s << tr("All gaps smaller than %1 seconds are ignored").arg(_minimumGap) << endl;
  int nStations=_stations.count();
  int nComp=_stations.nComponents();
  for(int iStat=0;iStat<nStations;iStat++) {
    StationSignals * stat=_stations.at(iStat);
    s << tr("Station %1").arg(stat->name()) << endl;
    for(int iComp=0;iComp<nComp;iComp++) {
      const SubSignalPool& subPool=stat->originals(iComp);
      if(!subPool.isEmpty()) {
        s << tr("  Component %1").arg(subPool.first()->componentUserName()) << endl;
      }
      // Compute available range (green)
      SparseTimeRange compRange;
      int nSigs=subPool.count();
      for(int iSig=0;iSig<nSigs;iSig++) {
        compRange.add(subPool.at(iSig)->timeRange().intersection(gcLimits), false);
      }
      if(_minimumGap>0.0) {
        compRange.removeGaps(_minimumGap);
      }
      // Compute non available range (red)
      compRange=compRange.invert(gcLimits);
      const QVector<TimeRange> rs=compRange.ranges();
      int n=rs.count();
      for(int i=0;i<n;i++) {
        const TimeRange& r=rs.at(i);
        s << tr("    %1 from %2 to %3")
             .arg(Number::secondsToDuration(r.lengthSeconds()))
             .arg(r.start().toString())
             .arg(r.end().toString()) << endl;
      }
    }
  }

}

void ChronogramLayer::paintData(const LayerPainterRequest& lp, QPainter& p, double) const
{
  TRACE;
  const GraphContentOptions& gc = lp.options();
  const DateTime& tref=graph()->xAxis()->timeReference();
  TimeRange gcLimits(tref.shifted(gc.xVisMin()), tref.shifted(gc.xVisMax()));
  int n=_ranges.count();
  for(int i=0; i<n; i++) {
    const StationRange& r=_ranges.at(i);
    // Draws unselected available range (light green)
    SparseTimeRange t=r._available.intersection(gcLimits);
    t.remove(r._selected);
    t.removeGaps(_minimumGap);
    drawRanges(gc, p, t, i+1, -0.45, -0.15, QColor(200, 255, 193, 128), QColor(152, 238, 77));
    // Draws selected available range (dark green)
    t=r._selected.intersection(gcLimits);
    t.removeGaps(_minimumGap);
    drawRanges(gc, p, t, i+1, -0.45, -0.15, QColor(103, 229, 87, 128), QColor(152, 238, 77));
    // Draws non available range (red)
    t=r._available.invert(gcLimits);
    t.removeBlocks(_minimumGap);
    drawRanges(gc, p, t, i+1, -0.15, 0.15, QColor(254, 156, 158, 128), QColor(255, 75, 78));
    // Draws overlapping range (blue)
    t=r._overlap.intersection(gcLimits);
    t.removeBlocks(_minimumGap);
    drawRanges(gc, p, t, i+1, 0.15, 0.45, QColor(158, 156, 254, 128), QColor(78, 75, 255));
  }
}

void ChronogramLayer::drawRanges(const GraphContentOptions& gc, QPainter& p,
                                 const SparseTimeRange& r,
                                 double yBase, double minY, double maxY,
                                 const QColor& fill, const QColor& border) const
{
  TRACE;
  const DateTime& tref=graph()->xAxis()->timeReference();
  p.save();
  int wtop=gc.yr2s(yBase+minY);
  int wheight=gc.yr2s(yBase+maxY)-wtop;
  int *wleft, *wwidth;
  const QVector<TimeRange> rs=r.ranges();
  int n=rs.count();
  wleft=new int[n];
  wwidth=new int[n];
  p.setPen(QPen(Qt::NoPen));
  p.setBrush(fill);
  for(int i=0; i<n; i++) {
    const TimeRange& tr=rs.at(i);
    wleft[i]=gc.xr2s(tref.secondsTo(tr.start()));
    wwidth[i]=gc.xr2s(tref.secondsTo(tr.end()))-wleft[i];
    p.drawRect(wleft[i], wtop, wwidth[i], wheight);
  }
  p.setPen(QPen(border, 1, Qt::SolidLine));
  p.setBrush(QBrush(Qt::NoBrush));
  for(int i=0; i<n; i++) {
    p.drawRect(wleft[ i ], wtop, wwidth[ i ], wheight);
  }
  delete [] wleft;
  delete [] wwidth;
  p.restore();
}

void ChronogramLayer::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    XMLSubSignalPool subPool(*_subPool);
    subPool.xml_save(s, context);
  }
  GraphContentLayer::xml_writeChildren(s, context);
}

XMLMember ChronogramLayer::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    if(tag==XMLSubSignalPool::xmlSubSignalPoolTag) {
      return XMLMember(new XMLSubSignalPool, true);
    }
  }
  return GraphContentLayer::xml_member(tag, attributes, context);
}

void ChronogramLayer::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  if(child->xml_tagName()==XMLSubSignalPool::xmlSubSignalPoolTag) {
    XMLSubSignalPool * subPool=static_cast<XMLSubSignalPool *>(child);
    if(_subPoolOwner) delete _subPool;
    _subPool=subPool->subPool();
    _subPoolOwner=true;
    subPoolUpdate();
  }
}

/*!
  Setup property editor
*/
void ChronogramLayer::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tab)) {
    ChronogramProperties * w=static_cast<ChronogramProperties *>(pp->currentTabWidget());
    w->setLayer(this);
    pp->addReference(this);
  } else {
    ChronogramProperties * w=new ChronogramProperties;
    w->setLayer(this);
    pp->addTab(_tab, tr("Chronogram"), w, this);
  }
}

/*!
  Clean property editor
*/
void ChronogramLayer::removeProperties(PropertyProxy * pp)
{
  pp->removeTab(_tab, this);
}

void ChronogramLayer::properties(PropertyWidget * w) const
{
  TRACE;
  if(w->id()==_tab) {
    w->setValue(ChronogramProperties::MinimumGap, _minimumGap);
  }
}

void ChronogramLayer::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  LayerLocker ll(this);
  if(wid==_tab) {
    switch(pid) {
    case ChronogramProperties::MinimumGap:
      _minimumGap=val.toDouble();
      deepUpdate();
      break;
    default:
      break;
    }
  }
  graphContent()->deepUpdate();
}

bool ChronogramLayer::trackRectangle(int id, double rx1, double ry1, double rx2, double ry2, Qt::KeyboardModifiers m)
{
  TRACE;
  if(id!=CHRONOGRAMLAYER_TRACKING_ID) {
    return false;
  }
  if(!(m & Qt::SHIFT)) {
    for(int i=_ranges.count()-1; i>=0; i--) {
      _ranges[i]._selected.clear();
    }
  }

  // Get min and max Y indexes
  if(ry1>ry2) {
    std::swap(ry1, ry2);
  }
  int min=qCeil(ry1+0.15)-1;
  if(min<0) {
    min=0;
  }
  int max=qFloor(ry2+0.45)-1;
  if(max>=_ranges.count()) {
    max=_ranges.count()-1;
  }
  const DateTime& tref=graph()->xAxis()->timeReference();
  TimeRange rect(tref.shifted(rx1), tref.shifted(rx2));
  for(int i=min; i<=max; i++) {
    StationRange& r=_ranges[i];
    r._selected.add(r._available.hit(rect), true);
  }
  deepUpdate();
  return true;
}

SubSignalPool ChronogramLayer::selection() const
{
  TRACE;
  SubSignalPool sel;
  int nStations=_stations.count();
  int nComp=_stations.nComponents();
  int i=0;
  for(int iStat=0; iStat<nStations; iStat++) {
    StationSignals * stat=_stations.at(iStat);
    for(int iComp=0; iComp<nComp; iComp++) {
      const StationRange& r=_ranges.at(i);
      const SubSignalPool& subPool=stat->originals(iComp);
      // Computes available range (green)
      int nSigs=subPool.count();
      for (int iSig=0; iSig<nSigs; iSig++) {
        Signal * sig=subPool.at(iSig);
        if(r._selected.intersects(sig->timeRange())) {
          sel.addSignal(sig);
        }
      }
      i++;
    }
  }
  return sel;
}

} // namespace GeopsyGui
