//================================================================
// lib2D
//
// Camera
//
// AUTHOR:  Gabor Nagy
// DATE:    2023-Jun-20 20:42:30
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _EpCamera_h
#define _EpCamera_h

#ifndef EClass_h
#include <EClass.h>
#endif  // EClass_h

#include <EThreads.h>

#include <Image/GL/Texture.h>

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif


// Processing modes
//
enum
{
	ECPU, ECPU_MULTI_THREADED, EGPU
};


// Exposure modes
//
enum
{
	EpExposureMANUAL = 1,
	EpExposureAUTO_APERTURE_PRIORITY
};

typedef struct EpCameraInterfaceClass_S  EpCameraInterfaceClass;
typedef struct EpCamera_S                EpCamera;

/*
#define Ep_THREAD_LOCK(m)    do { pthread_mutex_lock(&(m##_mutex));m##_mutex_LockCount++;assert(m##_mutex_LockCount == 1); } while(0)
#define Ep_THREAD_UNLOCK(m)  do { pthread_mutex_unlock(&(m##_mutex));m##_mutex_LockCount--;assert(m##_mutex_LockCount == 0); } while(0)
*/
#define Ep_THREAD_LOCK(m)    do { pthread_mutex_lock(&(m##_mutex)); } while(0)
#define Ep_THREAD_UNLOCK(m)  do { pthread_mutex_unlock(&(m##_mutex)); } while(0)


typedef struct
{
	char*  Name;
} EpColorFilter;

typedef struct
{
	EIndex  Slot;
} EpFilterWheelSettings;

#define EpFilterWheelCore\
	char*  Name;\
	EList(, EpColorFilter*, FilterSlots);\
	EList(, int32_t, AutoCycleSequence);EBool  AutoCycle;\
	EpFilterWheelSettings  Settings, RequestedSettings, AcknowledgedSettings;

typedef struct
{
	EpFilterWheelCore;
} EpFilterWheel;



// User-settable parameters
// There is a version of this struct in the Camera and one in the Interface
// The EpCamera drivers check if the one in the Camera is different and try to apply it.
// If the camera could not apply a setting, the values will remain different in the Camera and in the Interface
// This allows for easy comparisons of what the app wanted and what the driver can support.
// It also allows for detecting setting change requests in the driver and applying them at the _right time_ (e.g. when a frame is about to begin), without relying on a large number of "changed" flags.
// Unlike using uint32_t flags, this also supports more than 32 user-settable parameters.
//
typedef struct
{
	EIndex2   Resolution;
	int       CapturePixelFormat;  // Physical capture format (may be different than the Images in Camera->Images[] field. If the Camera supports RAW formats, demosaicking will happen in the API/EpCamera)

	float     FramesPerSecond;     // Frames captured per second

// Exposure
//
	uint8_t   ExposureMode;
	EIndex    ShutterTime_us;      // Microseconds that the shutter is open
	float     Gain;                // May be linear or dB
	float     LumaGamma;           // There may be separate gamma values for R,G,B. This is for luminanace
	float     Saturation;          // Color saturation

	EBool     HDR;                 // On-camera HDR (e.g. Ximea XiQ cameras)

	EBool     NormalizePixelValues;  // E.g. 10-bit values 16 bits. When not normalized, they may range from 0 to 0x3FF. When normalization is enabled, they will be shifted up to go from 0 to 0xFFC0

	EBool     WhiteBalance_Auto;float  WhiteBalance_ColorTemperature;
	int       Sharpness;

	uint32_t  FlipX:1, FlipY:1;

// Region of interest (image sub-region to capture)
//
	EBool     ROI;EIndex2  ROI_Position, ROI_Size;

// Cooling
//
	EBool     Cooling_On;
	float     Cooling_Power, Cooling_TargetTemperature;
} EpCameraSettings;

enum
{
	EpLINEAR = 1,
	EpdB
};


// Used for dark frames etc.
//
typedef struct
{
	EIndex    ShutterTime_us;  // Microseconds that the shutter is open
	float     Gain;            // May be linear or dB
	float     Temperature;     // In degrees Celsius
	EpImage*  Image;
} EpCalibrationImage;


