/***************************************************************************
**
**  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: 2002-08-30
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpGuiTools.h>
#include <SciFigs.h>

#include "SignalLayer.h"
#include "SignalsProperties.h"
#include "GeopsyGuiEngine.h"

namespace GeopsyGui {

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

  Full description of class still missing
*/

const QString SignalLayer::xmlSignalLayerTag="SignalLayer";
const QString SignalLayer::signalColorTag="signalColor";
const QString SignalLayer::signalOverlapTag="signalOverlap";
const QString SignalLayer::indexTag="index";

REGISTER_GRAPHCONTENTLAYER(SignalLayer, "SignalLayer")

SignalLayer::SignalLayer (AxisWindow * parent) :
    GridPlot(parent)
{
  TRACE;
  // Required for viewing the layer in figue (through geopsyfigs plugin)
  if(!GeopsyGuiEngine::instance()) {
    CoreApplication::addGlobalObject(new GeopsyGuiEngine);
  }
  _subPool=nullptr;
  _subPoolOwner=false;
  // Trace display options
  _wiggleTrace=true;
  _variableArea=true;
  _offset=NoOffset;
  _normalize=NormalizeAll;
  _normalizeValue=1.0;
  _clip=NoClip;
  _clipPerc=50.0;
  _clipValue=1.0;
  _overlap=1.0;
  _gapHeight=0.375;
  // Grid plotting options
  _gridPlot=nullptr;
  // Y axis type options
  _yAxis=ViewerIndex;
  _timeScale=AbsoluteTime;
  // Time range options
  _timeRange=AvailableRange;
  _beforePickDelay=0.02;
  _afterPickDelay=0.02;
  // Palette options
  setLinearColorMap(-_overlap*0.5, _overlap*0.5);
}

SignalLayer::~SignalLayer ()
{
  TRACE;
  delete _gridPlot;
  if(_subPoolOwner) {
    delete _subPool;
  }
}

int SignalLayer::baseLineCount() const
{
  TRACE;
  if(_subPool->count()==1) {
    return 1;
  } else {
    switch(_yAxis) {
    case Receiver:
    case ViewerIndex:
      break;
    case SignalName: {
        QMap<QString,int> names=_subPool->signalNames();
        return names.count();
      }
    case Overlay:
      return 1;
    }
    return _subPool->count();
  }
}

void SignalLayer::subPoolUpdate (SubSignalPool * subPool)
{
  TRACE;
  LayerLocker ll(this);
  if(subPool) {
    _subPool=subPool;
    _subPoolOwner=false;
  }
  if(!_subPool) {
    return;
  }
  // Y axis type options
  _signalY.resize(_subPool->count());
  if(baseLineCount()==1) {
    for(int i=_subPool->count()-1; i>=0; i--) {
      _signalY[i]=0.0;
    }
  } else {
    switch(_yAxis) {
    case Receiver:
      _signalY=_subPool->receivers().project();
      break;
    case ViewerIndex:
      for(int i=_subPool->count()-1; i>=0; i--) {
        _signalY[i]=i+1;
      }
      break;
    case SignalName: {
        QMap<QString,int> names=_subPool->signalNames();
        for(int i=_subPool->count()-1; i>=0; i--) {
          _signalY[i]=names[_subPool->at(i)->nameComponent()]+1;
        }
      }
      break;
    case Overlay: // already processed above
      break;
    }
  }
  if(_gridPlot) updateGrid();
}

void SignalLayer::signalsUpdate ()
{
  TRACE;
  if(_gridPlot) {
    LayerLocker ll(this);
    updateGrid();
  }
  graphContent()->deepUpdate();
}

/*!
  Gives the min and max for Y to show all signals
*/
void SignalLayer::minMaxY(double& min, double& max) const
{
  TRACE;
  int n=_subPool->count();
  if(n<=0) {
    return;
  }
  if(baseLineCount()==1) {
    min=std::numeric_limits<double>::infinity();
    max=-std::numeric_limits<double>::infinity();
    double mini, maxi;
    SAFE_UNINITIALIZED(mini, 0.0)
    for(int i=0; i<n; i++) {
      Signal * sig=_subPool->at(i);
      maxi=sig->maximumAmplitude();
      switch (sig->type()) {
      case Signal::UndefinedSignalType:
      case Signal::Waveform:
        // Shift due to offset correction
        switch(_offset) {
        case NoOffset:
          mini=-maxi;
          break;
        case GlobalOffset:
        case VisibleOffset: { // Cannot handle visible range here
            double a=sig->averageAmplitude();
            mini=fabs(a)-maxi;
            maxi=-mini;
          }
          break;
        }
        break;
      case Signal::Spectrum:
        mini=0;
        break;
      case Signal::Phase:
        mini=-M_PI;
        maxi=M_PI;
        break;
      }
      if(mini<min) min=mini;
      if(maxi>max) max=maxi;
    }
    double factor=1.0/_overlap;
    min*=factor;
    max*=factor;
  } else {
    // find min and max of base line of signals
    switch(_yAxis) {
    case Receiver: {
        min=std::numeric_limits<double>::infinity(); max=-std::numeric_limits<double>::infinity();
        for(int i=0; i<n; i++) {
          if(_signalY[i]<min) {
            min=_signalY[i];
          }
          if(_signalY[i]>max) {
            max=_signalY[i];
          }
        }
        double delta=0.5*(max-min)/n;
        min-=delta;
        max+=delta;
      }
      break;
    case SignalName:
      min=n;
      max=0;
      for(int i=0; i<n; i++) {
        if(_signalY[i]<min) {
          min=_signalY[i];
        }
        if(_signalY[i]>max) {
          max=_signalY[i];
        }
      }
      min-=0.5;
      max+=0.5;
      break;
    case ViewerIndex:
      min=0.5;
      max=n+0.5;
      break;
    case Overlay: // already processed above
      break;
    }
  }
}

