//# tSubImage.cc: Test program for class SubImage
//# Copyright (C) 1998,1999,2000,2001,2003
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This program 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 2 of the License, or (at your option)
//# any later version.
//#
//# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//# $Id: tSubImage.cc 20567 2009-04-09 23:12:39Z gervandiepen $

#ifndef IMAGES_IMAGEPROFILEFITTER_H
#define IMAGES_IMAGEPROFILEFITTER_H

#include <imageanalysis/ImageAnalysis/ImageTask.h>

#include <components/SpectralComponents/GaussianMultipletSpectralElement.h>
#include <imageanalysis/ImageAnalysis/ImageFit1D.h>
#include <casacore/images/Images/TempImage.h>

#include <casacore/casa/namespace.h>

namespace casa {

class ProfileFitResults;

class ImageProfileFitter : public ImageTask<casacore::Float> {
	// <summary>
	// Top level interface for one-dimensional profile fits.
	// </summary>

	// <reviewed reviewer="" date="" tests="" demos="">
	// </reviewed>

	// <prerequisite>
	// </prerequisite>

	// <etymology>
	// Fits one-dimensional profiles.
	// </etymology>

	// <synopsis>
	// Top level interface for one-dimensional profile fits.
	// </synopsis>

	// <example>
	// <srcblock>
	// ImageProfileFitter fitter(...)
	// fitter.fit()
	// </srcblock>
	// </example>

public:
	// constructor appropriate for API calls.
	// Parameters:
	// <src>image</src> - the input image in which to fit the models
	// <src>box</src> - A 2-D rectangular box in direction space in which to use pixels for the fitting, eg box=100,120,200,230
	// In cases where both box and region are specified, box, not region, is used.
	// <src>region</src> - Named region to use for fitting. "" => Don't use a named region
	// <src>regPtr</src> - Pointer to a region record. 0 => don't use a region record.
	// <src>chans</src> - Zero-based channel range on which to do the fit.
	// <src>stokes</src> - casacore::Stokes plane on which to do the fit. Only a single casacore::Stokes parameter can be
	// specified.
	// Only a maximum of one of region, regionPtr, or box/stokes/chans should be specified.
	// <src>mask</src> - Mask (as LEL) to use as a way to specify which pixels to use </src>
	// <src>axis</src> - axis along which to do the fits. If <0, use spectral axis, and if no spectral
	// axis, use zeroth axis.
	// <src>ngauss</src> number of single gaussians to fit.
	// <src>estimatesFilename</src> file containing initial estimates for single gaussians.
	// <src>spectralList</src> spectral list containing initial estimates of single gaussians. Do
	// not put a polynomial in here; set that with setPolyOrder().

	// Fit only Gaussian singlets and an optional polynomial. In this case, the
	// code guestimates initial estimates for the specified number of Gaussian
	// singlets. If you only wish to fit a polynomial, you must use this
	// constructor and you must set <src>ngauss</src> to zero. After construction,
	// you must call setPolyOrder().
	ImageProfileFitter(
		const SPCIIF image, const casacore::String& region,
		const casacore::Record *const &regionPtr, const casacore::String& box,
		const casacore::String& chans, const casacore::String& stokes, const casacore::String& mask,
		const casacore::Int axis, const casacore::uInt ngauss, casacore::Bool overwrite=false
	);

	// Fit only Gaussian singlets and an optional polynomial. In this case, the number
	// of Gaussian singlets is deduced from the specified estimates file.
	ImageProfileFitter(
		const SPCIIF image, const casacore::String& region,
		const casacore::Record *const &regionPtr, const casacore::String& box,
		const casacore::String& chans, const casacore::String& stokes, const casacore::String& mask,
		const casacore::Int axis, const casacore::String& estimatesFilename,
		casacore::Bool overwrite=false
	);

	// Fit any permitted combination of spectral components and an optional polynomial.
	// All components to be fit (except a possible polynomial) must be represented
	// with initial estimates in <src>spectralList</src>.
	ImageProfileFitter(
		const SPCIIF image, const casacore::String& region,
		const casacore::Record *const &regionPtr, const casacore::String& box,
		const casacore::String& chans, const casacore::String& stokes, const casacore::String& mask,
		const casacore::Int axis, const SpectralList& spectralList, casacore::Bool overwrite=false
	);

	// destructor
	~ImageProfileFitter();

	// Do the fit. If doDetailedResults is false, an empty casacore::Record is returned.
	casacore::Record fit(casacore::Bool doDetailedResults=true);

	// get the fit results. If fit was run with doDetailedResults=false, an empty casacore::Record is returned
	casacore::Record getResults() const;

