/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  ArrayCore 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 General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2018-04-10
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "BlockAveragingParameters.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  BlockAveragingParameters::BlockAveragingParameters()
    : AbstractParameters()
  {
    TRACE;
    _overlap=false;
    _count=0;
    _countFactor=2;
    _statisticCount=50;
    _statisticMaxOverlap=0.0;
  }

  /*!
    Description of destructor still missing
  */
  BlockAveragingParameters::BlockAveragingParameters(const BlockAveragingParameters& o)
    : AbstractParameters(o)
  {
    _overlap=o._overlap;
    _count=o._count;
    _countFactor=o._countFactor;
    _statisticCount=o._statisticCount;
    _statisticMaxOverlap=o._statisticMaxOverlap;
  }

  void BlockAveragingParameters::setStatisticMaxOverlap(double n)
  {
    if(n<0.0) {
      _statisticMaxOverlap=0.0;
    } else if(n>1.0) {
      _statisticMaxOverlap=1.0;
    } else {
      _statisticMaxOverlap=n;
    }
  }

  int BlockAveragingParameters::count(int stationCount) const
  {
    TRACE;
    if(_count>0) {
      return _count;
    } else {
      return qRound(_countFactor*stationCount);
    }
  }

  /*!
    Return the block increment required to have approximately STATISTIC_COUNT process.
    There can be sometimes a little more process than STATISTIC_COUNT.
    STATISTIC_MAX_OVERLAP is also considered.
  */
  int BlockAveragingParameters::increment(const TimeRangeList& winList, const QVector<int>& blocks) const
  {
    TRACE;
    // No window or no maximum expected window count specified
    if(winList.isEmpty()) {
      return 1; // At least one to avoid possible infinite loop
    }
    // Number of time windows required for the current processing
    // In case of time window overlap, it can be larger than the actual block count
    int processCount=blocks.last()-blocks.first()+1;
    int minIncr=qCeil(processCount*(1.0-_statisticMaxOverlap));
    if(_statisticCount<=0) {
      return minIncr;
    }
    // Time span of one single process
    double processLength=winList.at(blocks.first()).start().secondsTo(winList.at(blocks.last()).end());
    // Total time available
    double totalLength=winList.timeRange().lengthSeconds();
    // totalLength-processLength is the amount of time available before the last processing
    // winList.count()/totalLength is the window density, the number of windows per unit of time
    //                          It includes the possible overlaps or gaps
    // Approximate number of maximum process that can be run
    double maxProcessCount=(totalLength-processLength)*(winList.count()/totalLength)+1;
    int winIncr=qRound(maxProcessCount/static_cast<double>(_statisticCount));
    // In case of gaps processLength may be larger than the average, maxProcessCount may be smaller
    // than the average, hence the local increment may be smaller which is the expected behavior.
    if(winIncr<2) {
      winIncr=1;
    }
    if(winIncr<minIncr) {
      winIncr=minIncr;
    }
    return winIncr;
  }


  /*!
    Return a list of blocks (indexes to time windows).
  */
  QVector<int> BlockAveragingParameters::list(int firstWindowIndex, int stationCount,
                                              const TimeRangeList& winList) const
  {
    TRACE;
    // Number of blocks must be at least the number of stations
    // to get a full rank cross-correlation matrix (Capon 1969).
    // With frequency averaging, a bit less is possible.
    int nWin=winList.count();
    int nBlocks=count(stationCount);
    TimeRange currentWindow;
    QVector<int> list(nBlocks);
    int windowIndex=firstWindowIndex;
    for(int iBlock=0; iBlock<nBlocks; iBlock++) {
      // Get the next non-overlapping block
      if(iBlock>0) {
        windowIndex++;
        if(!_overlap) {
          while(windowIndex<nWin && winList.at(windowIndex).intersects(currentWindow)) {
            windowIndex++;
          }
        }
      }
      if(windowIndex>=nWin) { // Not enough blocks available
        list.clear();
        if(firstWindowIndex==0) {
          App::log(tr("Not enough time windows available to have %1 non-overlapping blocks.\n")
                           .arg(nBlocks));
        }
        break;
      }
      currentWindow=winList.at(windowIndex);
      list[iBlock]=windowIndex;
    }
    return list;
  }

  int BlockAveragingParameters::keywordCount(PARAMETERS_KEYWORDCOUNT_ARGS) const
  {
    return 5+AbstractParameters::keywordCount();
  }

  void BlockAveragingParameters::collectKeywords(PARAMETERS_COLLECTKEYWORDS_ARGS)
  {
    TRACE;
    int baseIndex=AbstractParameters::keywordCount();
    keywords.add(prefix+"BLOCK_OVERLAP"+suffix, this, baseIndex);
    keywords.add(prefix+"BLOCK_COUNT"+suffix, this, baseIndex+1);
    keywords.add(prefix+"BLOCK_COUNT_FACTOR"+suffix, this, baseIndex+2);
    keywords.add(prefix+"STATISTIC_COUNT"+suffix, this, baseIndex+3);
    keywords.add(prefix+"STATISTIC_MAX_OVERLAP"+suffix, this, baseIndex+4);
  }

  QString BlockAveragingParameters::toString(PARAMETERS_TOSTRING_ARGS_IMPL) const
  {
    TRACE;
    QString log;
    log+="# Overlap is controled by the WINDOWS parameters, by default non overlapping blocks are selected\n";
    log+=prefix+"BLOCK_OVERLAP"+suffix+"(y/n)="+(_overlap ? "y\n" : "n\n");
    log+="# If BLOCK_COUNT is null, BLOCK_COUNT=BLOCK_COUNT_FACTOR*<number of stations>\n";
    log+=prefix+"BLOCK_COUNT"+suffix+"="+QString::number(_count)+"\n";
    log+=prefix+"BLOCK_COUNT_FACTOR"+suffix+"="+QString::number(_countFactor)+"\n";
    log+="# If STATISTIC_COUNT is not null, approx. STATISTIC_COUNT estimates par frequency\n";
    log+=prefix+"STATISTIC_COUNT"+suffix+"="+QString::number(_statisticCount)+"\n";
    log+="# If STATISTIC_MAX_OVERLAP=100%, successive statistics can be computed on overlapping block sets\n";
    log+="# If STATISTIC_MAX_OVERLAP=0%, successive statistics are computed on non-overlapping block sets\n";
    log+=prefix+"STATISTIC_MAX_OVERLAP"+suffix+"(%)="+QString::number(_statisticMaxOverlap*100.0)+"\n";
    return log;
  }

  bool BlockAveragingParameters::setValue(PARAMETERS_SETVALUE_ARGS)
  {
    TRACE;
    switch(index-AbstractParameters::keywordCount()) {
    case 0:
      _overlap=(value=="y");
      return true;
    case 1:
      _count=value.toInt();
      return true;
    case 2:
      _countFactor=value.toDouble();
      return true;
    case 3:
      _statisticCount=value.toInt();
      return true;
    case 4:
      setStatisticMaxOverlap(value.toDouble()*0.01);
      return true;
    default:
      break;
    }
    return AbstractParameters::setValue(index, value, unit, keywords);
  }

} // namespace ArrayCore

