/*******************************************************************************
  Copyright(c) 2017 Ilia Platone, Jasem Mutlaq. All rights reserved.

 DSP plugin Interface

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Library General Public
 License version 2 as published by the Free Software Foundation.

 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; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
*******************************************************************************/

#pragma once

#include "indidevapi.h"
#include "dsp.h"

#include <fitsio.h>
#include <functional>
#include <string>

namespace INDI
{
class DefaultDevice;
}

namespace DSP
{
/**
 * @namespace DSP
 * @brief The DSP Namespace adds signal processing to INDI drivers. Primarily written for sensors and detectors, it can be used also
 * for CCDs. This namespace includes buffer transformations, convolution and signal filters, like bandpass and wavelets.
 */
extern const char *DSP_TAB;
class Interface
{
        /**
         * @brief The Interface class is the base class for the DSP plugins of INDI.
         *
         * The plugins must implement the Callback, which will be called by the DSP::Manager class. All the DSP
         * plugins work multidimensionally, so even for single dimensional streams the meaning is to add a single dimension.
         * Initially classes calls set dimensions and sizes the same as the original picture or frame, later or during processing
         * into the Callback functions the plugins can alter or set dimensions arbitrarily.
         * The plugins return an array which can be of various depths and a BLOB will be generated and sent to the client with the
         * result.
         * The DSP, when enabled by the property xxx_HAS_DSP, will generate properties for activation of the single plugins, and
         * after activation or deactivation, Activate() and Deactivate() methods will be called permitting further properties etc.
         * getSizes()/setSizes() will help within Callback to alter dimensions and sizes, getBPS/setBPS will change color depth or
         * sample size.
         * Classes that use the Interface class children should call processBLOB to propagate until children's Callback methods and
         * generate BLOBs.
         *
         * @see DSP::Convolution
         * @see DSP::Transforms
         * @see DSP::Manager
         * @see INDI::CCD and INDI::SensorInterface utilize the DSP Namespace.
         */
    public:
        /**
         * \struct Type
         * \brief Holds the process type
         */
        typedef enum
        {
            DSP_NONE = 0,
            DSP_DFT,
            DSP_IDFT,
            DSP_CONVOLUTION,
            DSP_WAVELETS,
            DSP_SPECTRUM,
            DSP_HISTOGRAM,
        } Type;

        virtual void ISGetProperties(const char *dev);
        virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n);
        virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n);
        virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n);
        virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[],
                               char *names[], int n);
        virtual bool saveConfigItems(FILE *fp);
        virtual bool updateProperties();

        /**
         * @brief processBLOB Propagate to Callback and generate BLOBs for parent device.
         * @param buf The input buffer
         * @param ndims Number of the dimensions of the input buffer
         * @param dims Sizes of the dimensions of the input buffer
         * @param bits_per_sample original bit depth of the input buffer
         * @return True if successful, false otherwise.
         */
        virtual bool processBLOB(uint8_t* buf, uint32_t ndims, int* dims, int bits_per_sample);

        /**
         * @brief setSizes Set the returned file dimensions and corresponding sizes.
         * @param num Number of dimensions.
         * @param sizes Sizes of the dimensions.
         */
        void setSizes(uint32_t num, int* sizes)
        {
            BufferSizes = sizes;
            BufferSizesQty = num;
        }

        /**
         * @brief getSizes Get the returned file dimensions and corresponding sizes.
         * @param num Number of dimensions.
         * @param sizes Sizes of the dimensions.
         */
        void getSizes(uint32_t *num, int** sizes)
        {
            *sizes = BufferSizes;
            *num = BufferSizesQty;
        }

        /**
         * @brief setBPS Set the returned file bit depth/sample size.
         * @param bps Bit depth / sample size.
         */
        void setBPS(int bps)
        {
            BPS = bps;
        }

        /**
         * @brief getBPS Get the returned file bit depth/sample size.
         * @return Bit depth / sample size.
         */
        int getBPS()
        {
            return BPS;
        }

        /**
         * @brief setIntegrationFileExtension Set the returned file extension.
         * @param ext The extension suffix.
         */
        void setCaptureFileExtension(const char *ext);

    protected:

        /**
         * @brief Activated Called after activation from client application.
         */
        virtual void Activated();

        /**
         * @brief Deactivated Called after deactivation from client application.
         */
        virtual void Deactivated();

        /**
         * @brief Callback Called by processBLOB.
         * @param buf The input buffer
         * @param ndims Number of the dimensions of the input buffer
         * @param dims Sizes of the dimensions of the input buffer
         * @param bits_per_sample original bit depth of the input buffer
         * @return The processed buffer
         */
        virtual uint8_t* Callback(uint8_t* buf, uint32_t ndims, int* dims, int bits_per_sample);

        /**
         * @brief loadFITS Converts FITS data into a dsp_stream structure pointer.
         * @param buf The input buffer
         * @param len Size of the FIT in bytes
         * @return A dsp_stream_p structure containing the data of the loaded FITS
         */
        dsp_stream_p loadFITS(char* buf, int len);

        bool PluginActive;

        IBLOBVectorProperty FitsBP;
        IBLOB FitsB;

        ISwitchVectorProperty ActivateSP;
        ISwitch ActivateS[2];

        //  We are going to snoop these from a telescope
        INumberVectorProperty EqNP;
        INumber EqN[2];

        ITextVectorProperty ActiveDeviceTP;
        IText ActiveDeviceT[4] {};

        Interface(INDI::DefaultDevice *dev, Type type = DSP_NONE, const char *name = "DSP_PLUGIN",
                  const char *label = "DSP Plugin");
        virtual ~Interface();

        const char *getDeviceName();
        INDI::DefaultDevice *m_Device {  nullptr };
        const char *m_Name {  nullptr };
        const char *m_Label {  nullptr };
        Type m_Type {  DSP_NONE };
        bool setStream(void *buf, uint32_t dims, int *sizes, int bits_per_sample);
        bool setMagnitude(void *buf, uint32_t dims, int *sizes, int bits_per_sample);
        bool setPhase(void *buf, uint32_t dims, int *sizes, int bits_per_sample);
        bool setReal(void *buf, uint32_t dims, int *sizes, int bits_per_sample);
        bool setImaginary(void *buf, uint32_t dims, int *sizes, int bits_per_sample);
        uint8_t *getStream();
        uint8_t *getMagnitude();
        uint8_t *getPhase();
        uint8_t *getReal();
        uint8_t *getImaginary();
        uint8_t* getBuffer(dsp_stream_p in, uint32_t *dims, int **sizes);
        dsp_stream_p stream;

    private:
        char captureExtention[MAXINDIBLOBFMT] { "fits" };
        void *buffer { nullptr };
        uint32_t BufferSizesQty {0 };
        int *BufferSizes { nullptr };
        int BPS { 16 };

        void fits_update_key_s(fitsfile *fptr, int type, std::string name, void *p, std::string explanation, int *status);
        void addFITSKeywords(fitsfile *fptr);
        bool sendFITS(uint8_t *buf, bool sendCapture, bool saveCapture);
        bool uploadFile(const void *fitsData, size_t totalBytes, bool sendIntegration, bool saveIntegration, const char* format);
        int getFileIndex(const char *dir, const char *prefix, const char *ext);
};
}
