#pragma once

//This is part of MSU Video Quality Measurement Tool (VQMT) back SDK
//For MSU VQMT 12 BETA Premium
//MSU G&M Lab. Video Group 2019

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifndef VQMT_EXPORT
#define VQMT_EXPORT
#endif

//internal VQMT structs
struct VQMT_MetricInst;
struct VQMT_MetricHandler;
struct VQMT_ImageConverter;
struct VQMT_RawBuffer;
struct VQMT_ArbitraryRequest;

/**
 * Callback, that will notify you about calculated value.
 * Each metric can output several values per frame (columns), typically 1 value.
 * The index of value will be transmitted to valueIdx.
 *
 * If value was requested via vqmt_requestCalculationArbitrary, payload param will be 
 * a pointer, specified to vqmt_requestCalculationArbitrary call, else (and for 
 * accumulated values) it will be nullptr
 *
 * If frame < 0 then it is a global metric value (accumulator)
 */
typedef void(*VQMT_MetrCB)(int frame, int valueIdx, float value, void* cbparam, void* payload);

enum class VQMT_CC {
	Y = 0,
	U = 1,
	V = 2,
	L = 3,
	R = 4,
	G = 5,
	B = 6,
	unused
};

enum class VQMT_MetricCC {
	Y = 0,
	U,
	V,
	L,
	R,
	G,
	B,
	YUV, RGB,
	unused
};

enum class VQMT_ValueType {
	V_8u = 0, //<- sample type uint8_t
	V_16u,	  //<- sample type uint16_t
	V_32f,	  //<- sample type float
	unused
};

enum class VQMT_AccumulatorType {
	METRIC_VALUE, //<- value in same dimension as metric value (for example, mean value)
	FRAME,        //<- integer frame number
	OTHER_VALUE,  //<- value in other dimension than metric value (for example, standard deviation)
	unused
};

enum class VQMT_VisualizationType {
	NONE = 0,     //<- metric doesn't support visualization
	FLOAT = 1,	  //<- metric supports one float plane visualization
	FLOAT_x3 = 2, //<- metric support 3 float planes visualization (typicaly, RGB)
	BYTE = 4	  //<- metric support 1 octet plane visualization
};

/**
 * This structure contains information about VQMT metric.
 * To initialize metric use vqmt_createMetric with VQMT_MetricInfo::handler
 *
 * Filled for all metrics simulateously by vqmt_getMetricList call
 */
typedef struct {
	/**
	 * Metric can be identified by group and variation
	 */
	const char* group;
	const char* variation;

	const char* name;
	const char* interfaceName;
	const char* longName;

	const char* infoUrl;

	const char* configJSON;

	/**
	 * DeviceName is "cpu" for CPU metrics or name of device
	 */
	const char* deviceName;

	/**
	 * needReference is true if reference consumes 2 input images per frame.
	 * needReference is false if reference consumes 1 input image per frame.
	 */
	bool needReference;

	/**
	 * supportedCCs[0],...,supportedCCs[N-1] is list of supported color components by metric.
	 * supportedCCs[N] == VQMT_CC::unused
	 */
	const VQMT_MetricCC* supportedCCs;

	/*
	 * This handler should be passed to createMetric to create instance of that metric
	 */
	VQMT_MetricHandler* handler = nullptr;
} VQMT_MetricInfo;

/**
 * This structure is to obtain supported image formats of metric
 *
 * User should fill availablePlanes before call to vqmt_requestPlanes
 * requestedPlanes will be filled by vqmt_requestPlanes
 */
typedef struct {
	bool availablePlanes[(int)VQMT_ValueType::unused][(int)VQMT_CC::unused];
	bool requestedPlanes[(int)VQMT_ValueType::unused][(int)VQMT_CC::unused];
} VQMT_ImageRequest;

/**
 * This struct describes one plane from VQMT_MetricImage
 *
 * Should be filled by user as member of VQMT_MetricImage
 * to use in vqmt_putFrame
 */
typedef struct {
	int strideBytes;
	float rangeMin;
	float rangeMax;
	const void* data;
} VQMT_PlaneDescription;

/**
 * This structure contains input frame image for metric
 *
 * Should be filled by user to use in vqmt_putFrame
 */
typedef struct {
	int width;
	int height;

	/**
	 * User should fill planes, requested by vqmt_requestPlanes call, coresponding to true values
	 * in VQMT_ImageRequest::requestedPlanes
	 */
	VQMT_PlaneDescription planes[(int)VQMT_ValueType::unused][(int)VQMT_CC::unused];
} VQMT_MetricImage;