typedef struct
{
	char*     Name;         // E.g. "60fps"
	uint32_t  Numerator, Denominator;
} EpFrameRate;


typedef struct
{
	char*     Name;         // E.g. "1080p", or "2560x1440 (s)", or "4144x2814 b2"
	char*     Description;  // E.g. "1920 x 1080", or "2560 x 1440 (scaled)", or "4144x2814 with 2x2 binning"
	int       Binning;      // Camera-side binning level. E.g. 2 means 2x2 piels are averaged
	EIndex2   Min, Max, Step;

	EList(, EpCalibrationImage*, DarkFields);  // Dark field Images for different shutter times, gains and sensor temperatures
	EpImage*  FlatFieldImage;                  // This is calibrated, so only one Image is needed per optics

	EList(, EpFrameRate, FrameRates);  // Suppoerted framerates for this pixel format and resolution
} EpResolution;


typedef struct
{
	int  PixelFormat;
	EList(, EpResolution, Resolutions);
} EpPixelFormatAndResolutions;


enum
{
	EpGraticuleCROSSHAIR = 1,
	EpGraticuleCIRCLE
};

typedef struct
{
	float   TargetTemperature_Min, TargetTemperature_Max;  // Celsius. Both values are 0.0 if cooling is not supported
	float   Power_Min, Power_Max;                          // Both values are 0.0 if cooling is not supported
} EpCameraCooling;

#define EpCameraInterfaceCore\
	EObjectCore(EpCameraInterface);\
\
	EList(, char*, DevicePaths);                                                   /* E.g. /dev/video0 for UVC V4L2 Cameras, "ximea:0" or "ximea:MQ042CG-CM/48282451" format for Ximea cameras */\
	char*             ModelName;\
	char*             SerialNumber;\
	char*             Version;\
	char*             IDString;                                                    /* A string that uniquely identifies a device (ModelName + SerialNumber)*/\
\
	ETime             InitializationStart_ms;\
	int               ColorFilterArray;\
	int               Demosaic;                                                    /* De-bayer algorithm used (on the host) : 0 none, EcDemosaicBILINEAR, EcDemosaicMALVAR                      */\
	int               BlackLevel;\
	int               SensorMax;                                                   /* E.g. 0x03FF for 10-bit un-normalized pixel values */\
	EIndex            Exposure_ShutterTime_us_Min, Exposure_ShutterTime_us_Max;    /* Microseconds  */\
	EList(, EIndex, ShutterTimes_us);                                              /* Some cameras only accept a limited set of shutter times    */\
	ETime             LongExposureStart_us;                                        /* When not -1, a long exposure is in progress (bulb mode) and it shows when the exposure started            */\
	int               Exposure_Gain_Type;                                          /* EpLINEAR or EpdB  */\
	float             Exposure_Gain_Min, Exposure_Gain_Max;\
	EBool             Exposure_LumaGamma_Supported;float  Exposure_LumaGamma_Min, Exposure_LumaGamma_Max;\
	EBool             Exposure_Saturation_Supported;float  Exposure_Saturation_Min, Exposure_Saturation_Max;\
	int               BacklightCompensation_Min, BacklightCompensation_Max;\
	float             FramesPerSecond_Min, FramesPerSecond_Max;\
	EBool             WhiteBalance_Auto_Supported;int  WhiteBalance_ColorTemperature_Min, WhiteBalance_ColorTemperature_Max;\
	int               Sharpness_Min, Sharpness_Max;\
	uint16_t          ROI_Supported:1,                                             /* Region of interest (image sub-region to read)              */\
	                  HDR_Supported:1,                                             /* On-camera HDR support                                      */\
	                  BadPixelCorrection_Supported:1;\
	uint8_t           HDR_KneePointCount;\
	int               HDR_T1, HDR_K1,  HDR_T2, HDR_K2;                             /* Percent of EXPOSURE (shutter time) and kneepoint pairs     */\
	int               ResponseBins;                                                /* Number of entries in the Camera response tables            */\
	EpWeightedValue*  ResponseR;EpWeightedValue*  ResponseG;EpWeightedValue*  ResponseB;  /* Camera response temporary buffer. This may have up to 65536 values. May be less. E.g. 1024, if CapturePixelFormat is RAW10, 4096, if it's RAW12 */\
	EIndex            LastAcquiredFrame;\
	EChangeFlags      CameraSettingsChangedFromUI;                                 /* OR-ed flags of EpCameraChgPIXEL_FORMAT, EpCameraChgBAD_PIXEL_CORRECTION etc. */\
	pthread_mutex_t   CameraStatus_mutex;EIndex  CameraStatus_mutex_LockCount;\
	pthread_t         ImageCapture_pthread;\
	EBool             ImageCapture_ThreadCreated, ImageCapture_ThreadTerminate;\
	pthread_mutex_t   ImageCapture_mutex;EIndex  ImageCapture_mutex_LockCount;\
	EpCameraSettings  AcknowledgedSettings;                                        /* Setting requests acknowledged by the driver plugin. They may be different from below, if the driver is not able to support them. But, this way we won't keep trying to set them unnecessarily. */\
	EpCameraSettings  Settings;                                                    /* Settings currently in use (the application sets the ones it requests in the Camera and these are updated if/when the driver accepts them and starts using them)                                */\
	float             ChipTemperature, HousingTemperature;\
	int               CameraStatus, PrevStatus;\
	EList(, EpPixelFormatAndResolutions, PixelFormatsAndResolutions);\