/*!
  Calculates the \a valClip and \a affmax for signal \a sig between samples \a itmin and \a itmax.

  \a affMax is the conversion factor from amplitude units to display units. For instance, when various signals
  are ploted in the same graph, the display unit for each signal vary from -0.5 to 0.5 at an overlap of 1.
  \a affMax is computed outside this function for all normalizations including all signals.
*/
void SignalLayer::drawingAmplitude(const Signal * sig, int itmin, int itmax, double& valClip, double& affMax) const
{
  TRACE;
  double vmax;
  SAFE_UNINITIALIZED(vmax,0);
  if(baseLineCount()==1) {
    affMax=1.0;
    switch (_normalize) {
    case NormalizeAll:
    case NormalizeOne:
    case NormalizeValue:
      switch (sig->type()) {
      case Signal::Waveform:
      case Signal::UndefinedSignalType:
      case Signal::Spectrum:
        vmax=sig->maximumAmplitude();
        break;
      case Signal::Phase:
        vmax=M_PI;
        break;
      }
      break;
    case NormalizeVisibleAll:
    case NormalizeVisibleOne: {
        switch (sig->type()) {
        case Signal::Waveform:
        case Signal::UndefinedSignalType:
        case Signal::Spectrum:
          vmax=sig->maximumAmplitude(itmin, itmax);
          break;
        case Signal::Phase:
          vmax=M_PI;
          break;
        }
      }
      break;
    }
    switch (_clip) {
    case NoClip:
    case ClipOverlap: valClip=vmax; break;
    case ClipValue: valClip=_clipValue; break;
    case ClipPercentage: valClip=vmax*_clipPerc*0.01;
    }
  } else {
    switch (sig->type()) {
    case Signal::Waveform:
      switch (_normalize) {
      case NormalizeAll:
        vmax=sig->maximumAmplitude();
        break;
      case NormalizeVisibleAll:
        vmax=sig->maximumAmplitude(itmin, itmax);
        break;
      case NormalizeOne:
        vmax=sig->maximumAmplitude();
        if(vmax==0. ) vmax=1.;
        affMax=0.5/vmax;
        break;
      case NormalizeVisibleOne:
        vmax=sig->maximumAmplitude(itmin, itmax);
        if(vmax==0. ) vmax=1.;
        affMax=0.5/vmax;
        break;
      case NormalizeValue:
        vmax=sig->maximumAmplitude();
        affMax=0.5/_normalizeValue;
        break;
      }
      break;
    case Signal::Spectrum:
      switch (_normalize) {
      case NormalizeAll:
        vmax=sig->maximumAmplitude();
        break;
      case NormalizeVisibleAll:
        vmax=sig->maximumAmplitude(itmin, itmax);
        break;
      case NormalizeOne:
        vmax=sig->maximumAmplitude();
        if(vmax==0. ) vmax=1.;
        affMax=1.0/vmax;
        break;
      case NormalizeVisibleOne:
        vmax=sig->maximumAmplitude(itmin, itmax);
        if(vmax==0. ) vmax=1.;
        affMax=1.0/vmax;
        break;
      case NormalizeValue:
        vmax=sig->maximumAmplitude();
        affMax=1.0/_normalizeValue;
        break;
      }
      break;
    case Signal::Phase:
      affMax=0.5/M_PI * _overlap;
      valClip=M_PI;
      return;
    case Signal::UndefinedSignalType:
      affMax=0.5 * _overlap;
      valClip=1.0;
      return;
    }
    affMax *= _overlap;
    QMap<const Signal *, double>::const_iterator it=_signalOverlaps.find(sig);
    if(it!=_signalOverlaps.end()) {
      affMax *= it.value();
    }
    switch (_clip) {
    case NoClip: valClip=vmax; break;
    case ClipOverlap: valClip=0.9/affMax; break;
    case ClipValue: valClip=_clipValue; break;
    case ClipPercentage: valClip=vmax*_clipPerc*0.01;
    }
  }
}

const DateTime& SignalLayer::timeReference(const Signal * sig) const
{
  TRACE;
  switch(_timeScale) {
  case AbsoluteTime:
    break;
  case RelativeToStartTime:
    return sig->startTime();
  }
  return graph()->xAxis()->timeReference();
}

/*!
  Sets itmin and itmax (sample index) for visible range for signal sig
  Returns true if all samples are visible (1 samples > 1 pixel)
  \a x0 is the time or the frequency of the first sample
  \a dx is the frequency step or the time step between samples
*/
bool SignalLayer::visibleSamples(const GraphContentOptions& gc, const Signal * sig,
                                 int& itmin, int& itmax, double& x0, double& dx) const
{
  TRACE;
  const DateTime& tref=timeReference(sig);
  switch (sig->type()) {
  case Signal::Waveform: {
      dx=sig->samplingPeriod();
      ::TimeRange tw;
      switch(_timeRange) {
      case AvailableRange:
        tw.setStart(tref.shifted(gc.xVisMin()));
        tw.setEnd(tref.shifted(gc.xVisMax()));
        break;
      case AroundPickRange: {
          DateTime t=sig->metaData<TimePick>().value(_aroundPickName);
          tw.setStart(t.shifted(-_beforePickDelay));
          tw.setEnd(t.shifted(_afterPickDelay));
        }
        break;
      case CustomRange:
        tw=_customTimeRange.absoluteRange(sig);
        break;
      }
      itmin=qFloor(sig->startTime().secondsTo(tw.start())/dx)-1;
      itmax=qCeil(sig->startTime().secondsTo(tw.end())/dx)+1;
      if(itmin<0) {
        itmin=0;
      }
      if(itmax>sig->nSamples()-1) {
        itmax=sig->nSamples()-1;
      }
      x0=tref.secondsTo(sig->startTime())+itmin*dx;
      if(fabs(gc.ax()*dx)<1.0) {
        return false;
      } else {
        return true;
      }
    }
  case Signal::Spectrum:
  case Signal::Phase: {
      int nSamples2=sig->nSamples()/2;
      dx=1.0/sig->duration();
      itmin=qFloor(gc.xVisMin()/dx)-1;
      if(itmin<1) itmin=1; /* don't plot 0 Hz as we are supposed to plot it
                              in log scale */
      itmax=qCeil(gc.xVisMax()/dx)+1;
      if(itmax>nSamples2) {
        itmax=nSamples2;
      }
      x0=itmin*dx;
      if(fabs(gc.ax()*dx)<1.0) {
        return false;
      } else {
        return true;
      }
    }
  case Signal::UndefinedSignalType:
    return false;
  }
  return false;
}

/*!
  \fn void visibleSamples(const GraphContentOptions& gc, const Signal * sig, int& itmin, int& itmax) const

  Overloaded function to simplify computation of \a itmin and \b itmax only
*/

