//# ImageFit1D.h: Class to fit profiles to vectors from images
//# Copyright (C) 2004
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This library is free software; you can redistribute it and/or modify it
//# under the terms of the GNU Library General Public License as published by
//# the Free Software Foundation; either version 2 of the License, or (at your
//# option) any later version.
//#
//# This library 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 Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; 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: ImageFit1D.h 20229 2008-01-29 15:19:06Z gervandiepen $

#ifndef IMAGES_IMAGEFIT1D_H
#define IMAGES_IMAGEFIT1D_H

//# Includes
#include <casacore/casa/aips.h>
#include <casacore/casa/Arrays/Vector.h>
#include <casacore/scimath/Mathematics/NumericTraits.h>
#include <casacore/measures/Measures/MDirection.h>
#include <casacore/measures/Measures/MFrequency.h>
#include <casacore/measures/Measures/MDoppler.h>
#include <casacore/coordinates/Coordinates/CoordinateSystem.h>
#include <components/SpectralComponents/ProfileFit1D.h>

#include <memory>

namespace casacore{

class ImageRegion;
template<class T> class ImageInterface;
}

namespace casa {

class SpectralElement;
class SpectralList;


// <summary>
// Fit spectral components to a casacore::Vector of data from an image
// </summary>

// <use visibility=export>

// <reviewed reviewer="" date="" tests="tImageFit1D.cc">
// </reviewed>

// <prerequisite>
//   <li> <linkto class="SpectralElement">SpectralElement</linkto> 
//   <li> <linkto class="SpectralList">SpectralList</linkto> 
//   <li> <linkto class="SpectralFit">SpectralFit</linkto> 
//   <li> <linkto class="ProfileFit1D">ProfileFit1D</linkto> 
// </prerequisite>

// <synopsis> 
// Fit lists (held in class SpectralList) of SpectralElements to a casacore::Vector of data 
// from the image.  Each SpectralElement can  be one from a variety of types.
// The values of the parameters for each SpectralElement provide the initial 
// starting guesses for the fitting process.  
//
// You specify the domain in which the fit is to be done via the 
// enum AbcissaType.  The casacore::CoordinateSystem in the image is used
// to convert the pixel coordinates to the desired abcissa.  
// You can change the units of the casacore::CoordinateSystem if you want
// to fit in different units.  If you set an estimate yourself
// (function setElements or addElement) it is the callers responsibility
// that the elements are in the correct abcissa domain.  Function
// setGaussianElements will automatically make an estimate in the 
// correct domain.
//
// Also, a SpectralElement object holds a mask indicating whether 
// a parameter should be held fixed or solved for.   After the 
// fitting is done, a new SpectralList holding SpectralElements with 
// the fitted parameters is created.  
//
// For all the functions that return a status casacore::Bool, true is good. If
// false is returned, an error message can be recovered with function
// <src>errorMessage</src>,  You should not proceed if false is returned.
// 
// Exceptions will be thrown if you do not set the Image and axis 
// via the constructor or <src>setImage</src> function.
// </synopsis> 

// <example>
// <srcblock>
// casacore::PagedImage<casacore::Float> im("myimage");
// casacore::Int axis = 2;
// ImageFit1D<casacore::Float> fitter(image, axis);
// casacore::IPosition pos(in.ndim(),0);
// fitter.setData(pos, ImageFit1D<casacore::Float>::IM_NATIVE);     // Fit in native coordinate space
// fitter.setGaussianElements(3);                      // FIt 3 Gaussians
// if (fitter.fit()) {
//    cerr << fitter.getList() << endl;                // Print result
// }
// 
// </srcblock>
// </example>

// <todo asof="2004/07/10">
//   <li> Add constraints
// </todo>


template <class T> class ImageFit1D {
public:

using FitterType = typename casacore::NumericTraits<T>::PrecisionType;

    enum AbcissaType {
       PIXEL = 0,
       IM_NATIVE = 1,
       VELOCITY = 2,
       N_TYPES};


    // Constructor.  Fitting weights are assumed all unity.
    ImageFit1D(std::shared_ptr<const casacore::ImageInterface<T> > image, casacore::uInt axis=0);

    // Constructor with fitting weights image.  The data and weights images must
    // be the same shape.
    ImageFit1D(
    	std::shared_ptr<const casacore::ImageInterface<T> > image,
    	std::shared_ptr<const casacore::ImageInterface<T> > weights, casacore::uInt axis=0
    );

    // Destructor
    ~ImageFit1D();

    // Copy constructor.  Uses reference semantics.
    ImageFit1D(const ImageFit1D& other);

    // Assignment operator. Uses reference semantics.
    ImageFit1D& operator=(const ImageFit1D& other);


    // Set the data to be fit.  All non-profile axes data are averaged.
    // For the profile axis, the full spectrum is taken.  The abscissa
    // world values are computed when you call these functions unless they
    // have been set previously by a call to setAbscissa() in which case
    // the values that were passed to that method are used. Use the first
    // form of setData() in this case. The domain of the
    // abscissa values is controlled by <src>AbcissaType</src> and
    // <src>doAbs</src> (absolute coordinates).  The casacore::CoordinateSystem in
    // the image is used to convert from pixels to world values.
    // If <src>type</src>=IN_NATIVE and <src>abscissaDivisor</src> is not null,
    // the world abscissa values will be divided by the value pointed to by
    // <src>abscissaDivisor</src>. This mitigates having very large or very small
    // abscissa values when fitting. If xfunc and/or yfunc is not NULL, the x and/or
    // y values are fed to the specified function and the resultant values are what
    // are used for the x and/or y values in the fit. If xfunc is not NULL and
    // setAbscissa values has been called prior, no abscissa value transformation occurs.
    // Thus if you want to apply a function to the abscissa values, the caller should
    // pass the result of that function into setAbscissaValues.
    // <group>
    void setData (
    	const casacore::IPosition& pos, /*const ImageFit1D<T>::AbcissaType type,
        const casacore::Bool doAbs=true, const casacore::Double* const &abscissaDivisor=0,
        casacore::Array<casacore::Double> (*xfunc)(const casacore::Array<casacore::Double>&)=0, */
        casacore::Array<FitterType> (*yfunc)(const casacore::Array<FitterType>&)=0
    );

    void setData (
    	const casacore::IPosition& pos, const ImageFit1D<T>::AbcissaType type,
    	const casacore::Bool doAbs=true, const casacore::Double* const &abscissaDivisor=0,
    	casacore::Array<casacore::Double> (*xfunc)(const casacore::Array<casacore::Double>&)=0,
    	casacore::Array<FitterType> (*yfunc)(const casacore::Array<FitterType>&)=0
    );

    /*
    casacore::Bool setData (
    	const casacore::ImageRegion& region, const ImageFit1D<T>::AbcissaType type,
        casacore::Bool doAbs=true
    );
    */
    // </group>

    // Set a SpectralList of SpectralElements to fit for.    These elements
    // must be in the correct abcissa domain set in function <src>setData</src>.
    // You must have already called <src>setData</src> to call this function.
    // The SpectralElements in the list hold the
    // initial estimates.  They also contain the information about whether
    // specific parameters are to be held fixed or allowed to vary in
    // the fitting process.
    // You can recover the list of elements with function getList.
    void setElements (const SpectralList& list) {_fitter.setElements(list);};

    // Add new SpectralElement(s) to the SpectralList (can be empty)
    // of SpectralElements to be fit for.  
    // You must have already called <src>setData</src> to call this function.
    //<group>
    void addElement (const SpectralElement& el) {_fitter.addElement(el);};
    void addElements (const SpectralList& list) {_fitter.addElements(list);};
    // </group>

    // Set a SpectralList of Gaussian SpectralElements to fit for.  
    // The initial estimates for the Gaussians will be automatically determined
    // in the correct abcissa domain.
    // All of the parameters created by this function will be solved for
    // by default. You can recover the list of elements with function getList.
    // Status is returned, if false, error message can be recovered with <src>errorMessage</src>
    void setGaussianElements (casacore::uInt nGauss);

    // Clear the SpectralList of elements to be fit for
    void clearList () {_fitter.clearList();};

    // Do the fit and return convergence status.  Errors in the fitting
    // process will generate an casacore::AipsError exception and you should catch
    // these yourself.
    casacore::Bool fit ();

    // Get Chi Squared of fit
    casacore::Double getChiSquared () const {return _fitter.getChiSquared();}

    // Get number of iterations for last fit
    casacore::Double getNumberIterations () const {return _fitter.getNumberIterations();}

    // Recover the list of elements.  You can get the elements
    // as initially estimated (fit=false), or after fitting 
    // (fit=true).  In the latter case, the SpectralElements
    // hold the parameters and errors of the fit.
    const SpectralList& getList (casacore::Bool fit=true) const {return _fitter.getList(fit);};

    // Recover vectors for the estimate, fit and residual.
    // If you don't specify which element, all elements are included
    // If the Vectors are returned with zero length, it means an error
    // condition exists (e.g. asking for fit before you do one). In this
    // case an error message can be recovered with function <src>errorMessage</src>.
    //<group>
    casacore::Vector<T> getEstimate (casacore::Int which=-1) const;
    casacore::Vector<T> getFit (casacore::Int which=-1) const;
    casacore::Vector<T> getResidual (casacore::Int which=-1, casacore::Bool fit=true)  const;
    //</group>

    casacore::Bool setXMask(const std::set<casacore::uInt>& indices, casacore::Bool specifiedPixelsAreGood) {
    	return _fitter.setXMask(indices, specifiedPixelsAreGood);
    }

    // get data mask
    casacore::Vector<casacore::Bool> getDataMask () const {return _fitter.getDataMask();};

    // Get Total Mask (data and range mask)
    casacore::Vector<casacore::Bool> getTotalMask () const {return _fitter.getTotalMask();};

    // did the fit succeed? should only be called after fit().
    casacore::Bool succeeded() const;

    // did the fit converge? should only be called after fit().
    casacore::Bool converged() const;

    // Helper function.  Sets up the casacore::CoordinateSystem to reflect the choice of
    // abcissa unit and the doppler (if the axis is spectral).
    static casacore::Bool setAbcissaState (casacore::String& errMsg, ImageFit1D<T>::AbcissaType& type,
                                 casacore::CoordinateSystem& cSys, const casacore::String& xUnit,
                                 const casacore::String& doppler, casacore::uInt pixelAxis);


    // flag the solution as invalid based on external criteria.
    void invalidate();

    // is the solution valid? If false, some external logic has
    // called invalidate()
    casacore::Bool isValid() const;

    // Set the abscissa values prior to running setData. If this is done, then
    // the abscissa values will not be recomputed when setData is called.
    // This can imporove performance if, for example, you are looping over several fitters for
    // which you know the abscissa values do not change.
    void setAbscissa(const casacore::Vector<casacore::Double>& x) { _x.assign(x); }

    // make the abscissa values, <src>x</src>. If <src>type</src>=IN_NATIVE
    // and <src>abscissaDivisor is not null, then divide the native values
    // by the value pointed to by <src>abscissaDivisor</src> in making the abscissa
    // values.
    casacore::Vector<casacore::Double> makeAbscissa (
		   ImageFit1D<T>::AbcissaType type,
		   casacore::Bool doAbs, const casacore::Double* const &abscissaDivisor
   );
private:
   std::shared_ptr<const casacore::ImageInterface<T> > _image, _weights;
   casacore::uInt _axis;

// In the future I will be able to template the fitter on T. For now
// it must be Double.

   ProfileFit1D<FitterType> _fitter;
   casacore::Bool _converged, _success, _isValid;
   casacore::Vector<casacore::Double> _x, _unityWeights, _weightSlice;
   casacore::IPosition _sliceShape;
   
   // Disallow default constructor
   ImageFit1D() {}

   void check() const;
   void checkType() const;
   void _construct();
   void copy (const ImageFit1D<T>& other);

   //void setWeightsImage (const casacore::ImageInterface<T>& im);

   // reset the fitter, for example if we've done a fit and want to move
   // to the next position in the image
   void _resetFitter();


   // Set Image(s) and axis
   // <group>
   // void setImage (const casacore::ImageInterface<T>& im, const casacore::ImageInterface<T>& weights, casacore::uInt pixelAxis);
   // void setImage (const casacore::ImageInterface<T>& im, casacore::uInt pixelAxis);
   // </group>

};

} //#End casa namespace

#ifndef CASACORE_NO_AUTO_TEMPLATES
#include <imageanalysis/ImageAnalysis/ImageFit1D.tcc>
#endif //# CASACORE_NO_AUTO_TEMPLATES
#endif