\
	EpCameraCooling   Cooling;\
\
	EpFilterWheel*    FilterWheel


// CameraInterface status enums
// 0 : never initialized
//
enum
{
	EpCONNECTING               =  1,
	EpCONNECTING_DEVICE_FOUND  =  2,
	EpCONNECTING_DEVICE_OPENED =  3,
	EpREADY                    =  4,   // Ready, but not capturing (stopped)
	EpRUNNING                  =  5,   // Acquisition is running
	EpERROR                    = -1,
	EpNO_CAMERA                = -10,
	EpNO_INTERFACE             = -20,  // Could not initialize interface (e.g. Ximea or V4L plugin is not loaded)
	EpCOULD_NOT_CONNECT        = -30,
	EpLOST_CONNECTION          = -40   // Lost connection after it was already established once
};


// Color filter array types
//
enum
{
	EpBayerRGGB = 1,
	EpCMYG,
	EpRGR,
	EpBayerBGGR,
	EpBayerGRBG,
	EpBayerGBRG
};


enum
{
	EcDemosaicDROPIN = 1,
	EcDemosaicNEAREST,
	EcDemosaicBILINEAR,
	EcDemosaicMALVAR,
	EcDemosaicVNG,        // "Interpolation using a Threshold-based variable number of gradients"
	EcDemosaicSMOOTH_HUE,
	EcDemosaicAPI         // Do de-bayer in the camera's API (don't necessarily know what algorithm the manufacturer uses)
};


// Lens parameter flags
//
#define EpLensFOCUS          0x0001
#define EpLensAPERTURE       0x0002
#define EpLensNDFILTER       0x0004
#define EpLensIRCUT_FILTER   0x0008
#define EpLensIRPASS_FILTER  0x0010


// Camera settings change flags
//
#define EpCameraChgSHUTTERTIME           0x0001
#define EpCameraChgGAIN                  0x0002
#define EpCameraChgFRAMERATE             0x0004
#define EpCameraChgBAD_PIXEL_CORRECTION  0x0008
#define EpCameraChgPIXEL_FORMAT          0x0010
#define EpCameraChgRESOLUTION            0x0020
#define EpCameraChgHDR_EXPOSURE          0x0040
#define EpCameraChgREGION                0x0080
#define EpCameraChgMIRROR                0x0100
#define EpCameraChgWHITE_BALANCE         0x0200

#define EpCameraChgALL                   0x00FF


typedef struct
{
	EpCameraInterfaceCore;
} EpCameraInterface;


struct EpCameraInterfaceClass_S
{
	EBaseClassCore(EpCameraInterface);
//	int  (*DiscoverDevices)(EpCamera** PCameras);  // Return a list of Cameras on this interface/bus (e.g. all connected Ximea cameras, or all USB2.0 V2L4 cameras).

	EListA(, EpCameraInterface*, HWDevices);  // List of Cameras available on this interface/bus (e.g. all connected Ximea cameras, or all USB2.0 V2L4 cameras).