void SignalLayer::drawSignal(const GraphContentOptions& gc, QPainter& p, const Signal * sig, int iSig,
                             int itmin, int itmax, double x0, double dx,
                             bool allSamplesVisible, double affMax, int pixelWidth,
                             bool variableArea, bool wiggleTrace) const
{
  TRACE;
  if(itmin>=itmax) return;
  // Introduction : preparing display options
  // ----------------------------------------
  double valClip;
  if((_normalize & NormalizeAll) && sig->type()==Signal::Spectrum) {
    affMax*=2;
  }
  drawingAmplitude(sig, itmin, itmax, valClip, affMax);
  if(_subPool->count()>1) {
    // Even if y axis is reversed keep positive signal up
    if(gc.ay()>0) affMax=-affMax;
  }

  // Core Routine: drawing
  // ---------------------
  double y0;
  SAFE_UNINITIALIZED(y0,0);
  // Do not remove offset for spectra
  if(sig->isSpectrum()) {
    y0=_signalY[ iSig ];
  } else {
    switch(_offset) {
    case NoOffset: y0=_signalY[ iSig ]; break;
    case GlobalOffset: y0=_signalY[iSig]-sig->averageAmplitude()*affMax;; break;
    case VisibleOffset: y0=_signalY[iSig]-sig->averageAmplitude(itmin, itmax)*affMax;; break;
    }
  }
  // For phase ploting max spectrum is required, not for other modes.
  // This is taken out of the general lock to avoid recursive locks of signal samples
  // Normalizeally ok but mix of write and read locks are not fully recursives (bug fixed in Qt 4.4.0)
  double globalMaxSpec;
  SAFE_UNINITIALIZED(globalMaxSpec,0);
  switch (sig->type()) {
  case Signal::Phase:
    globalMaxSpec=sig->maximumAmplitude();
    break;
  default:
    break;
  }
  CONST_LOCK_SAMPLES(double, samples, sig)
    // Plot a line at each sample from min to max observed in a pixel interval
    // Variable area just extend interval to 0 if min and max have the same sign
    if(!allSamplesVisible && (wiggleTrace || variableArea)) {
      double val, minVal, maxVal;
      double x=x0, x_step;
      int i=itmin;
      int ix=gc.xr2s(x0);
      int iy1, iy2, iy1n, iy2n;
      switch (sig->type()) {
      case Signal::Waveform:
        maxVal=samples[ i ];
        if(maxVal > valClip) maxVal=valClip;
        else if(maxVal < -valClip) maxVal=-valClip;
        iy1=gc.yr2s(y0 + maxVal * affMax);
        iy2=iy1;
        for( ;ix < pixelWidth && i < itmax;ix++ ) {
          x_step=gc.xs2r(ix);
          minVal=samples[ i ];
          maxVal=minVal;
          while(x < x_step && i < itmax) {
            val=samples[ i ];
            if(val < minVal) minVal=val;
            else if(val > maxVal) maxVal=val;
            i++;
            x += dx;
          }
          if(maxVal > valClip) maxVal=valClip;
          else if(maxVal < -valClip) maxVal=-valClip;
          if(minVal > valClip) minVal=valClip;
          else if(minVal < -valClip) minVal=-valClip;
          if(variableArea && minVal > 0.0) minVal=0;
          iy1n=gc.yr2s(y0 + maxVal * affMax);
          iy2n=gc.yr2s(y0 + minVal * affMax);
          if(iy2<iy1n || iy1>iy2n) {
            p.drawLine(ix-1, iy2, ix, iy1n);
          }
          iy1=iy1n;
          iy2=iy2n;
          if(iy2==iy1) {
            p.drawPoint(ix, iy1);
          } else {
            p.drawLine(ix, iy2, ix, iy1);
          }
        }
        break;
      case Signal::Spectrum:
        maxVal=sig->amplitude(samples, i);
        if(maxVal > valClip) maxVal=valClip;
        iy1=gc.yr2s(y0 + maxVal * affMax);
        iy2=iy1;
        for( ;ix < pixelWidth && i < itmax;ix++ ) {
          x_step=gc.xs2r(ix);
          minVal=sig->amplitude(samples, i);
          maxVal=minVal;
          while(x < x_step && i < itmax) {
            val=sig->amplitude(samples, i);
            if(val < minVal) minVal=val;
            else if(val > maxVal) maxVal=val;
            i++;
            x += dx;
          }
          if(maxVal > valClip) {
            maxVal=valClip;
            if(minVal > valClip) minVal=valClip;
          }
          if(variableArea && minVal > 0.0) minVal=0;
          iy1n=gc.yr2s(y0 + maxVal * affMax);
          iy2n=gc.yr2s(y0 + minVal * affMax);
          if(iy2<iy1n || iy1>iy2n) {
            p.drawLine(ix-1, iy2, ix, iy1n);
          }
          iy1=iy1n;
          iy2=iy2n;
          if(iy2==iy1) {
            p.drawPoint(ix, iy1);
          } else {
            p.drawLine(ix, iy2, ix, iy1);
          }
        }
        break;
      case Signal::Phase: {
          maxVal=sig->phase(samples, i);
          if(maxVal > valClip) maxVal=valClip;
          else if(maxVal < -valClip) maxVal=-valClip;
          iy1=gc.yr2s(y0 + maxVal * affMax);
          iy2=iy1;
          for( ;ix < pixelWidth && i < itmax;ix++ ) {
            x_step=gc.xs2r(ix);
            minVal=sig->phase(samples, i);
            maxVal=minVal;
            double maxSpec=0, valSpec;
            while(x < x_step && i < itmax) {
              val=sig->phase(samples, i);
              valSpec=sig->amplitude(samples, i);
              if(valSpec > maxSpec) maxSpec=valSpec;
              if(val < minVal) minVal=val;
              else if(val > maxVal) maxVal=val;
              i++;
              x += dx;
            }
            if(maxVal > valClip) maxVal=valClip;
            else if(maxVal < -valClip) maxVal=-valClip;
            if(minVal > valClip) minVal=valClip;
            else if(minVal < -valClip) minVal=-valClip;
            if(variableArea && minVal > 0) minVal=0;
            int specColor=qRound(255.0*(1.0-maxSpec/globalMaxSpec));
            p.setPen(QColor(specColor, specColor, specColor) );
            iy1n=gc.yr2s(y0 + maxVal * affMax);
            iy2n=gc.yr2s(y0 + minVal * affMax);
            if(iy2<iy1n || iy1>iy2n) {
              p.drawLine(ix-1, iy2, ix, iy1n);
            }
            iy1=iy1n;
            iy2=iy2n;
          }
        }
        break;
      case Signal::UndefinedSignalType:
        break;
      }
    } else {
      if(wiggleTrace) {
        switch (sig->type()) {
        case Signal::Waveform: {
            double val=samples[ itmin ];
            if(val > valClip) val=valClip;
            else if(val < -valClip) val=-valClip;
            double x=x0;
            QPoint p1=gc.r2s(x, y0 + val * affMax);
            x += dx;
            for(int i=itmin + 1; i <= itmax; i++, x += dx) {
              val=samples[ i ];
              if(val > valClip) val=valClip;
              else if(val < -valClip) val=-valClip;
              QPoint p2=gc.r2s(x, y0 + val * affMax);
              p.drawLine(p1, p2);
              p1=p2;
            }
          }
          break;
        case Signal::Spectrum: {
            double val=sig->amplitude(samples, itmin);
            if(val > valClip) val=valClip;
            double x=x0;
            QPoint p1=gc.r2s(x, y0 + val * affMax);
            x += dx;
            for(int i=itmin + 1; i <= itmax; i++, x += dx) {
              val=sig->amplitude(samples, i);
              if(val > valClip) val=valClip;
              QPoint p2=gc.r2s(x, y0 + val * affMax);
              p.drawLine(p1, p2);
              p1=p2;
            }
          }
          break;
        case Signal::Phase: {
            double val=sig->phase(samples, itmin);
            if(val > valClip) val=valClip;
            else if(val < -valClip) val=-valClip;
            double x=x0;
            QPoint p1=gc.r2s(x, y0 + val * affMax);
            x += dx;
            for(int i=itmin + 1; i <= itmax; i++, x += dx) {
              val=sig->phase(samples, i);
              if(val > valClip) val=valClip;
              else if(val < -valClip) val=-valClip;
              QPoint p2=gc.r2s(x, y0 + val * affMax);
              int specColor=qRound(255.0*(1.0-sig->amplitude(samples, i)/globalMaxSpec));
              p.setPen(QColor( specColor, specColor, specColor) );
              p.drawLine(p1, p2);
              p1=p2;
            }
          }
          break;
        case Signal::UndefinedSignalType:
          break;
        }
      }
      if(variableArea) {
        const int NPTS=500;
        QPoint pSeq[ NPTS + 5 ];
        int n;
        double val;
        switch (sig->type()) {
        case Signal::Waveform: {
            n=0;
            double x=x0;
            for(int i=itmin; i <= itmax; i++, x += dx) {
              val=samples[ i ];
              if(val > valClip) val=valClip;
              else if(val < -valClip) val=-valClip;
              if(val >= 0. ) {
                if( !n) {
                  if(i==itmin) {
                    pSeq[ n ]=gc.r2s(x, y0);
                    n++;
                  } else {
                    double inter=samples[ i ]/(samples[ i ] - samples[ i - 1 ] );
                    pSeq[ n ]=gc.r2s(x - inter * dx, y0);
                    n++;
                  }
                }
                pSeq[ n ]=gc.r2s(x, y0 + val * affMax);
                n++;
                if(n >= NPTS) {
                  pSeq[ n ]=gc.r2s(x, y0);
                  n++;
                  p.drawPolygon(pSeq, n);
                  pSeq[ 0 ]=pSeq[ n - 1 ];
                  pSeq[ 1 ]=pSeq[ n - 2 ];
                  n=2;
                }
              } else {
                if(n) {
                  double inter=samples[ i ]/(samples[ i - 1 ] - samples[ i ] );
                  pSeq[ n ]=gc.r2s(x + inter * dx, y0);
                  n++;
                  p.drawPolygon(pSeq, n);
                  n=0;
                }
              }
            }
            if(n) {
              pSeq[ n ]=gc.r2s(x - dx, y0);
              n++;
              p.drawPolygon(pSeq, n);
              n=0;
            }
          }
          break;
        case Signal::Spectrum: {
            double x=x0;
            pSeq[ 0 ]=gc.r2s(x, y0);
            n=1;
            for(int i=itmin; i <= itmax; i++, x += dx) {
              val=sig->amplitude(samples, i);
              if(val > valClip) val=valClip;
              pSeq[ n ]=gc.r2s(x, y0 + val * affMax);
              n++;
              if(n >= NPTS || pSeq[ n - 1 ].y()==pSeq[ 0 ].y()) {
                pSeq[ n ]=gc.r2s(x, y0);
                n++;
                p.drawPolygon(pSeq, n);
                pSeq[ 0 ]=pSeq[ n - 1 ];
                pSeq[ 1 ]=pSeq[ n - 2 ];
                n=2;
              }
            }
            if(n) {
              pSeq[ n ]=gc.r2s(x - dx, y0);
              n++;
              p.drawPolygon(pSeq, n);
              n=0;
            }
          }
          break;
        case Signal::Phase: {
            n=0;
            double x=x0;
            for(int i=itmin; i <= itmax; i++, x += dx) {
              int specColor=qRound(255.0*(1.0-sig->amplitude(samples, i)/globalMaxSpec));
              p.setPen(QColor( specColor, specColor, specColor) );
              val=sig->phase(samples, i);
              if(val > valClip) val=valClip;
              else if(val < -valClip) val=-valClip;
              if(val >= 0. ) {
                if( !n) {
                  if(i==itmin) {
                    pSeq[ n ]=gc.r2s(x, y0);
                    n++;
                  } else {
                    val=sig->phase(samples, i); // unclipped only
                    double inter=val/(val - sig->phase(samples, i - 1) );
                    pSeq[ n ]=gc.r2s(x - inter * dx, y0);
                    n++;
                  }
                }
                pSeq[ n ]=gc.r2s(x, y0 + val * affMax);
                n++;
                if(n >= NPTS) {
                  pSeq[ n ]=gc.r2s(x, y0);
                  n++;
                  p.drawPolygon(pSeq, n);
                  pSeq[ 0 ]=pSeq[ n - 1 ];
                  pSeq[ 1 ]=pSeq[ n - 2 ];
                  n=2;
                }
              } else {
                if(n) {
                  val=sig->phase(samples, i); // unclipped only
                  double inter=val/(sig->phase(samples, i - 1) - val);
                  pSeq[ n ]=gc.r2s(x + inter * dx, y0);
                  n++;
                  p.drawPolygon(pSeq, n);
                  n=0;
                }
              }
            }
            if(n) {
              pSeq[ n ]=gc.r2s(x - dx, y0);
              n++;
              p.drawPolygon(pSeq, n);
              n=0;
            }
          }
          break;
        case Signal::UndefinedSignalType:
          break;
        }
      }
    }
  UNLOCK_SAMPLES(sig);
}