/**
 * Information about a column
 *
 * Metric can output multiple values per frame (columns)
 * Typical metrics provide only one column
 *
 * This structure will be filled by vqmt_getConfiguredMetricInfo
 * as member of VQMT_ConfiguredMetricInfo
 */
typedef struct {
	//NOTE: name and unit can be empty string
	//we guarantee, that if metric has several columns, they will have different names
	const char* name;
	const char* unit;

	//if isIncline, higher metric value means better quality
	bool isIncline;
} VQMT_MetricOutputValue;

/**
 * Information about an accumulator
 *
 * Accumulator provides global metric value
 * Typical accumulators: mean, harmonic mean, std. dev, variance, minimum value, maximum value, minimum frame, maximum frame
 * For PSNR also total PSNR
 *
 * This structure will be filled by vqmt_getConfiguredMetricInfo 
 * as member of VQMT_ConfiguredMetricInfo
 */
typedef struct {
	/**
	 * value for this accumulator will be reported to the callback with this frame value
	 * frameId < 0, this will help you distinguish accumulated value from a value for a frame
	 */
	int frameId;

	const char* key;
	const char* name;
	VQMT_AccumulatorType type;

	//if type == FRAME, this is key for accumulator, which contained value for this frame
	//for example, if key == "min_frame" than isFrameFor == "min_val"
	const char* isFrameFor;
} VQMT_Accumulator;

/**
 * This structure contains all necessary information about metric
 * after it has been initialized. This structure will be filled by 
 * vqmt_getConfiguredMetricInfo
 */
typedef struct {
	/**
	 * If metric outputs N values per frame, than valueNames[0], ..., valueNames[N-1] 
	 * contains descriptions of that values,
	 * columnsCount = N
	 *
	 * The number of output values can depends on configuration of metric.
	 */
	int columnsCount;
	VQMT_MetricOutputValue* outputValues;

	/**
	 * About accumulators see note for VQMT_Accumulator
	 * 
	 * Metric should provide a value for each column and each accumulator
	 * Metric will provide values for accumulators during vqmt_releaseMetric call
	 * Value for accumulator can be NAN
	 */
	int accumulatorsCount;
	VQMT_Accumulator* accumulators;

	VQMT_VisualizationType supportedVisualization;
	float visualizationMinRange;
	float visualizationMaxRange;

	/**
	 * If true, you can call vqmt_putFrame in arbirary order from different threads
	 * Be sure, you must call vqmt_requestCalculationMonotonic and vqmt_requestCalculationArbitrary 
	 * not overlaping calls
	 * 
	 * If false, all calls to vqmt_putFrame should be from single thread,
	 * with monotonic frame number
	 */
	bool supportMultithreading;

	/**
	 * To calculate value for frame N you should provide at least lookforwardFrames
	 * frames before N and at least lookbackFrames after N
	 *
	 * So, to get value for frame N you should provide frames N - lookforwardFrames .. N + lookbackFrames
	 * if you don't need values for some of these frames, you should'n call 
	 * vqmt_requestCalculationMonotonic or vqmt_requestCalculationArbitrary
	 *
	 * If lookforwardFrames==-1 or lookbackFrames==-1, you can use only vqmt_requestCalculationMonotonic 
	 * and you should not skip frames for this metric
	 */
	int lookforwardFrames;
	int lookbackFrames;

	const char* configSummary;
	const char* configError;
} VQMT_ConfiguredMetricInfo;

/**
 * This structure to get visualization image from metric
 * You should allocate buffers and set proper pointers according to
 * supportedVisualization value from VQMT_ConfiguredMetricInfo
 * all not set pointers should be nullptr
 */
typedef struct {
	/**
	 * if metric outputs float plane, it will be written to this pointer
	 * can be NULL
	 * floatStrides in bytes, must be divisible by sizeof(float)
	 * ! pointer should be allocated and set by user
	 */
	float* floatData;
	int floatStrides;

	/**
	 * if metric outputs 3 float planes, it will be written to these pointers
	 * all can be NULL
	 * float3Strides[...] in bytes, must be divisible by sizeof(float)
	 * ! pointers should be allocated and set by user
	 */
	float* float3Data[3];
	int float3Strides[3];

	/**
	 * if metric outputs octet planes, it will be written to these pointers
	 * can be NULL
	 * byteStrides in bytes
	 * ! pointer should be allocated and set by user
	 */
	uint8_t* byteData;
	int byteStrides;

	/**
	 * you should fill width and height
	 * this is size of all images, specified by not null pointers from ...Data
	 */
	int width, height;
} VQMT_VisualizationImage;

/**
 * This structure represents planes of raw image (for example, yuv420p, 
 * rgb images, etc).
 * Only some first planes (depending on format) can be filled.
 */
typedef struct {
	int planeCount;
	void* planes[8];
	int strides[8];
	int width[8];
	int height[8];
} VQMT_RawImage;