    inline casacore::String getClass() const { return _class; };

    // set the order of a polynomial to be simultaneously fit.
    void setPolyOrder(casacore::Int p);

    // set whether to do a pixel by pixel fit.
    inline void setDoMultiFit(const casacore::Bool m) { _multiFit = m; }

    // set if results should be written to the logger
    inline void setLogResults(const casacore::Bool logResults) { _logResults = logResults; }

    // set minimum number of good points required to attempt a fit
    inline void setMinGoodPoints(const casacore::uInt mgp) {
    	ThrowIf(mgp == 0, "Number of good points has to be > 0");
    	_minGoodPoints = mgp;
    }

    // <group>
    // Solution images. Only written if _multifit is true
    // model image name
    inline void setModel(const casacore::String& model) { _model = model; }
    // residual image name
    inline void setResidual(const casacore::String& residual) { _residual = residual; }
    // gaussian amplitude image name
    inline void setAmpName(const casacore::String& s) { _ampName = s; }
    // gaussian amplitude error image name
    inline void setAmpErrName(const casacore::String& s) { _ampErrName = s; }
    // gaussian center image name
    inline void setCenterName(const casacore::String& s) { _centerName = s; }
    // gaussian center error image name
    inline void setCenterErrName(const casacore::String& s) { _centerErrName = s; }
    // gaussian fwhm image name
    inline void setFWHMName(const casacore::String& s) { _fwhmName = s; }
    // gaussian fwhm error image name
    inline void setFWHMErrName(const casacore::String& s) { _fwhmErrName = s; }
    // gaussian integral image name
    inline void setIntegralName(const casacore::String& s) { _integralName = s; }
    // gaussian integral error image name
    inline void setIntegralErrName(const casacore::String& s) { _integralErrName = s; }
    // </group>

    // set the name of the power logarithmic polynomial image.
    inline void setPLPName(const casacore::String& s) { _plpName = s; }

    // set the name of the power logarithmic polynomial image.
    inline void setPLPErrName(const casacore::String& s) { _plpErrName = s; }

    // set the name of the logarithmic transformed polynomial image.
    inline void setLTPName(const casacore::String& s) { _ltpName = s; }

    // set the name of the logarithmic transformed polynomial image.
    inline void setLTPErrName(const casacore::String& s) { _ltpErrName = s; }

    // set the range over which PFC amplitude solutions are valid
    void setGoodAmpRange(const casacore::Double min, const casacore::Double max);

    // set the range over which PFC center solutions are valid
    void setGoodCenterRange(const casacore::Double min, const casacore::Double max);

    // set the range over which PFC FWHM solutions are valid
    void setGoodFWHMRange(const casacore::Double min, const casacore::Double max);

    // <group>
    // set standard deviation image
    void setSigma(const casacore::Array<casacore::Float>& sigma);

    void setSigma(const casacore::ImageInterface<casacore::Float> *const &sigma);

    inline void setOutputSigmaImage(const casacore::String& s) { _sigmaName = s; }
    // </group>

    const casacore::Array<std::shared_ptr<ProfileFitResults> >& getFitters() const;

    //Converts a pixel value into a world value either in velocity, wavelength, or
    //frequency units.  If the tabular index >= 0, it uses the tabular index for conversion
    //with the specified casacore::MFrequency type, otherwise, it uses the spectral axis for
    //conversion.
    casacore::Double getWorldValue(
    	casacore::Double pixelVal, const casacore::IPosition& imPos, const casacore::String& units,
        casacore::Bool velocity, casacore::Bool wavelength, casacore::Int tabularIndex=-1,
        casacore::MFrequency::Types type=casacore::MFrequency::DEFAULT
    ) const;

    void setAbscissaDivisor(casacore::Double d);

    void setAbscissaDivisor(const casacore::Quantity& q);

    // for backward compatibility with ia.continuumsub. If no residual
    // image has been provided, a casacore::TempImage is created.
    void createResidualImage(casacore::Bool b) { _createResid = b; }

    SPIIF getResidual() const {
    	return _residImage;
    }

    // set the planes along the fit axis that are considered good for performing
    // the fits. An empty vector implies that all planes are considered "good".
    void setGoodPlanes(const std::set<casacore::uInt>& planes) { _goodPlanes = planes; }

protected:

    inline CasacRegionManager::StokesControl _getStokesControl() const {
   		return CasacRegionManager::USE_FIRST_STOKES;
   	}

    inline std::vector<casacore::Coordinate::Type> _getNecessaryCoordinates() const {
    	return std::vector<casacore::Coordinate::Type>(0);
    }

    casacore::Bool _hasLogfileSupport() const { return true; }