void SignalLayer::paintData(const LayerPainterRequest& lp, QPainter& p, double) const
{
  TRACE;
  if(!_subPool) return ;
  const GraphContentOptions& gc=lp.options();
  int w=lp.size().width();
  // Useful only for NormalizeAll and NormalizeVisibleAll
  double affMax=0.5/maxAmplitude(gc);
  int isigmin, isigmax;
  SAFE_UNINITIALIZED(isigmin, 0);
  SAFE_UNINITIALIZED(isigmax, 0);
  visibleIndexRange(gc, isigmin, isigmax);
  if(_gridPlot) {
    if(smooth())
      drawGrid2DYSmooth( *_gridPlot, colorMap(), lp, p);
    else
      drawGrid2DBlock( *_gridPlot, colorMap(), lp, p);
    return ;
  }
  lp.graphContent()->setProgressMaximum (isigmax-isigmin-1);
  int itmin, itmax;
  double x0, dx;
  CacheProcess cp;
  for(int iSig=isigmin; iSig<isigmax; ++iSig) {
    const Signal * sig=_subPool->at(iSig);
    visibleSamples(gc, sig, itmin, itmax, x0, dx);
    cp.add(_subPool->at(iSig));
  }
  // Really draw signals
  for(int iSig=isigmin; iSig<isigmax; ++iSig) {
    if(lp.terminated()) {
      return;
    }
    const Signal * sig=_subPool->at(iSig);
    lp.graphContent()->setProgressValue(iSig-isigmin);
    bool allSamplesVisible=visibleSamples(gc, sig, itmin, itmax, x0, dx);
    const QColor& c=signalColor(sig);
    p.setPen(c);
    p.setBrush(c);
    drawSignal(gc, p, sig, iSig, itmin, itmax, x0, dx, allSamplesVisible, affMax, w, _variableArea, _wiggleTrace);
    // Paint gaps if any
    drawGaps(gc, p, sig, iSig);
    cp.next();
  }
}

void SignalLayer::timeWindowY(const GraphContentOptions& gc, int iSig, double rh, int& top, int& height) const
{
  if(_subPool->count()==1) {
    rh*=gc.yVisMax();
  } else {
    switch(_yAxis) {
    case Receiver:
      rh*=gc.yVisMax()/_subPool->count();
      break;
    case ViewerIndex:
    case SignalName:
      break;
    case Overlay:
      rh*=gc.yVisMax();
      break;
    }
  }
  top=gc.yr2s(_signalY[iSig]-rh);
  height=gc.yr2s(_signalY[iSig]+rh)-top;
}

