#ifndef IMAGEANALYSIS_PIXELVALUEMANIPULATOR_H
#define IMAGEANALYSIS_PIXELVALUEMANIPULATOR_H

#include <imageanalysis/ImageAnalysis/ImageTask.h>

#include <imageanalysis/ImageAnalysis/ImageCollapserData.h>
#include <imageanalysis/ImageAnalysis/PixelValueManipulatorData.h>

namespace casa {

template <class T> class PixelValueManipulator : public ImageTask<T> {
    // <summary>
    // Top level interface for getting and setting image pixel values.
    // </summary>

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

    // <prerequisite>
    // </prerequisite>

    // <etymology>
    // Manipulates pixel values.
    // </etymology>

    // <synopsis>
    // Top level interface for getting and setting image pixel values.
    // </synopsis>

    // <example>
    // </example>

public:

    enum Operator {
        ADDITION,
        SUBTRACTION,
        MULTIPLICATION,
        DIVISION,
        NONE
    };

    PixelValueManipulator() = delete;

    // regionRec = 0 => no region selected, full image used
    // mask = "" => No additional mask specification, although image default
    // mask will be used if one exists.
    PixelValueManipulator(
        const SPCIIT image, const casacore::Record *const regionRec,
        const casacore::String& mask,
        casacore::Bool verboseDuringConstruction=casacore::True
    );

    ~PixelValueManipulator() {}

    static void addNoise(
        SPIIT image, const casacore::String& type,
        const casacore::Record& region,
        const casacore::Vector<casacore::Double>& pars,
        casacore::Bool zero,
        const std::pair<casacore::Int, casacore::Int> *const &seeds
    );

    // <src>dirFrame</src> and <src>freqFrame</src> are the codes for the
    // frames for which it is desired that the returned measures should be
    // specified. In both cases, one can specify "native" for the native
    // coordinate frame, "cl" for the conversion layer frame, or any valid frame
    // string from casacore::MDirection::showType() or MFrequency::showType().
    static casacore::Record* coordMeasures(
        casacore::Quantum<T>& intensity, casacore::Record& direction,
        casacore::Record& frequency, casacore::Record& velocity,
        SPCIIT image, const casacore::Vector<casacore::Double>& pixel,
        const casacore::String& dirFrame, const casacore::String& freqFrame
    );

    // set axes to average over. If invert is true, select all axes other than
    // the specified axes to average over.
    void setAxes(const casacore::IPosition& axes, casacore::Bool invert=false);

    // Get pixel values, pixel mask values, etc.
    // The return casacore::Record has the following fields:
    // 'values' => casacore::Array<T> of pixel values
    // 'mask'   => casacore::Array<casacore::Bool> of pixel mask values
    casacore::Record get() const;

    // get a slice through the image. The values are interpolated at regular
    // intervals to provide samples at npts number of points. x and y are
    // in pixel coordinates

    static casacore::Record* getSlice(
        SPCIIT image, const casacore::Vector<casacore::Double>& x,
        const casacore::Vector<casacore::Double>& y,
        const casacore::Vector<casacore::Int>& axes,
        const casacore::Vector<casacore::Int>& coord,
        casacore::Int npts=0, const casacore::String& method="linear"
    );

    static void put(
        SPIIT image, const casacore::Array<T>& pixelsArray,
        const casacore::Vector<casacore::Int>& blc,
        const casacore::Vector<casacore::Int>& inc, casacore::Bool list,
        casacore::Bool locking, casacore::Bool replicate
    );

    static casacore::Bool putRegion(
        SPIIT image, const casacore::Array<T>& pixels,
        const casacore::Array<casacore::Bool>& mask,
        casacore::Record& region, casacore::Bool list,
        casacore::Bool usemask, casacore::Bool replicateArray
    );

    // get the aggregated values along the specified pixel axis using the region
    // and mask at construction and any other mask the image may have. Supported
    // values of <src>function</src> are (case-insensitive, minimum match) those
    // supported by ImageCollapser, ie "flux", "max", "mean", "median", "min",
    // "rms", "sqrtsum", "sqrtsum_npix", sqrtsum_npix_beam", "stddev", "sum",
    // "variance",  and "zero". Aggregation of values occurs along all axes
    // orthogonal to the one specified. One may specify the unit in which
    // coordinate values are calculated using the <src>unit</src> parameter. If
    // unit starts with "pix", then pixel coordinates are calculated, world
    // coordinates otherwise. If pixel coordinates, the values are relative to
    // the zeroth pixel on the corresponding axis of the input image.  If
    // specified and it doesn't start with "pix", the unit must be conformant
    // with the unit of <src>axis</src> in the coordinate system of the image,
    // or it must be a unit that this axis can be readily converted to (eg km/s
    // if the axis is a frequency axis with base unit of Hz). If the selected
    // axis is the spectral axis and if the unit is chosen to be something other
    // than the native spectral coordinate unit (such as velocity or wavelength
    // for a native frequency unit), <src>specType</src> indicates the system to
    // use when converting the frequency. Values of RELATVISTIC, RADIO_VELOCITY,
    // and OPTICAL_VELOCITY are only permitted if <src>unit</src> represents a
    // velocity unit. Values of WAVELENGTH and AIR_WAVELENGTH are only permitted
    // if <src>unit</src> represents a length unit. For a velocity unit, DEFAULT
    // is equivalent to RELATIVISTIC. For a length unit, DEFAULT is equivalent
    // to WAVELENGTH.
    // If the selected axis is the spectral axis and <src>unit</src> is a
    // velocity unit, <src>restFreq</src> represents the rest frequency with
    // respect to which the velocity scale should be calculated. If null, the
    // rest frequency associated with the spectral coordinate is used. If the
    // selected axis is the spectral axis, and <src>unit</src> is a frequency
    // unit, <src>frame</src> represents the frame of reference with respect to
    // which the frequency scale should be calculated. If empty, the reference
    // frame associated with the spectral coordinate is used. The return Record
    // has the following keys: "values" is a casacore::Vector<T> containing the
    // aggregate pixel values, "mask" is the associated mask values
    // (Vector<Bool>), "coords" is a casacore::Vector<casacore::Double> of
    // coordinate values, and "xUnit" is a casacore::String containing the
    // coordinate unit, and "yUnit" is a string containing the ordinate unit.
    casacore::Record getProfile(
        casacore::uInt axis, const casacore::String& function,
        const casacore::String& unit,
        PixelValueManipulatorData::SpectralType specType
            =PixelValueManipulatorData::DEFAULT,
        const casacore::Quantity *const restFreq=nullptr,
        const casacore::String& frame=""
    );

    casacore::Record getProfile(
        casacore::uInt axis, ImageCollapserData::AggregateType function,
        const casacore::String& unit,
        PixelValueManipulatorData::SpectralType specType
            =PixelValueManipulatorData::DEFAULT,
        const casacore::Quantity *const restFreq=nullptr,
        const casacore::String& frame=""
    );

    casacore::String getClass() const { return _className; }

    // region refers to the region in the image to be inserted, not the region
    // that was chosen at object construction
    static void insert(
        casacore::ImageInterface<T>& target,
        const casacore::ImageInterface<T>& image,
        const casacore::Record& region,
        const casacore::Vector<casacore::Double>& locatePixel,
        casacore::Bool verbose
    );

    // Make a block of regions from a Record
    // public so ImageAnalysis can use it, once those methods have been
    // excised, make private
    static void makeRegionBlock(
        casacore::PtrBlock<const casacore::ImageRegion*>& regions,
        const casacore::Record& Regions
    );

    casacore::Record pixelValue(
        const casacore::Vector<casacore::Int>& pixel
    ) const;

    void pixelValue(
        casacore::Bool& offImage, casacore::Quantum<T>& value,
        casacore::Bool& mask, casacore::Vector<casacore::Int>& pos
    ) const;

    // set specified pixels or mask equal to provided scalar value
    static casacore::Bool set(
        SPIIF image, const casacore::String& pixels,
        const casacore::Int pixelmask, casacore::Record& region,
        const casacore::Bool list = false
    );

    // set region name for logging purposes. Only used if the logfile is set.
    void setRegionName(const casacore::String& rname) { _regionName = rname; }

protected:
    CasacRegionManager::StokesControl _getStokesControl() const {
        return CasacRegionManager::USE_ALL_STOKES;
    }

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

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

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

private:
    casacore::IPosition _axes, _lastChunkShape;
    casacore::String _regionName;
    const static casacore::String _className;

    void _checkUnit(
        const casacore::String& unit, const casacore::CoordinateSystem& csys,
        PixelValueManipulatorData::SpectralType specType
    ) const;

    SPIIT _doComposite(
        casacore::uInt axis, const casacore::String& function, Operator op
    ) const;

    SPIIT _doSingle(
        casacore::uInt axis, const casacore::String& function
    ) const;

    casacore::Record _doWorld(
        SPIIT collapsed, const casacore::String& unit,
        PixelValueManipulatorData::SpectralType specType,
        const casacore::Quantity *const restFreq, const casacore::String& frame,
        casacore::uInt axis
    ) const;

    void _doNoncomformantUnit(
        casacore::Vector<casacore::Double>& coords,
        const casacore::CoordinateSystem& csys,
        const casacore::String& unit,
        PixelValueManipulatorData::SpectralType specType,
        const casacore::Quantity *const restFreq,
        const casacore::String& axisUnit
    ) const;

    casacore::Vector<casacore::uInt> _npts(casacore::uInt axis) const;
};

}

#ifndef AIPS_NO_TEMPLATE_SRC
#include <imageanalysis/ImageAnalysis/PixelValueManipulator.tcc>
#endif //# AIPS_NO_TEMPLATE_SRC

#endif