#ifndef FRINGESTOPPINGFITTER_H
#define FRINGESTOPPINGFITTER_H

#include <vector>

#include "../algorithms/surfacefitmethod.h"

#include "../../structures/samplerow.h"
#include "../../structures/timefrequencymetadata.h"

class FringeStoppingFitter final : public SurfaceFitMethod {
 public:
  FringeStoppingFitter();
  virtual ~FringeStoppingFitter();

  void SetMetaData(TimeFrequencyMetaDataCPtr metaData) {
    _metaData = metaData;
    _fieldInfo = &metaData->Field();
    _bandInfo = &metaData->Band();
    _antenna1Info = &metaData->Antenna1();
    _antenna2Info = &metaData->Antenna2();
    _observationTimes = &metaData->ObservationTimes();
  }
  virtual void Initialize(const TimeFrequencyData &input) final override {
    _originalData = &input;
    _realBackground = Image2D::CreateZeroImagePtr(_originalData->ImageWidth(),
                                                  _originalData->ImageHeight());
    _imaginaryBackground = Image2D::CreateZeroImagePtr(
        _originalData->ImageWidth(), _originalData->ImageHeight());
    _originalMask = input.GetSingleMask();
  }
  unsigned int TaskCount() const {
    return _fringeFit ? _originalData->ImageHeight()
                      : _originalData->ImageWidth();
  }
  virtual void PerformFit(unsigned taskNumber) final override;
  void PerformStaticFrequencyFitOnOneChannel(unsigned y);
  void PerformFringeStop();
  class TimeFrequencyData Background() const {
    return TimeFrequencyData(aocommon::Polarization::StokesI, _realBackground,
                             _imaginaryBackground);
  }
  void SetFringesToConsider(long double fringesToConsider) {
    _fringesToConsider = fringesToConsider;
  }
  void SetMinWindowSize(size_t minWindowSize) {
    _minWindowSize = minWindowSize;
  }
  void SetMaxWindowSize(size_t maxWindowSize) {
    _maxWindowSize = maxWindowSize;
  }
  void SetFitChannelsIndividually(bool fitChannelsIndividually) {
    _fitChannelsIndividually = fitChannelsIndividually;
  }
  void SetReturnFittedValue(bool returnFittedValue) {
    _returnFittedValue = returnFittedValue;
  }
  void SetReturnMeanValue(bool returnMeanValue) {
    _returnMeanValue = returnMeanValue;
  }
  void PerformDynamicFrequencyFit();
  void PerformDynamicFrequencyFitOnOneChannel(unsigned y);
  void PerformDynamicFrequencyFitOnOneChannel(unsigned y, unsigned windowSize);
  void PerformDynamicFrequencyFit(unsigned yStart, unsigned yEnd);
  void PerformDynamicFrequencyFit(unsigned yStart, unsigned yEnd,
                                  unsigned windowSize);
  num_t GetAmplitude(unsigned yStart, unsigned yEnd);

  numl_t NewPhaseCentreRA() const { return _newPhaseCentreRA; }
  void SetNewPhaseCentreRA(long double newPhaseCentreRA) {
    _newPhaseCentreRA = newPhaseCentreRA;
  }

  numl_t NewPhaseCentreDec() const { return _newPhaseCentreDec; }
  void SetNewPhaseCentreDec(long double newPhaseCentreDec) {
    _newPhaseCentreDec = newPhaseCentreDec;
  }

 private:
  num_t CalculateFitValue(const Image2D &image, size_t y);
  inline num_t CalculateMaskedAverage(const Image2D &image, size_t x,
                                      size_t yFrom, size_t yLength);
  inline num_t CalculateUnmaskedAverage(const Image2D &image, size_t x,
                                        size_t yFrom, size_t yLength);
  void CalculateFitValue(const Image2D &real, const Image2D &imaginary,
                         size_t x, size_t yFrom, size_t yLength, num_t &rValue,
                         num_t &iValue);
  num_t GetFringeFrequency(size_t x, size_t y);

  void GetRFIValue(num_t &r, num_t &i, int x, int y, num_t rfiPhase,
                   num_t rfiStrength);
  void GetMeanValue(num_t &rMean, num_t &iMean, num_t phase, num_t amplitude,
                    const SampleRow &real, const SampleRow &imaginary,
                    unsigned xStart, unsigned xEnd, unsigned y);
  void MinimizeRFIFitError(num_t &phase, num_t &amplitude,
                           const SampleRow &real, const SampleRow &imaginary,
                           unsigned xStart, unsigned xEnd, unsigned y) const
      throw();

  void PerformDynamicFrequencyFitOnOneRow(const SampleRow &real,
                                          const SampleRow &imaginary,
                                          unsigned y);
  void PerformDynamicFrequencyFitOnOneRow(const SampleRow &real,
                                          const SampleRow &imaginary,
                                          unsigned y, unsigned windowSize);

  Mask2DCPtr _originalMask;
  const class TimeFrequencyData *_originalData;

  Image2DPtr _realBackground, _imaginaryBackground;

  TimeFrequencyMetaDataCPtr _metaData;
  const class FieldInfo *_fieldInfo;
  const class BandInfo *_bandInfo;
  const class AntennaInfo *_antenna1Info, *_antenna2Info;
  const std::vector<double> *_observationTimes;
  num_t _fringesToConsider;
  size_t _minWindowSize, _maxWindowSize;
  bool _fitChannelsIndividually;
  bool _returnFittedValue, _returnMeanValue;
  bool _fringeFit;
  numl_t _newPhaseCentreDec, _newPhaseCentreRA;
};

#endif