void SignalLayer::drawGaps(const GraphContentOptions& gc, QPainter& p, const Signal * sig, int iSig) const
{
  const SparseTimeRange& signalRange=sig->timeRange();
  const DateTime& tref=timeReference(sig);
  ::TimeRange gcLimits(tref.shifted(gc.xVisMin()), tref.shifted(gc.xVisMax()));
  ::TimeRange sigLimits(sig->startTime(), sig->endTime());
  SparseTimeRange gaps=signalRange.invert(true); // Infinite ends to plot gaps at the start/end of signal
  gaps=gaps.intersection(gcLimits).intersection(sigLimits);
  const QVector< ::TimeRange >& g=gaps.ranges();
  int n=g.count();
  if(n==0) return;
  p.save();
  int wtop, wheight;
  timeWindowY(gc, iSig, _gapHeight, wtop, wheight);
  int * wleft=new int[static_cast<unsigned int>(n)];
  int * wwidth=new int[static_cast<unsigned int>(n)];
  p.setPen(QPen( Qt::NoPen) );
  p.setBrush(QBrush( QColor(254, 156, 158, 128) ));
  for(int i=0; i<n; i++) {
    const ::TimeRange& tr=g.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( QColor(255, 75, 78), 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();
}

Rect SignalLayer::boundingRect() const
{
  TRACE;
  Rect r;
  if(!_subPool->isEmpty()) {
    double ymin=0, ymax=1;
    minMaxY(ymin, ymax);
    int numSpectra=_subPool->spectraCount();
    if(numSpectra==_subPool->count()) {
      double fmax=_subPool->maximumFrequency();
      if(fmax>1.0) {
        r.setLimits(0.1, ymin, fmax, ymax);
      } else {
        r.setLimits(0.01*fmax, ymin, fmax, ymax);
      }
    } else if(numSpectra==0) {
      switch(_timeScale) {
      case AbsoluteTime: {
          const DateTime& tref=graph()->xAxis()->timeReference();
          ::TimeRange tr=_subPool->timeRange();
          r.setLimits(tref.secondsTo(tr.start()), ymin, tref.secondsTo(tr.end()), ymax);
        }
        break;
      case RelativeToStartTime:
        double ll=0.0;
        // Find the longest signal length
        for(SubSignalPool::const_iterator it=_subPool->begin(); it!=_subPool->end(); it++) {
          double l=(*it)->duration();
          if(l>ll) {
            ll=l;
          }
        }
        r.setLimits(0.0, ymin, ll, ymax);
        break;
      }
    }
  }
  return r;
}

void SignalLayer::setGrid(bool b)
{
  TRACE;
  if(b) {
    if(!_gridPlot) {
      _gridPlot=new Grid2D<double>(10, 10);
    }
  } else {
    delete _gridPlot;
    _gridPlot=nullptr;
  }
}

void SignalLayer::updateGrid()
{
  TRACE;
  if(!_subPool || _subPool->isEmpty()) return;
  // delete previously created grid
  delete _gridPlot;
  // Get time limits and sampling step
  double t0, deltaT;
  int n=-1, nSamples2;
  DoubleSignal::SignalType sType;
  SAFE_UNINITIALIZED(sType,DoubleSignal::UndefinedSignalType);
  SAFE_UNINITIALIZED(deltaT,0);
  SAFE_UNINITIALIZED(t0,0);
  SAFE_UNINITIALIZED(nSamples2,0);
  // Useful only for NormalizeAll and NormalizeVisibleAll
  double affMax0=0.5/maxAmplitude(graphContent()->options());
  SubSignalPool::iterator it;
  // Check if uniform t0 and dt
  for(it=_subPool->begin(); it!=_subPool->end(); ++it) {
    Signal * sig=*it;
    double it0, ideltaT;
    int in=sig->nSamples();
    DoubleSignal::SignalType isType=sig->type();
    switch (sig->type()) {
    case Signal::Waveform:
      ideltaT=sig->samplingPeriod();
      it0=timeReference(sig).secondsTo(sig->startTime());
      break;
    case Signal::Spectrum: {
        double fNyquist=0.5/sig->samplingPeriod();
        ideltaT=2.0*fNyquist/static_cast<double>(in);
        it0=0;
      }
      break;
    default:
      Message::warning(MSG_ID, "Building signal grid",
                           "Only Waveform and spectra can be used to build a colored grid",
                           Message::cancel());
      _gridPlot=nullptr;
      return;
    }
    if(n==-1) {
      sType=isType;
      t0=it0;
      n=in;
      deltaT=ideltaT;
      nSamples2=in/2;
    } else if(it0!=t0 || in!=n || ideltaT!=deltaT) {
      Message::warning(MSG_ID, "Building signal grid",
                           "To build a colored grid all the signals must have the same deltaT and T0",
                           Message::cancel());
      _gridPlot=nullptr;
      return ;
    }
  }
  // Re-allocate a new grid
  switch (sType) {
  case Signal::Waveform:
    _gridPlot=new Grid2D<double>(n, _subPool->count());
    break;
  case Signal::Spectrum:
    _gridPlot=new Grid2D<double>(nSamples2 + 1, _subPool->count());
    break;
  default:
    break;
  }
  _gridPlot->setOrigin(Point2D( t0, 1) );
  _gridPlot->setDeltaX(deltaT);
  // fill in the grid with signals
  double * cells=_gridPlot->valuePointer(0, 0);
  double valClip, affMax;
  int imin, imax;
  for(it=_subPool->begin(); it!=_subPool->end(); ++it) {
    const Signal * sig=*it;
    CONST_LOCK_SAMPLES(double, samples, sig)
      visibleSamples(graphContent()->options(), sig, imin, imax);
      switch (sig->type()) {
      case Signal::Waveform: {
          if(_normalize & NormalizeAll) affMax=affMax0;
          drawingAmplitude(sig, imin, imax, valClip, affMax);
          for(int i=0; i < n; i++, cells++ ) {
            double val=samples[ i ];
            if(val > valClip) val=valClip;
            *cells=val * affMax;
          }
        }
        break;
      case Signal::Spectrum: {
          if(_normalize & NormalizeAll) affMax=2 * affMax0;
          drawingAmplitude(sig, imin, imax, valClip, affMax);
          for(int i=0; i <= nSamples2; i++, cells++ ) {
            double val=sig->amplitude(samples, i);
            if(val > valClip) val=valClip;
            *cells=val * affMax;
          }
        }
        break;
      case Signal::Phase: {
          if(_normalize & NormalizeAll) affMax=affMax0;
          drawingAmplitude(sig, imin, imax, valClip, affMax);
          for(int i=0; i <= nSamples2; i++, cells++ ) {
            double val=sig->phase(samples, i);
            if(val > valClip) val=valClip;
            *cells=val * affMax;
          }
        }
        break;
      case Signal::UndefinedSignalType:
        break;
      }
    }
  UNLOCK_SAMPLES(sig)
}

/*!
  Return the index of signal at position Y \a yPos. Return -1 is subPool is empty.
  \a baseDistance is set to the ditance between \a yPos and the base line of returned signal.
*/
int SignalLayer::signalAt (int yPos, double& baseDistance) const
{
  TRACE;
  int n=_subPool->count();
  if(n < 1) return -1;
  double yd=graphContent()->options().ys2r(yPos);
  // Find which signal has the closest base line to yd
  int iSig=0;
  double d=fabs(_signalY[ 0 ] - yd);
  baseDistance=d;
  for(int i=1;i < n;i++ ) {
    d=fabs(_signalY[ i ] - yd);
    if(d < baseDistance) {
      baseDistance=d;
      iSig=i;
    }
  }
  return iSig;
}

/*!
  Finds the maximum amplitude across all traces of current subpool.
  Returns 0 if normalize() is not NormalizeAll or NormalizeVisibleAll
*/
double SignalLayer::maxAmplitude(const GraphContentOptions& gc) const
{
  TRACE;
  double vmax=0.0;
  int n=_subPool->count();
  SignalDatabase * db=_subPool->database();
  GeopsyCoreEngine::instance()->setProgressMaximum(db, n-1);
  switch(_normalize) {
  case NormalizeAll:
    for(int i=0;i < n;i++ ) {
      const Signal * sig=_subPool->at(i);
      double val=sig->maximumAmplitude();
      if(val>vmax) vmax=val;
      GeopsyCoreEngine::instance()->setProgressValue(db, i);
    }
    break;
  case NormalizeVisibleAll:
    int iMin, iMax;
    for(int i=0;i < n;i++ ) {
      const Signal * sig=_subPool->at(i);
      visibleSamples(gc, sig, iMin, iMax);
      double val=sig->maximumAmplitude(iMin, iMax);
      if(val>vmax) vmax=val;
      GeopsyCoreEngine::instance()->setProgressValue(db, i);
    }
    break;
  case NormalizeOne:
  case NormalizeValue:
  case NormalizeVisibleOne:
    break;
  }
  return vmax;
}

/*!
  Set the Y limits of the rectangle to include signal with index
*/
void SignalLayer::sigYBoundaries (int index, double overlap, QRect& bound) const
{
  TRACE;
  double h=0.5 * overlap;
  bound.setTop(graphContent()->options().yr2s(_signalY[ index ] + h) );
  bound.setBottom(graphContent()->options().yr2s(_signalY[ index ] - h) );
}

QString SignalLayer::offsetString() const
{
  TRACE;
  switch (offset()) {
  case GlobalOffset:
    return "GlobalOffset";
  case VisibleOffset:
    return "VisibleOffset";
  default:
    return "NoOffset";
  };
}

void SignalLayer::setOffset (QString o)
{
  TRACE;
  if(o.count()<5) return;
  switch(o[0].unicode()) {
  case 'N':
    setOffset(NoOffset);
    break;
  case 'G':
    setOffset(GlobalOffset);
    break;
  case 'V':
    setOffset(VisibleOffset);
    break;
  default:
    break;
  }
}

QString SignalLayer::normalizeString() const
{
  TRACE;
  switch (normalize()) {
  case NormalizeOne:
    return "NormalizeOne";
  case NormalizeVisibleAll:
    return "NormalizeVisisibleAll";
  case NormalizeVisibleOne:
    return "NormalizeVisisibleOne";
  case NormalizeValue:
    return "NormalizeValue";
  default:
    return "NormalizeAll";
  };
}

void SignalLayer::setNormalize(QString n)
{
  TRACE;
  if(n.count()>=11) {
    switch (n[10].unicode()) {
    case 'n':
      if(n=="NormalizeOne" ) setNormalize(NormalizeOne);
      break;
    case 'l':
      if(n=="NormalizeAll" ) setNormalize(NormalizeAll);
      // Compatibility
      else if(n=="NormAutoAll" ) setNormalize(NormalizeAll);
      break;
    case 'i':
      if(n=="NormalizeVisibleAll" ) setNormalize(NormalizeVisibleAll);
      else if(n=="NormalizeVisibleOne" ) setNormalize(NormalizeVisibleOne);
      break;
    case 'a':
      if(n=="NormalizeValue" ) setNormalize(NormalizeValue);
      break;
    case 'e': // Compatibility
      if(n=="NormAutoOne" ) setNormalize(NormalizeOne);
      break;
    case 'o': // Compatibility
      if(n=="NormVisAutoAll" ) setNormalize(NormalizeVisibleOne);
      else if(n=="NormVisAutoOne" ) setNormalize(NormalizeVisibleAll);
      break;
    default:
      break;
    }
  // Compatibility
  } else if(n=="NormValue" ) setNormalize(NormalizeValue);
}

QString SignalLayer::clipString() const
{
  TRACE;
  switch (clip()) {
  case ClipOverlap:
    return "ClipOverlap";
  case ClipPercentage:
    return "ClipPercentage";
  case ClipValue:
    return "ClipValue";
  default:
    return "NoClip";
  };
}

void SignalLayer::setClip (QString c)
{
  TRACE;
  if(c.count()<5) return;
  switch(c[4].unicode()) {
  case 'i':
    setClip(NoClip);
    break;
  case 'O':
    setClip(ClipOverlap);
    break;
  case 'V':
    setClip(ClipValue);
    break;
  case 'P':
    setClip(ClipPercentage);
    break;
  default:
    break;
  }
}

QString SignalLayer::yAxisString() const
{
  TRACE;
  switch (yAxis()) {
  case ViewerIndex:
    break;
  case Receiver:
    return "ReceiverCoordinate";
  case SignalName:
    return "SignalName";
  case Overlay:
    return "Overlay";
  };
  return "ViewerIndex";
}

void SignalLayer::setYAxis(QString a)
{
  TRACE;
  if(a.isEmpty()) return;
  switch(a[0].unicode()) {
  case 'O':
    setYAxis(Overlay);
    break;
  case 'R':
    setYAxis(Receiver);
    break;
  case 'S':
    setYAxis(SignalName);
    break;
  case 'V':
    setYAxis(ViewerIndex);
    break;
  default:
    break;
  }
}

QString SignalLayer::timeRangeString() const
{
  TRACE;
  switch (_timeRange) {
  case CustomRange:
    break;
  case AroundPickRange:
    return "AroundPickRange";
  case AvailableRange:
    return "AvailableRange";
  };
  return "CustomRange";
}

void SignalLayer::setTimeRange(QString tr)
{
  TRACE;
  if(tr.count()<2) return;
  switch(tr[1].unicode()) {
  case 'v':
    setTimeRange(AvailableRange);
    break;
  case 'r':
    setTimeRange(AroundPickRange);
    break;
  case 'u':
    setTimeRange(CustomRange);
    break;
  default:
    break;
  }
}

QString SignalLayer::timeScaleString() const
{
  TRACE;
  switch (_timeScale) {
  case AbsoluteTime:
    break;
  case RelativeToStartTime:
    return "RelativeToStartTime";
  };
  return "AbsoluteTime";
}

void SignalLayer::setTimeScale(TimeScale s)
{
  TRACE;
  _timeScale=s;
}

void SignalLayer::setTimeScale(QString ts)
{
  TRACE;
  if(ts.isEmpty()) return;
  switch(ts[0].unicode()) {
  case 'A':
    setTimeScale(AbsoluteTime);
    break;
  case 'R':
    setTimeScale(RelativeToStartTime);
    break;
  default:
    break;
  }
}

void SignalLayer::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  GridPlot::xml_writeProperties(s, context);
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->makeUp()) {
    XMLSaveAttributes att;
    QString& value=att.add(indexTag);
    for(QMap<const Signal *,QColor>::const_iterator it=_signalColors.begin(); it!=_signalColors.end(); it++ ) {
      value=QString::number(_subPool->indexOf(it.key()) );
      writeProperty(s, signalColorTag, att, it.value().name());
    }
    for(QMap<const Signal *,double>::const_iterator it=_signalOverlaps.begin(); it!=_signalOverlaps.end(); it++ ) {
      value=QString::number(_subPool->indexOf(it.key()) );
      writeProperty(s, signalOverlapTag, att, it.value());
    }
    att.clear();
    _customTimeRange.xml_attributes(att, context);
    writeProperty(s, TimeRangeParameters::xmlTimeRangeParametersTag, att);
  }
}

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

