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

#include "AbstractArrayTool.h"
#include "ArrayParameters.h"
#include "ArrayStations.h"
#include "ArrayTaskManager.h"
#include "AbstractArrayResults.h"

namespace ArrayCore {

  /*!
    \class AbstractArrayTool AbstractArrayTool.h
    \brief Abstract base class for all array techniques

    Stores the list of stations and log stream.
  */

  AbstractArrayTool::AbstractArrayTool()
  {
    TRACE;
    _results=nullptr;
  }

  AbstractArrayTool::~AbstractArrayTool()
  {
    TRACE;
    delete _results;
  }

  bool AbstractArrayTool::setSubPool(SubSignalPool * subPool)
  {
    TRACE;
    if(!AbstractTool::setSubPool(subPool)) {
      return false;
    }
    App::log("*********** "+subPool->name()+" ***********\n");
    StationSignals::organizeSubPool(subPool);
    if(!_array.addSignals(subPool) ||
       !_array.hasAllComponents()) {
      return false;
    }
    _array.setReference();
    _array.setRelativePos(); // Compute relative pos without magnetic declination
                             // Relative coordinates are then ready to be used
                             // for everything except computing 3C
    if(_array.count()<2) {
      App::log(tr("Found less than 2 stations.\n"));
      return false;
    }
    // Check that UTM zone is the same for all stations
    bool ok=true;
    _array.utmZone(ok);
    if(!ok) { // can be not valid for all stations
      App::log(tr("Not the same UTM zone for all stations\n") );
      return false;
    }
    // Check nul coordinates (< 1 cm), selection is ignored here
    if(_array.radius()<0.01) {
      App::log(tr("Null coordinates\n") );
      return false;
    }
    return true;
  }

  StationSignals * AbstractArrayTool::belongsTo(Signal * sig)
  {
    TRACE;
    QList<StationSignals *>::iterator it;
    for(it=_array.begin();it!=_array.end();++it) {
      if((*it)->contains(sig)) return *it;
    }
    return nullptr;
  }

  bool AbstractArrayTool::setParameters(const AbstractParameters& param)
  {
    TRACE;
    const ArrayParameters * aParam=dynamic_cast<const ArrayParameters *>(&param);
    if(!aParam) {
      qDebug() << "Parameters are not ArrayParameters in AbstractArrayTool::setParameters.";
      return false;
    }
    // Compute the definitive coordinates with corrections for 3C processing
    _array.setRelativePos(aParam->magneticDeclination());

    if(!AbstractTool::setParameters(param)) {
      return false;
    }

    _results->setParameters(parameters());

    ArrayParameters * myParam=parameters();
    SamplingParameters& samplingParam=myParam->frequencySampling();
    if(samplingParam.isValid()) {
      return true;
    }
    if(array()->count()>0) {
      App::log(tr("Adjusting frequency range...\n") );
      // Minimum: at least 2 block sets
      // Maximum: up to Nyquist frequency
      if(samplingParam.minimum()<=0.0) {
        samplingParam.setMinimum(minimumFrequency(2, myParam->windowing().periodCount()));
      }
      if(samplingParam.maximum()<=0.0) {
        samplingParam.setMaximum(maximumFrequency());
      }
      if(samplingParam.step()<=1.0) {
        samplingParam.setStep(1.025);
      }
      if(samplingParam.isValid()) {
        return true;
      } else {
        App::log(tr("Invalid frequency sampling:\n%1\n").arg(samplingParam.toUserString()));
        return false;
      }
    } else {
      return false;
    }
  }

  /*!
    Return the minimum possible frequency to have at least \a nBlockset
    with \a periodCount periods. Gaps are taken into account.
  */
  double AbstractArrayTool::minimumFrequency(int nBlockSet, double periodCount) const
  {
    TRACE;
    // Minimum number of required block sets
    double nWin=nBlockSet*parameters()->blockAveraging().count(array()->count());
    SparseTimeRange rref=timeRange();
    // Maximum window length form the theoretical total time available (accounting for gaps)
    double maxWinLength=rref.totalLength()/nWin;
    SparseTimeRange r=rref;
    // Knowing the maximum window length, we have to exlcude all blocks smaller than this size
    // They were counted in the total time available but they cannot be used for large time windows.
    r.removeBlocks(maxWinLength*(1.0+1e-10));
    // Check that nWin still fit within the total time available
    while(nWin*maxWinLength>r.totalLength()) {
      // If not decrease time window length by 2.5%
      maxWinLength*=0.975;
      r=rref;
      r.removeBlocks(maxWinLength*(1.0+1e-10));
    }
    // nWin windows fit into the total time available
    // Deduce the frequency for which periodCount period can fit inside maxWinLength
    return periodCount/maxWinLength;
  }

  double AbstractArrayTool::maximumFrequency() const
  {
    TRACE;
    if(!array()->isEmpty()) {
      return 0.5*array()->first()->samplingFrequency();
    } else {
      return 0.0;
    }
  }

  bool AbstractArrayTool::setLoop()
  {
    TRACE;
    ASSERT(_results);
    if(!AbstractTool::setLoop()) {
      return false;
    }
    ArrayTaskManager * loop=static_cast<ArrayTaskManager *>(AbstractTool::loop());
    loop->setResults(_results);
    if(loop->setParameters(parameters())) {
      loop->setTasks();
      return true;
    } else {
      return false;
    }
  }

  void AbstractArrayTool::start()
  {
    TRACE;
    _results->clear();
    AbstractTool::start();
  }

  SparseTimeRange AbstractArrayTool::timeRange() const
  {
    TimeRange r;
    if(parameters()) {
      r=parameters()->timeLimits().absoluteRange(subPool());
    } else {
      r=TimeRange(DateTime::minimumTime, DateTime::maximumTime);
    }
    SparseTimeRange sr(r);
    int n=_array.count();
    for(int i=0; i<n; i++) {
      SparseTimeRange ssr=_array.at(i)->timeRange(r);
      if(!ssr.isNull()) {
        sr=sr.intersection(ssr);
      }
    }
    return sr;
  }

} // namespace ArrayCore