	int  (*CameraConnect)(EpCamera* PCamera);
	int  (*CameraDisconnect)(EpCamera* PCamera);

	int  (*CameraStartCapturing)(EpCamera* PCamera);
	int  (*CameraStopCapturing)(EpCamera* PCamera);
	int  (*CameraCaptureFrame)(EpCamera* PCamera,  uint8_t** PSrcBufferP, EIndex* PSrcBufferSizeP, EIndex* PBufferIndexP);

	int  (*CameraSetExposure)(EpCamera* PCamera);
	int  (*CameraSetLens)(EpCamera* PCamera, const uint32_t PFlags);
	int  (*CameraUpdateSettings)(EpCamera* PCamera);
	int  (*CameraGetStatus)(EpCamera* PCamera);

	int  (*CameraGetParam_i32)(EpCamera* PCamera, const EResource* PParam, int32_t* PValue);
	int  (*CameraSetParam_i32)(EpCamera* PCamera, const EResource* PParam, const int32_t PValue);
	int  (*CameraGetParam_i64)(EpCamera* PCamera, const EResource* PParam, int64_t* PValue);
	int  (*CameraSetParam_i64)(EpCamera* PCamera, const EResource* PParam, const int64_t PValue);
	int  (*CameraGetParam_f32)(EpCamera* PCamera, const EResource* PParam, float* PValue);
	int  (*CameraSetParam_f32)(EpCamera* PCamera, const EResource* PParam, const float PValue);

	ELoggingFlags  Verbosity;  // Or-ed flags of ELogTIMING etc.
};


extern EpCameraInterfaceClass   EpCameraInterface_class[];

extern void                     EpCameraInterfaceClass_DevicesFree(EpCameraInterfaceClass* PClass);

extern EpCameraInterfaceClass*  EpCameraInterface_ClassRegister(EpCameraInterfaceClass* PClassTemplate);
extern int                      EpCameraInterface_ClassDeactivate(EpCameraInterfaceClass* PClass);

extern int                      EpCamera_Connect(EpCamera* PCamera);
extern int                      EpCamera_Disconnect(EpCamera* PCamera);


enum EpIOMethod
{
	EpIO_METHOD_READ,
	EpIO_METHOD_MMAP,
	EpIO_METHOD_USERPTR
};


// h264 compression parameters
//
#define EpH264ParamIFRAME_PERIOD  0x0001
#define EpH264ParamBITRATE        0x0002

typedef struct EpCameraClass_S  EpCameraClass;

enum
{
	EFlagUNSUPPORTED = -1,
	EFlagOFF         =  0,
	EFlagON          =  1,
};

typedef struct
{
	int8_t  AutoFocus;     // EFlagOFF(0), EON(1) or EFlagUNSUPPORTED(-1)
	float   Focus;         // -1.0: undefined
	float   Aperture;      //  0.0: undefined
	float   NDFilter;      //  0.0: not active, otherwise linear factor (e.g. 4.0 = ND 4 (2 f-stops darker))
	int8_t  IRCutFilter;
	int8_t  IRPassFilter;  // EFlagOFF(0), EFlagON(1) or EFlagUNSUPPORTED(-1)
} EpCameraLens;


typedef struct
{
	EThreadCore;

	EpCamera*  Camera;
	EIndex2    SrcSize;  // The source image may be a different size than Image (e.g. demosaic in RAW_G mode)
	EpImage*   Image;
} EpImageProcessThread;


struct EpCamera_S
{
	EObjectCore(EpCamera);

	EpImage*            Images[2];             // Double-buffer if we process the frames with the CPU
	int                 DrawImageIndex;        // Which Image is ready to be drawn

// User parameters
//
	EpCameraSettings    RequestedSettings;     // Settings requested by the application. The HWInterface has its own EpCameraSettings with the requested value acknowledged and one with the actual settings being used.

	int                 ImageProcessMode;      // EpCPU, EpCPU_MULTI_THREADED, EpGPU
	int                 PixelFormatChanged;
	EpToneMapper        ToneMapper;            // White balance, response curve (pixel value to radiance-linear), gamma, exposure etc.
	EpExposure          Exposure;              // Software (re)exposure