XMLMember SignalLayer::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    if(tag==XMLSubSignalPool::xmlSubSignalPoolTag) {
      return XMLMember(new XMLSubSignalPool, true);
    } else if(tag==XMLSignal::xmlSignalTag) { // For compatibility
      return XMLMember(new XMLSignal, true);
    }
  }
  if(scifigsContext->makeUp()) {
    if(tag==signalColorTag) {
      return XMLMember(0);
    } else if(tag==signalOverlapTag) {
      return XMLMember(1);
    } else if(tag==TimeRangeParameters::xmlTimeRangeParametersTag) {
      return XMLMember(2);
    }
  }
  return GridPlot::xml_member(tag, attributes, context)+3;
}

bool SignalLayer::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  switch (memberID) {
  case 0: {
      XMLRestoreAttributeIterator it=attributes.find(indexTag);
      if(it!=attributes.end()) {
        int index=it.value().toInt();
        if(index >= 0 && _subPool && index < _subPool->count()) {
          setSignalColor(_subPool->at(index), QColor(content.toString()) );
        } else {
          App::log(tr("Index '%1' out of range for 'signalColor' property.\n").arg(index) );
        }
      } else {
        App::log(tr("No 'index' for 'signalColor' property.\n") );
      }
    }
    return true;
  case 1: {
      XMLRestoreAttributeIterator it=attributes.find(indexTag);
      if(it!=attributes.end()) {
        int index=it.value().toInt();
        if(index >= 0 && _subPool && index < _subPool->count()) {
          Signal * sig=_subPool->at(index);
          _signalOverlaps.insert(sig, content.toDouble());
        } else {
          App::log(tr("Index '%1' out of range for 'signalOverlap' property.\n").arg(index) );
        }
      } else {
        App::log(tr("No 'index' for 'signalOverlap' property.\n") );
      }
    }
    return true;
  case 2:
    return _customTimeRange.xml_setAttributes(attributes, context);
  default:
    return GridPlot::xml_setProperty(memberID-3, tag, attributes, content, context);
  }
}