/**
 * Get information about all metrics
 *
 * If MetricInfo* res = getMetricList()
 * then res[0], res[1], ..., res[N-1] is the list of all available metrics,
 * res[N].handler == nullptr, res[i].handler != nullptr for 0 <= i < N
 */
VQMT_EXPORT const VQMT_MetricInfo* vqmt_getMetricList();

/**
 * Initialize metric
 * \param handler should be handler from MetricInfo instance, obtained by getMetricList()
 * \param width,height must be positive
 * \param color should be in supportedCCs of corresponding MetricInfo instance
 * \param configJson can be nullptr or JSON object for configuring the metric.
 * If configJson is incorrect JSON or metric could not match this configuration, nullptr will be returned
 * \param callback is used to obtain value from metric
 * \param cbparam if value that will be forwarded to callback as last argument
 *
 * \returns nullptr on error, pointer to metric instance otherwise.
 * Returned not null should be freed using vqmt_releaseMetric
 */
VQMT_EXPORT VQMT_MetricInst* vqmt_createMetric(const VQMT_MetricHandler* handler, int width, int height, VQMT_MetricCC color, bool needVisualization, const char* configJson, VQMT_MetrCB callback, void* cbparam);

/**
 * Return information about a configured metric
 * See VQMT_ConfiguredMetricInfo for more detailes
 */
VQMT_EXPORT VQMT_ConfiguredMetricInfo vqmt_getConfiguredMetricInfo(VQMT_MetricInst* inst);

/**
 * NOTE: recommend alternative for this function is vqmt_convertImage
 * which will do fast convertion from arbitrary format to all formats, needed by metrics you have
 *
 * This function will help you determinate, what input formats are supported by metric
 * Every metric guaranteed to support float images.
 * In requests you should set, what input formats (for each color components) you can provide
 * We recommend always add float planes for demanded color components
 *
 * This function will fill requests->requestedPlanes. And it fill requests->requestedPlanes[x][y] only if 
 * requests->availablePlanes[x][y] is true.
 *
 * You can use this function only once, it will return always the same result for some metric instance.
 * If metric can't work with data, specified in requests->availablePlanes, positive numebr will be returned
 *
 * NOTE: you are responsible to convert images to the formats, needed by the metric (returned by this function)
 * 
 * \returns 0 if success, not 0 if failed (can't work with available planes)
 */
VQMT_EXPORT int vqmt_requestPlanes(VQMT_MetricInst* inst, VQMT_ImageRequest* requests);

/**
 * Mark a frame as target before vqmt_putFrame and request metric calculation for this frame
 *
 * You should call this function before calculation of every frame (vqmt_putFrame)
 * Calls to this function should be monotonic (by frame for some inst) and not overlapped 
 * for the single inst even if metric is multithreaded.
 *
 * After all frames are put call vqmt_putEof
 *
 * If you use this function, you must provide every dependent frame (according to 
 * lookforwardFrames and lookbackFrames) via vqmt_putFrame only once
 */
VQMT_EXPORT void vqmt_requestCalculationMonotonic(VQMT_MetricInst* inst, int frame);

/**
 * Mark a frame as target before vqmt_putFrame and request metric calculation for this frame
 *
 * You should call this function before calculation of every frame (vqmt_putFrame)
 * You can call this (for multithreaded metrics only) with arbitrary order of frame,
 * but calls for same inst should be not overlapped.
 *
 * After call to this, you must provide all dependent frames (according to lookforwardFrames 
 * and lookbackFrames) even if some of them were provided for other request
 *
 * You shouldn't use this function for a metric if it is not multithreaded or
 * lookforwardFrames == -1 or lookbackFrames == -1
 *
 * Returned object must be deleted via vqmt_freeArbitraryRequest after all frames are supplied and 
 * values received
 *
 * \param payload additional pointer, which will be transmitted to callback, 
 * specified in vqmt_createMetric as payload
 */
VQMT_EXPORT VQMT_ArbitraryRequest* vqmt_requestCalculationArbitrary(VQMT_MetricInst* inst, int frame, void* payload);
VQMT_EXPORT void vqmt_freeArbitraryRequest(VQMT_ArbitraryRequest*);

/**
 * Provide input images (target or supplimentary)
 *
 * \param inst is non-released metric, obtained by createMetric
 * \param request should be nullptr if you requested frame via vqmt_requestFrameMonotonic or 
 * it should be return values of vqmt_requestFrameArbitrary
 * \param frame frame number. It will be reported to callback with this value
 * \param images is array of pointers to input frames. It should contain 1 pointer if !metricInfo.needReference else 2 pointers
 * \param output is RGB visualization image. It can be nullptr if visualization is not needed
 *
 * First put frame assumed to be 0 frame, second is 1-st, etc.
 * \return 0 on success, not null in case of error
 */