    inline casacore::Bool _supportsMultipleRegions() const {return true;}

private:
	casacore::String _residual, _model, _xUnit,
		_centerName, _centerErrName, _fwhmName,
		_fwhmErrName, _ampName, _ampErrName,
		_integralName, _integralErrName, _plpName, _plpErrName,
		_ltpName, _ltpErrName, _sigmaName, _abscissaDivisorForDisplay;
	casacore::Bool  _multiFit, _logResults, _isSpectralIndex,
		_createResid, _overwrite, _storeFits;
	casacore::Int _polyOrder, _fitAxis;
	casacore::uInt _nGaussSinglets, _nGaussMultiplets, _nLorentzSinglets,
		_nPLPCoeffs, _nLTPCoeffs, _minGoodPoints, _nProfiles, _nAttempted,
		_nSucceeded, _nConverged, _nValid;
	casacore::Array<std::shared_ptr<ProfileFitResults> > _fitters;
    // subimage contains the region of the original image
	// on which the fit is performed.
	std::shared_ptr<const casacore::SubImage<casacore::Float> > _subImage;
	casacore::Record _results;
	SpectralList _nonPolyEstimates;
	std::unique_ptr<std::pair<casacore::Double, casacore::Double> > _goodAmpRange, _goodCenterRange, _goodFWHMRange;
	casacore::Matrix<casacore::String> _worldCoords;
	std::shared_ptr<casacore::TempImage<casacore::Float> > _sigma;
	casacore::Double _abscissaDivisor;
	SPIIF _modelImage, _residImage;
	// planes along _fitAxis to use in fits, empty => use all planes
	// originally used to support continuum subtraction
	std::set<casacore::uInt> _goodPlanes;

	const static casacore::String _class;

	mutable casacore::Bool _haveWarnedAboutGuessingGaussians = false;

    std::vector<OutputDestinationChecker::OutputStruct> _getOutputStruct();

    void _checkNGaussAndPolyOrder() const;

    void _finishConstruction();

    // moved from ImageAnalysis
    void _fitallprofiles();

    // Fit all profiles in image.  The output images must be already
    // created; if the pointer is 0, that image won't be filled.
    // The mask from the input image is transferred to the output
    // images.    If the weights image is pointer is non-zero, the
    // values from it will be used to weight the data points in the
    // fit.  You can fit some combination of gaussians and a polynomial
    // (-1 means no polynomial).  Initial estimates are not required.
    // Fits are done in image space to provide astronomer friendly results,
    // but pixel space is better for the fitter when fitting polynomials.
    // Thus, atm, callers should be aware that fitting polynomials may
    // fail even when the data lie exactly on a polynomial curve.
    // This will probably be fixed in the future by doing the fits
    // in pixel space here and requiring the caller to deal with converting
    // to something astronomer friendly if it so desires.

    void _fitProfiles(const casacore::Bool showProgress);

    //casacore::Bool _inVelocitySpace() const;

    void _flagFitterIfNecessary(ImageFit1D<casacore::Float>& fitter) const;

    casacore::Bool _isPCFSolutionOK(const PCFSpectralElement *const &pcf) const;

    void _loopOverFits(
    	SPCIIF fitData, casacore::Bool showProgress,
    	std::shared_ptr<casacore::ProgressMeter> progressMeter, casacore::Bool checkMinPts,
    	const casacore::Array<casacore::Bool>& fitMask, ImageFit1D<casacore::Float>::AbcissaType abcissaType,
    	const casacore::IPosition& fitterShape, const casacore::IPosition& sliceShape,
    	const std::set<casacore::uInt> goodPlanes
    );

    void _setAbscissaDivisorIfNecessary(const casacore::Vector<casacore::Double>& abscissaValues);

    casacore::Bool _setFitterElements(
    	ImageFit1D<casacore::Float>& fitter, SpectralList& newEstimates,
    	const std::unique_ptr<const PolynomialSpectralElement>& polyEl,
    	const std::vector<casacore::IPosition>& goodPos,
    	const casacore::IPosition& fitterShape, const casacore::IPosition& curPos,
    	casacore::uInt nOrigComps
    ) const;

    void _updateModelAndResidual(
    	casacore::Bool fitOK,	const ImageFit1D<casacore::Float>& fitter,
    	const casacore::IPosition& sliceShape, const casacore::IPosition& curPos,
    	casacore::Lattice<casacore::Bool>* const &pFitMask, casacore::Lattice<casacore::Bool>* const &pResidMask
    ) const;

    // only implemented for the simplest cases so far
    casacore::uInt _nUnknowns() const;

};
}

#endif