void SignalLayer::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;
  } else if(child->xml_tagName()==XMLSignal::xmlSignalTag) { // For compatibility
    XMLSignal * sig=static_cast<XMLSignal *>(child);
    if(!_subPool) {
      _subPool=new SubSignalPool;
      _subPoolOwner=true;
    }
    _subPool->addSignal(sig->signal());
  }
}

bool SignalLayer::xml_polish(XML_POLISH_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  subPoolUpdate();
  return true;
}

void SignalLayer::highlightSignal(const GraphContentOptions& gc, QPainter& p, int w, int iSig, int isigmin, int isigmax) const
{
  TRACE;
  if(iSig<isigmin) return ;
  if(iSig>=isigmax) return ;
  Signal * sig=_subPool->at(iSig);
  p.setPen(QPen(Qt::red, 1));
  int itmin, itmax;
  double x0, dx;
  // Useful only for NormalizeAll and NormalizeVisibleAll
  double affMax=0.5/maxAmplitude(gc);
  if(sig->type()==Signal::Spectrum) {
    affMax*=2;
  }
  bool allSamplesVisible=visibleSamples(gc, sig, itmin, itmax, x0, dx);
  drawSignal(gc, p, sig, iSig, itmin, itmax, x0, dx, allSamplesVisible, affMax, w, false, true);
}

bool SignalLayer::wheelEvent (QWheelEvent * e)
{
  TRACE;
  if(e->modifiers() & Qt::ShiftModifier) {
    e->accept();
    spinOverlap(e->delta()/120, e->modifiers() & Qt::ControlModifier, e->y());
    return false; // do not process this event for other layers
  }
  return true; // Continue scanning other layers with this event
}

bool SignalLayer::keyPressEvent(QKeyEvent* e)
{
  if(e->modifiers() & Qt::ShiftModifier) {
    e->accept();
    switch (e->key()) {
    case Qt::Key_Up:
      spinOverlap(1, false, 0);
      break;
    case Qt::Key_Down:
      spinOverlap( -1, false, 0);
      break;
    }
    return false;
  }
  return true;
}

/*!
  If \a individual is true, \a y is used to identify only one signal and to adjust its overlap
*/
void SignalLayer::spinOverlap(int nSteps, bool individual, int y)
{
  if(baseLineCount()==1) {
    Axis * a=graph()->yAxis();
    double f=pow(1.2, -nSteps);
    a->setRange(a->minimum()*f, a->maximum()*f);
    _overlap*=pow(1.2, nSteps);
  } else {
    if(individual) { // Individual normalization factor adjustment
      double dBase;
      int iSig=signalAt(y, dBase);
      if(iSig<0) return;
      Signal * sig=_subPool->at(iSig);
      QMap<const Signal *, double>::iterator it=_signalOverlaps.find(sig);
      if(it==_signalOverlaps.end()) {
        it=_signalOverlaps.insert(sig, 1.0);
      }
      it.value() *= pow(1.2, nSteps);
    } else { // Global normalization factor adjustment
      _overlap*=pow(1.2, nSteps);
    }
  }
  deepUpdate();
  if(graph()->proxy()) {
    graph()->proxy()->setValues();
  }
  emit propertiesChanged();
}

/*!
  Return color for signal \a sig. The color of a signal is defined in a map.
*/
const QColor& SignalLayer::signalColor(const Signal * sig) const
{
  QMap<const Signal *, QColor>::const_iterator it=_signalColors.find(sig);
  if(it!=_signalColors.end()) {
    return it.value();
  } else {
    static const QColor black=Qt::black;
    return black;
  }
}

/*!
  Set color for signal \a sig. The color of a signal is defined in a map. If \a c is black,
  then the entry for \a sig is removed from the map.
*/
void SignalLayer::setSignalColor(const Signal * sig, const QColor& c)
{
  TRACE;
  LayerLocker ll(this);
  QMap<const Signal *, QColor>::iterator it=_signalColors.find(sig);
  if(c==Qt::black) {
    if(it!=_signalColors.end()) {
      _signalColors.remove(sig);
    }
  } else {
    if(it!=_signalColors.end()) {
      _signalColors[sig]=c;
    } else {
      _signalColors.insert(sig,c);
    }
  }
}

void SignalLayer::clearSignalColors()
{
  TRACE;
  LayerLocker ll(this);
  _signalColors.clear();
}

Legend SignalLayer::signalLegend() const
{
  TRACE;
  Legend l(_subPool->count());
  for(int i=_subPool->count()-1; i>=0; i--) {
    const Signal * sig=_subPool->at(i);
    l.setText(i, QString("%1: %2").arg(sig->id()).arg(sig->nameComponent()));
    l.setPen(i, Pen(signalColor(sig)));
  }
  return l;
}

void SignalLayer::setSignalLegend(const Legend& l)
{
  TRACE;
  ASSERT(l.count()==_subPool->count());
  for(int i=_subPool->count()-1; i>=0; i--) {
    const Signal * sig=_subPool->at(i);
    setSignalColor(sig, l.pen(i).color());
  }
}

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