	float               HDRExposure_ShutterMult;
	float               HDRExposure_CurrentShutterMult;
	uint16_t            Verbosity;

	float               Exposure_BackLightCompensation;

	EcRGBf32Color       WhiteBalance_RGB;           // White balance RGB coefficients (derived from WhiteBalance_ColorTemperature or directly specified)

	float               Image_Brightness, Image_Contrast, Image_Saturation, Image_Hue;

	EpCameraLens        LensRequest;                // Lens parameters requested by the application

	EBool               BadPixelCorrection;
	EBool               DarkFieldSubtraction;
	EpImage*            DarkFieldImage;

// Camera data
//
	char*               DeviceName;                 // Manufacturer device name
	char*               SerialNumberStr;            // Serial number as a string (may be alphanumeric)
	EIndex2             NativeResolution;

	int                 PixelFormat;                // Pixel format presented by the Camera, to the application (in the Images[] field)

	EpCameraLens        Lens;                       // Current lens parameters

	uint16_t            KeepRunning:1,              // Set to TRUE initially. If the app wants the Interface to give up waiting on something, it should set this to FALSE
	                    Connect_ThreadStarted:1,
	                    Connect_ThreadTerminate:1,
	                    RestartExposure:1,          // When in the middle of a long exposure ("bulb mode"), the app can interrup the camera driver by setting this to TRUE, so the user won't have to wait forever when he/she changes, say the shutter speed
                      CaptureInSeparateThread:1;

	EpCameraInterface*  HWInterface;                // Hardware interface the Camera is connected to (v4l2, Ximea etc.), or NULL if not connected

	EIndex              LastProcessedFrame;          // Last frame available to the application (e.g. some cameras only provide RAW format, so we need to demosaic etc.)
	EIndex              LastProcessedCompleteFrame;  // Last complete (e.g. not partially-exposed) frame available to the application

// General Video codec
//
	EIndex              VCODEC_BitRate;

// h264 codec
//
	EIndex              H264_IFramePeriod_ms;   // Create an i-frame every this milliseconds

	EObject*            UserObject;

	int                 GraticuleType;
	float               GraticuleSize;        // Proportion of image height

	EBool               FocusAssist;

// Image processing (de-bayer, tone map etc.)
//
	pthread_mutex_t  ImageProcess_mutex;

	EThreadSet       ImageProcessing_ThreadSet;
	pthread_mutex_t  ImageProcessing_GetWorkload_mutex;
	EBool            ImageProcessing_ThreadsCreated;
	const void*      ImageProcessing_SourcePixels;
	EIndex           ImageProcessing_NextWorkUnit;
	EIndex           ImageProcessing_PixelsDone;
	void*            ImageProcessing_OpenCLData;

// For OpenCL processing directly into OpenGL textures, from a separate thread
//
	void*            EGLXContext;
	void*            CurrentGLXContext;
	EPointerInt      EGLXDrawable;

	uint32_t         UpdateGUI;            // Valid parameter ranges changed by the driver etc. EpCameraChgFRAMERATE, EpCameraChgREGION etc.
};


struct EpCameraClass_S
{
	EBaseClassCore(EpCamera);
};


enum
{
	EpRecordModeFRAMES = 1,
	EpRecordModeDARK_FIELD,
	EpRecordModeFLAT_FIELD
};


extern EpCameraClass  EpCamera_class[];

EList(extern, EpCameraInterfaceClass*, EpCameraInterface_Classes);


extern int  _Ep_ResolutionCmp(const void* P0, const void* P1);

extern void  EpCameraSettings_Init(EpCameraSettings* PSettings);

//================================================================================================================
// Mark a frame ready to be consumed by the application.
// If PFrameComplete is FALSE, PCamera->LastProcessedCompleteFrame will not be incremented, to
// indicate that this frame was, for example partially exposed.
// An application may be ok displaying such frames, but it may not want to save / record them into a file
//================================================================================================================
static inline void EpCamera_FrameMarkProcessed(EpCamera* PCamera, const EBool PFrameComplete)
{
	EpCameraInterface*  LInterface = PCamera->HWInterface;
Ep_THREAD_LOCK(LInterface->ImageCapture);
	if(PCamera->LastProcessedFrame < LInterface->LastAcquiredFrame)
	{
		PCamera->LastProcessedFrame = LInterface->LastAcquiredFrame;
		if(PFrameComplete) PCamera->LastProcessedCompleteFrame = LInterface->LastAcquiredFrame;
	}
	PCamera->DrawImageIndex ^= 0x01;
Ep_THREAD_UNLOCK(LInterface->ImageCapture);
}