VQMT_EXPORT int vqmt_putFrame(VQMT_MetricInst* inst, VQMT_ArbitraryRequest* request, int frame, const VQMT_MetricImage** images, VQMT_VisualizationImage* output);

/**
 * Tell to metric, that there are no more frames after some frame
 * \param request - nullptr if you work with vqmt_requestCalculationMonotonic or task, returned from 
 * vqmt_requestCalculationArbitrary
 * \param frame - first frame, that are not exists in the input. It can be even requested frame
 *
 * First put frame assumed to be 0 frame, second is 1-st, etc.
 * \return 0 on success, not null in case of error
 */
VQMT_EXPORT int vqmt_putEof(VQMT_MetricInst* inst, VQMT_ArbitraryRequest* request, int frame);

/**
 * Stop metric and free it's memory.
 *
 * Be sure, you called vqmt_putEof if vqmt_requestCalculationMonotonic was preformed
 * Metric can provide some values during this call.
 * Also, it will provide average values to callback.
 */
VQMT_EXPORT void vqmt_releaseMetric(VQMT_MetricInst* inst);

/**
 * Create convertor, that will help you apply VQMT metric to images you have
 *
 * \param inputFormats - list of input formats. 1-st value is the format of original image
 * 2-nd value is the format of processed image (if some metrics are reference ones).
 * For example, if your inputs are yuv420 and you have a reference metric (at leas one 
 * metric from list is reference), you should specify 
 * inputFormats == {"yuv420", "yuv420"}
 *
 * VQMT supports over 1200 format names, for the full list of available formats 
 * see `vqmt -list raw' command output
 *
 * \param metricList - one or more metrics to initialize converter for (null-terminated lsit)
 * You can specify multiple metrics and then convert image for them simultaneaously
 * \param errBuff - buffer for writing error. Can be nullptr, in this case errBuffMax is ignored
 *
 * Retrun value can be nullptr in case of some error, which can be:
 * - unknown image format for some index
 * - metrics has differnet size
 *
 * Returned not-null can be used in vqmt_convertImage for converting image
 * Returned not-null must be freed with vqmt_freeImageConverter
 */
VQMT_EXPORT VQMT_ImageConverter* vqmt_initImageConverter(const char** inputFormats, VQMT_MetricInst** metricList, char* errBuff=nullptr, int errBuffMax = 0);
VQMT_EXPORT void vqmt_freeImageConverter(VQMT_ImageConverter*);

/*
 * Do convertion before metric applying and fill some VQMT_MetricImage
 *
 * \param buffer - buffer allocated with vqmt_allocateBuffer and then filled with your image
 * if buffer is nullptr nothing is done and true returned
 * \param converter - converter, allocated with vqmt_initImageConverter
 * \param index - 0 for reference image or non-reference metric, 1 for distorted image
 * \param dstImage - image, that will be suitable for all metrics, specified in metricList
 * while vqmt_initImageConverter call
 *
 * \return true if conversion success or buffer is nullptr, otherwise, false
 * 
 * NOTE: data in dstImage will be valid till next call with same converter AND index,
 * you shouldn't parallel these calls
 * NOTE: you can parallel calls to this if converter OR index are different
 * NOTE: this call can modify VQMT_RawBuffer* buffer, even placement of it's data
 * (it can swap data of dstImage and data of buffer)
 * NOTE: dstImage data will never overlap with buffer data after call to this function
 */
VQMT_EXPORT bool vqmt_convertImage(VQMT_RawBuffer* buffer, VQMT_ImageConverter* converter, int index, VQMT_MetricImage* dstImage);

/**
 * Allocate buffer of specified format
 * Each row of this buffer is guaranteed to be 64-byte aligned
 * You can access data of this buffer (for reading or writing)
 * via vqmt_getBufferData
 *
 * \param format - format name
 * VQMT supports over 1200 format names, for the full list of available formats 
 * see `vqmt -list raw' command output
 *
 * NOTE: you must free this buffer using vqmt_freeBuffer
 */
VQMT_EXPORT VQMT_RawBuffer* vqmt_allocateBuffer(const char* format, int width, int height);
VQMT_EXPORT void vqmt_freeBuffer(VQMT_RawBuffer*);

/**
 * Get pointers to buffer data (for reading or writing)
 * Only first some planes will be inited,
 * Count is equal to the number of planes,
 * supported by that format
 */ 
VQMT_EXPORT VQMT_RawImage vqmt_getBufferData(VQMT_RawBuffer*);

#ifdef __cplusplus
} // extern "C"
#endif