/*!
  Setup property editor
*/
void SignalLayer::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tab)) {
    SignalsProperties * w=static_cast<SignalsProperties *>(pp->currentTabWidget());
    w->setCurrentLayer(this);
    pp->addReference(this);
  } else {
    SignalsProperties * w=new SignalsProperties;
    w->setCurrentLayer(this);
    pp->addTab(_tab, tr("Signals"), w, this);
  }
  GridPlot::addProperties(pp);
}

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

void SignalLayer::properties(PropertyWidget * w) const
{
  TRACE;
  if(w->id()==_tab) {
    w->setValue(SignalsProperties::ColoredSection, static_cast<bool>(_gridPlot));
    if(_subPool && !_subPool->isEmpty())
      w->setValue(SignalsProperties::IsSpectrum, _subPool->first()->isSpectrum());
    w->setValue(SignalsProperties::Wiggle, _wiggleTrace);
    w->setValue(SignalsProperties::VariableArea, _variableArea);
    w->setValue(SignalsProperties::Normalize, SignalsProperties::normalize2item(_normalize));
    w->setValue(SignalsProperties::NormalizeValue, _normalizeValue);
    w->setValue(SignalsProperties::ClipMode, SignalsProperties::clip2item(_clip));
    if(_clip==ClipPercentage)
      w->setValue(SignalsProperties::ClipValue, _clipPerc);
    else
      w->setValue(SignalsProperties::ClipValue, _clipValue);
    w->setValue(SignalsProperties::Overlap, _overlap);
    w->setValue(SignalsProperties::Offset, SignalsProperties::offset2item(_offset));
    w->setValue(SignalsProperties::YAxis, SignalsProperties::yAxis2item(_yAxis));
    w->setValue(SignalsProperties::TimeRange, SignalsProperties::timeRange2item(_timeRange));
    w->setValue(SignalsProperties::TimeScale, SignalsProperties::timeScale2item(_timeScale));
    w->setValue(SignalsProperties::AroundPickName, _aroundPickName);
    w->setValue(SignalsProperties::BeforePickDelay, _beforePickDelay);
    w->setValue(SignalsProperties::AfterPickDelay, _afterPickDelay);
    w->setValue(SignalsProperties::CustomRange, QVariant::fromValue(_customTimeRange));
  } else {
    GridPlot::properties(w);
  }
}

void SignalLayer::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  LayerLocker ll(this);
  if(wid==_tab) {
    switch(pid) {
    case SignalsProperties::ColoredSection:
      if(val.toBool()) {
        if( !_gridPlot) updateGrid();
      } else {
        delete _gridPlot;
        _gridPlot=nullptr;
      }
      break;
    case SignalsProperties::Wiggle:
      _wiggleTrace=val.toBool();
      break;
    case SignalsProperties::VariableArea:
      _variableArea=val.toBool();
      break;
    case SignalsProperties::Normalize:
      _normalize=SignalsProperties::item2normalize(val.toInt());
      break;
    case SignalsProperties::NormalizeValue:
      _normalizeValue=val.toDouble();
      break;
    case SignalsProperties::ClipMode:
      _clip=SignalsProperties::item2clip(val.toInt());
      break;
    case SignalsProperties::ClipValue:
      if(_clip==ClipPercentage) {
        _clipPerc=val.toDouble();
      } else {
        _clipValue=val.toDouble();
      }
      break;
    case SignalsProperties::Overlap:
      setOverlap(val.toDouble());
      break;
    case SignalsProperties::Offset:
      _offset=SignalsProperties::item2offset(val.toInt());
      break;
    case SignalsProperties::YAxis:
      _yAxis=SignalsProperties::item2yAxis(val.toInt());
      subPoolUpdate();
      break;
    case SignalsProperties::TimeScale:
      _timeScale=SignalsProperties::item2timeScale(val.toInt());
      break;
    case SignalsProperties::TimeRange:
      _timeRange=SignalsProperties::item2timeRange(val.toInt());
      break;
    case SignalsProperties::AroundPickName:
      _aroundPickName=val.toString();
      break;
    case SignalsProperties::BeforePickDelay:
      _beforePickDelay=val.toDouble();
      break;
    case SignalsProperties::AfterPickDelay:
      _afterPickDelay=val.toDouble();
      break;
    case SignalsProperties::CustomRange:
      _customTimeRange=val.value<TimeRangeParameters>();
      break;
    default:
      break;
    }
  } else {
    GridPlot::setProperty(wid, pid, val);
  }
  emit propertiesChanged();
  graphContent()->deepUpdate();
}

void SignalLayer::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);
      mt.setRectangle(true);
      mt.showRectangle();
      switch (id) {
      case AddGaps:
        mt.setPen(QPen(QColor(255, 75, 78), 1, Qt::SolidLine));
        mt.setBrush(QColor(254, 156, 158, 128));
        break;
      case RemoveGaps:
        mt.setPen(QPen(QColor(152, 238, 77), 1, Qt::SolidLine));
        mt.setBrush(QColor(200, 255, 193, 128));
        break;
      default:
        return;
      }
      beginMouseTracking(mt);
    }
  } else {
    if(isMouseTracking(id)) {
      endMouseTracking(id);
    }
  }
}

QList<Signal *> SignalLayer::hitSignals(double ry1, double ry2) const
{
  QList<Signal *> sigs;
  TRACE;
  for(int iSig=_subPool->count()-1; iSig>=0; --iSig) {
    Signal * sig=_subPool->at(iSig);
    if(!sig->isSpectrum()) {
      double y0=signalY(iSig);
      if(y0>ry1 && y0<ry2) {
        sigs.append(sig);
      }
    }
  }
  return sigs;
}

bool SignalLayer::trackRectangle(int id, double rx1, double ry1, double rx2, double ry2,
                                 Qt::KeyboardModifiers)
{
  TRACE;
  LayerLocker ll(this);
  QList<Signal *> sigs=hitSignals(ry1, ry2);
  switch(id) {
  case AddGaps:
    for(int i=sigs.count()-1; i>=0; --i) {
      Signal * sig=sigs.at(i);
      const DateTime& tref=timeReference(sig);
      GeopsyCore::TimeRange r(tref.shifted(rx1), tref.shifted(rx2));
      SparseTimeRange sr=sig->timeRange();
      sr.remove(r);
      GeopsyGuiEngine::instance()->beginSignalChange(sig);
      sig->setTimeRange(sr);
      sig->setHeaderModified(true);
      GeopsyGuiEngine::instance()->endSignalChange(sig);
    }
    deepUpdate();
    return true;
  case RemoveGaps:
    for(int i=sigs.count()-1; i>=0; --i) {
      Signal * sig=sigs.at(i);
      const DateTime& tref=timeReference(sig);
      GeopsyCore::TimeRange r(tref.shifted(rx1), tref.shifted(rx2));
      SparseTimeRange sr=sig->timeRange();
      sr.add(r, true);
      GeopsyGuiEngine::instance()->beginSignalChange(sig);
      sig->setTimeRange(sr); // No risk of extension of current range
      sig->setHeaderModified(true);
      GeopsyGuiEngine::instance()->endSignalChange(sig);
    }
    deepUpdate();
    return true;
  default:
    break;
  }
  return false;
}

} // namespace GeopsyGui