extern int   EpCamera_SetDeviceByPath(EpCamera* PCamera, const char* PDevicePath);
extern void  EpCameraInterface_Init(EpCameraInterface* PInterface);
extern void  EpCameraInterface_Free(EpCameraInterface* PInterface);


extern int            EpCamera_LoadCalibrationFrame(const EpCamera* PCamera,  const ETime PShutterTime_us, const float PGain,  const int PRecordingMode, const char* PColorFilterName, const char* PImageFileExtension,  EpCalibrationImage** PResult);
extern void           EpCamera_RecordingFileName(const EpCamera* PCamera, const int PRecordingMode, const char* PRecordingPath, const char* PColorFilterName, const EIndex PFrameSequenceIndex, const EIndex PFrameIndex, const char* PImageFileExtension,  char* PFileName);
extern EpResolution*  EpCamera_CurrentResolution(const EpCamera* PCamera);

extern int   EpCamera_ImageProcess(EpCamera* PCamera,  const void* PBayerPixels, const EIndex PSrcSizeX, const EIndex PSrcSizeY, const int PFilterArrayType,  const int PRAWPixelFormat, const int PAlgorithm,  EpImage* PImage,  const uint16_t PVerbosity);
extern void  EpCamera_CaptureThreadStop(EpCamera* PCamera);
extern void  EpCamera_ImageProcessingThreadsStop(EpCamera* PCamera);


// OpenCL stuff
//
extern int    EpCamera_OpenCLInit(void);
extern void   EpCamera_OpenCLFree(void);

extern int    EpCamera_OpenCLTest(void);

extern EBool  EpCamera_UseOpenCL;  // Use OpenCL for demosaicking and tone-mapping

extern int  (*EpCL_CameraDemosaicFunc)(EpCamera* PCamera, const uint8_t* PRAWPixels, const int PRAWBytesPerPixel, const int PAlgorithmID,  EpImage* PDstImage,  EpGLTexture* PDstOpenGLTexture);
extern void (*EpCL_CameraOpenCLDataFree)(EpCamera* PCamera);



#define _DebayerGetOffsets(pFilterArrayType)\
	EIndex  LROffset = 0,\
          LG0Offset = 0,  LG0_R0Offset = 0, LG0_R1Offset = 0,  LG0_B0Offset = 0, LG0_B1Offset = 0,\
          LG1Offset = 0,  LG1_R0Offset = 0, LG1_R1Offset = 0,  LG1_B0Offset = 0, LG1_B1Offset = 0,\
          LBOffset = 0,\
          LGatRrowBcolOffset = 0, LGatBrowRcolOffset = 0;\
	EBool   LDebayerOffsetsValid = TRUE;\
\
	switch(pFilterArrayType)\
	{\
		case EpBayerGBRG:\
			LROffset = LSrcSizeX;\
\
			LG0Offset = 0;            LG0_R0Offset = -LSrcSizeX;LG0_R1Offset =  LSrcSizeX;    LG0_B0Offset = -1;LG0_B1Offset =                 1;\
			LG1Offset = LSrcSizeX + 1;LG1_R0Offset =  LSrcSizeX;LG1_R1Offset =  2 + LSrcSizeX;LG1_B0Offset =  1;LG1_B1Offset = 1 + LSrcSizeX * 2;\
\
			LGatRrowBcolOffset = LG1Offset;\
			LGatBrowRcolOffset = LG0Offset;\
\
			LBOffset = 1;\
		break;\
\
		default:\
			printf("%s() %s:%d  %sIMPLEMENT ME: Unsupported filter array type %d %s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, pFilterArrayType, ECodeNormal);fflush(stdout);\
			LDebayerOffsetsValid = FALSE;\
		break;\
	}


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}  // Close scope of 'extern "C"' declaration
#endif

#endif // _EpCamera_h
