#ifndef __CAPTURESTRUCTS_HPP
#define __CAPTURESTRUCTS_HPP

#include "utils.hpp"
#include "hw_defs.hpp"
#include <mutex>
#include <string>
#include "audio_data.hpp"

// It may happen that a frame is lost.
// This value prevents showing a black frame for that.
// Shouldn't happen with recent updates, though...
#define MAX_ALLOWED_NO_FRAME_TIME 0.120

#define FIX_PARTIAL_FIRST_FRAME_NUM 3

#define MAX_PACKET_SIZE_USB2 (1 << 9)
#define EXTRA_DATA_BUFFER_USB_SIZE MAX_PACKET_SIZE_USB2
#define EXTRA_DATA_BUFFER_FTD3XX_SIZE (1 << 10)
#define ERROR_DATA_BUFFER_FTD3XX_SIZE (1 << 10)

#define FTD2_INTRA_PACKET_HEADER_SIZE 2
#define MAX_PACKET_SIZE_FTD2 (MAX_PACKET_SIZE_USB2 - FTD2_INTRA_PACKET_HEADER_SIZE)

#define OPTIMIZE_3DS_AUDIO_BUFFER_MAX_SIZE 0x200

enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO, CAPTURE_CONN_CYPRESS_NISETRO, CAPTURE_CONN_CYPRESS_OPTIMIZE, CAPTURE_CONN_PARTNER_CTR };
enum InputVideoDataType { VIDEO_DATA_RGB, VIDEO_DATA_BGR, VIDEO_DATA_RGB16, VIDEO_DATA_BGR16 };
enum CaptureScreensType { CAPTURE_SCREENS_BOTH, CAPTURE_SCREENS_TOP, CAPTURE_SCREENS_BOTTOM, CAPTURE_SCREENS_ENUM_END };
enum CaptureSpeedsType { CAPTURE_SPEEDS_FULL, CAPTURE_SPEEDS_HALF, CAPTURE_SPEEDS_THIRD, CAPTURE_SPEEDS_QUARTER, CAPTURE_SPEEDS_ENUM_END };
enum PossibleCaptureDevices { CC_LOOPY_OLD_DS, CC_LOOPY_NEW_DS, CC_LOOPY_OLD_3DS, CC_LOOPY_NEW_N3DSXL, CC_IS_NITRO_EMULATOR, CC_IS_NITRO_CAPTURE, CC_IS_TWL_CAPTURE, CC_NISETRO_DS, CC_OPTIMIZE_O3DS, CC_OPTIMIZE_N3DS, CC_OPTIMIZE_O_O2DS, CC_PARTNER_CTR, CC_POSSIBLE_DEVICES_END };

// Readers are Audio and Video. So 2.
// Use 6 extra buffers. 5 for async writing in case the other 2 are busy,
// and the other for writing without overwriting the latest stuff in
// a worst case scenario...
enum CaptureReaderType { CAPTURE_READER_VIDEO, CAPTURE_READER_AUDIO, CAPTURE_READER_ENUM_END };
#define NUM_CONCURRENT_DATA_BUFFER_WRITERS 5
#define NUM_CONCURRENT_DATA_BUFFER_READERS ((int)CAPTURE_READER_ENUM_END)
#define NUM_CONCURRENT_DATA_BUFFERS (NUM_CONCURRENT_DATA_BUFFER_READERS + 1 + NUM_CONCURRENT_DATA_BUFFER_WRITERS)

#pragma pack(push, 1)

struct PACKED RGB83DSVideoInputData {
	uint8_t screen_data[IN_VIDEO_SIZE_3DS][3];
};

struct PACKED RGB83DSVideoInputData_3D {
	uint8_t screen_data[IN_VIDEO_SIZE_3DS_3D][3];
};

#define OLD_DS_PIXEL_R_BITS 5
#define OLD_DS_PIXEL_G_BITS 6
#define OLD_DS_PIXEL_B_BITS 5

// The internal order needs to be reversed... This is so confusing...
struct PACKED ALIGNED(2) USBOldDSPixelData {
	uint16_t r : OLD_DS_PIXEL_R_BITS;
	uint16_t g : OLD_DS_PIXEL_G_BITS;
	uint16_t b : OLD_DS_PIXEL_B_BITS;
};

struct PACKED ALIGNED(2) USBOldDSVideoInputData {
	USBOldDSPixelData screen_data[IN_VIDEO_SIZE_DS];
};

#define OPTIMIZE_3DS_PIXEL_R_BITS 5
#define OPTIMIZE_3DS_PIXEL_G_BITS 6
#define OPTIMIZE_3DS_PIXEL_B_BITS 5

// The internal order needs to be reversed... This is so confusing...
struct PACKED ALIGNED(2) USB5653DSOptimizePixelData {
	uint16_t b : OPTIMIZE_3DS_PIXEL_B_BITS;
	uint16_t g : OPTIMIZE_3DS_PIXEL_G_BITS;
	uint16_t r : OPTIMIZE_3DS_PIXEL_R_BITS;
};

struct PACKED USB8883DSOptimizePixelData {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};

struct PACKED USB3DSOptimizeSingleSoundData {
	uint16_t sample_index;
	uint16_t sample_r;
	uint16_t sample_l;
};

struct PACKED USB3DSOptimizeColumnInfo {
	// Order is Little Endian
	uint16_t column_index : 10;
	uint16_t unk : 4;
	uint16_t buffer_num : 1;
	uint16_t has_extra_header_data_2d_only : 1;
};

struct PACKED USB3DSOptimizeOldFirmwareColumnInfo {
	uint16_t fixed : 4; // Fixed to 5...?
	uint16_t column_index : 9;
	uint16_t extra_column_data : 2; // Can be 1 for RGB888 in 2D mode, always 0 for RGB565...?
	uint16_t is_first_column : 1;
};

struct PACKED USB3DSOptimizeHeaderData {
	uint16_t magic;
	USB3DSOptimizeColumnInfo column_info;
};

struct PACKED USB3DSOptimizeHeaderSoundData {
	USB3DSOptimizeHeaderData header_info;
	USB3DSOptimizeSingleSoundData samples[2];
};

// Old firmware RGB565 without sound. O2DS doesn't seem to have ever
// received RGB565 with sound...
struct PACKED USB5653DSOptimizeOldFirmwareHeaderData {
	uint16_t magic; // Magic is 0 if column_index is 1
	uint16_t unk;
	uint16_t num_pixels_passed;
	uint16_t unk2;
	USB3DSOptimizeOldFirmwareColumnInfo column_info;
	uint16_t unk3;
	uint16_t is_frame_not_2d_only;
	uint16_t unk4;
};

struct PACKED USB8883DSOptimizeOldFirmwareHeaderData {
	uint16_t magic; // Magic is 0 if column_info.column_index is 1
	USB3DSOptimizeOldFirmwareColumnInfo column_info;
};

struct PACKED USB8883DSOptimizeOldFirmwareHeaderSoundData {
	USB8883DSOptimizeOldFirmwareHeaderData header_info;
	USB3DSOptimizeSingleSoundData samples[2];
};

struct PACKED ALIGNED(16) USB5653DSOptimizeInputColumnData {
	USB3DSOptimizeHeaderSoundData header_sound;
	USB5653DSOptimizePixelData pixel[HEIGHT_3DS][2];
};

struct PACKED ALIGNED(16) USB5653DSOptimizeOldFirmwareInputColumnData {
	USB5653DSOptimizeOldFirmwareHeaderData header;
	USB5653DSOptimizePixelData pixel[HEIGHT_3DS][2];
};

struct PACKED ALIGNED(16) USB5653DSOptimizeInputColumnData3DTopRScreen {
	USB3DSOptimizeHeaderSoundData header_sound;
	USB5653DSOptimizePixelData pixel[HEIGHT_3DS];
};

struct PACKED ALIGNED(16) USB5653DSOptimizeInputColumnData3D {
	USB5653DSOptimizeInputColumnData3DTopRScreen top_r_screen_column;
	USB5653DSOptimizeInputColumnData bot_top_l_screens_column;
};

struct PACKED ALIGNED(16) USB8883DSOptimizeInputColumnData {
	USB3DSOptimizeHeaderSoundData header_sound;
	USB8883DSOptimizePixelData pixel[HEIGHT_3DS][2];
};

struct PACKED ALIGNED(16) USB8883DSOptimizeInputColumnData3D {
	USB3DSOptimizeHeaderSoundData header_sound;
	USB8883DSOptimizePixelData pixel[HEIGHT_3DS];
};

struct PACKED ALIGNED(16) USB8883DSOptimizeOldFirmwareInputColumnData {
	USB8883DSOptimizeOldFirmwareHeaderSoundData header_sound;
	USB8883DSOptimizePixelData pixel[HEIGHT_3DS][2];
};

struct PACKED ISNitroEmulatorVideoInputData {
	uint8_t screen_data[IN_VIDEO_SIZE_DS][3];
};

struct PACKED ALIGNED(2) ISTWLCaptureVideoInputData {
	USBOldDSPixelData screen_data[IN_VIDEO_SIZE_DS];
	uint8_t bit_6_rb_screen_data[(IN_VIDEO_SIZE_DS >> 3) * 2];
};

struct ALIGNED(16) PACKED FTD3_3DSCaptureReceived {
	RGB83DSVideoInputData video_in;
	uint16_t audio_data[N3DSXL_SAMPLES_IN];
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_FTD3XX_SIZE];
	uint8_t error_buffer[ERROR_DATA_BUFFER_FTD3XX_SIZE];
};

struct ALIGNED(16) PACKED FTD3_3DSCaptureReceived_3D {
	RGB83DSVideoInputData_3D video_in;
	uint16_t audio_data[N3DSXL_SAMPLES_IN];
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_FTD3XX_SIZE];
	uint8_t error_buffer[ERROR_DATA_BUFFER_FTD3XX_SIZE];
};

struct ALIGNED(16) PACKED USB3DSCaptureReceived {
	RGB83DSVideoInputData video_in;
	uint16_t audio_data[O3DS_SAMPLES_IN];
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_USB_SIZE];
};

struct ALIGNED(16) PACKED USB3DSCaptureReceived_3D {
	RGB83DSVideoInputData_3D video_in;
	uint16_t audio_data[O3DS_SAMPLES_IN];
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_USB_SIZE];
};

struct PACKED USBOldDSFrameInfo {
	uint8_t half_line_flags[(HEIGHT_DS >> 3) << 1];
	uint32_t frame;
	uint8_t valid;
	uint8_t unused[11];
};

struct ALIGNED(16) PACKED USBOldDSCaptureReceived {
	USBOldDSVideoInputData video_in;
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_USB_SIZE];
	USBOldDSFrameInfo frameinfo;
};

struct ALIGNED(16) PACKED FTD2OldDSCaptureReceived {
	USBOldDSVideoInputData video_in;
	uint16_t audio_data[DS_SAMPLES_IN];
	uint8_t unused_buffer[EXTRA_DATA_BUFFER_USB_SIZE];
};

struct ALIGNED(16) PACKED FTD2OldDSCaptureReceivedRaw {
	FTD2OldDSCaptureReceived data;
	uint8_t extra_ftd2_buffer[((sizeof(FTD2OldDSCaptureReceived) + MAX_PACKET_SIZE_FTD2 - 1) / MAX_PACKET_SIZE_FTD2) * FTD2_INTRA_PACKET_HEADER_SIZE];
};

struct ALIGNED(16) PACKED ISNitroCaptureReceived {
	ISNitroEmulatorVideoInputData video_in;
};

struct ALIGNED(16) PACKED CypressNisetroDSCaptureReceived {
	ISNitroEmulatorVideoInputData video_in;
};

struct ALIGNED(16) PACKED ISTWLCaptureVideoReceived {
	ISTWLCaptureVideoInputData video_in;
	uint32_t time;
	uint32_t frame;
	uint64_t random_data;
};

struct ALIGNED(16) PACKED ISTWLCaptureVideoInternalReceived {
	ISTWLCaptureVideoReceived real_video_in;
	uint8_t unknown[0x40000 - sizeof(ISTWLCaptureVideoReceived)];
};

struct ALIGNED(4) PACKED ISTWLCaptureSoundData {
	uint16_t data[TWL_CAPTURE_SAMPLES_IN];
};

struct ALIGNED(4) PACKED ISTWLCaptureAudioReceived {
	uint32_t time;
	ISTWLCaptureSoundData sound_data;
};

struct ALIGNED(16) PACKED ISTWLCaptureReceived {
	ISTWLCaptureVideoReceived video_capture_in;
	ISTWLCaptureAudioReceived audio_capture_in[TWL_CAPTURE_MAX_SAMPLES_CHUNK_NUM];
};

// Shared basic command structure for Kyoto MicroComputer Partner CTR Capture
struct ALIGNED(8) PACKED PartnerCTRCaptureCommand {
	uint16_t magic; // 0xE007, little endian, like everything...
	uint16_t command;
	uint32_t payload_size;
};

// Input data, maybe...? Sent before every screen... Usually empty?
struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeader0F {
	PartnerCTRCaptureCommand command;
	uint16_t unk[4];
};

// C4, C5 and C6. C4 is top screen. C5 second top screen. C6 bottom screen.
struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeaderCxScreen {
	PartnerCTRCaptureCommand command;
	uint8_t index_kind; // Reflects C4, C5 and C6, so either 04, 05 or 06
	uint16_t index;
	uint8_t unk;
	uint16_t unk2[2];
};

// Sound data, maybe...? Sent every 3 screens in 2D...
// Every 4 screens in 3D...?
struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeaderC7 {
	PartnerCTRCaptureCommand command;
	uint32_t index;
	uint32_t unk[5];
};

struct ALIGNED(16) PACKED USB5653DSOptimizeCaptureReceived {
	USB5653DSOptimizeInputColumnData columns_data[TOP_WIDTH_3DS];
	USB5653DSOptimizePixelData bottom_only_column[HEIGHT_3DS][2];
};

struct ALIGNED(16) PACKED USB5653DSOptimizeCaptureReceived_3D {
	USB5653DSOptimizeInputColumnData3D columns_data[TOP_WIDTH_3DS];
	USB5653DSOptimizeInputColumnData bottom_only_column;
};

struct ALIGNED(16) PACKED USB5653DSOptimizeOldFirmwareCaptureReceived {
	USB5653DSOptimizeOldFirmwareInputColumnData columns_data[TOP_WIDTH_3DS + 1];
};

struct ALIGNED(16) PACKED USB8883DSOptimizeCaptureReceived {
	USB8883DSOptimizeInputColumnData columns_data[TOP_WIDTH_3DS];
	USB8883DSOptimizePixelData bottom_only_column[HEIGHT_3DS][2];
};

struct ALIGNED(16) PACKED USB8883DSOptimizeCaptureReceived_3D {
	USB8883DSOptimizeInputColumnData3D columns_data[TOP_WIDTH_3DS][2];
	USB8883DSOptimizeInputColumnData3D bottom_only_column;
};

struct ALIGNED(16) PACKED USB8883DSOptimizeCaptureReceived_3D_Forced2DSingleScreen {
	USB8883DSOptimizeInputColumnData3D columns_data[TOP_WIDTH_3DS + 1];
};

struct ALIGNED(16) PACKED USB8883DSOptimizeOldFirmwareCaptureReceived {
	USB8883DSOptimizeOldFirmwareInputColumnData columns_data[TOP_WIDTH_3DS + 1];
};

struct ALIGNED(16) PACKED USB5653DSOptimizeCaptureReceivedExtraHeader {
	USB5653DSOptimizeInputColumnData columns_data[TOP_WIDTH_3DS + 1];
};

struct ALIGNED(16) PACKED USB8883DSOptimizeCaptureReceivedExtraHeader {
	USB8883DSOptimizeInputColumnData columns_data[TOP_WIDTH_3DS + 1];
};

#pragma pack(pop)

struct ALIGNED(16) FTD2OldDSCaptureReceivedNormalPlusRaw {
	FTD2OldDSCaptureReceived data;
	FTD2OldDSCaptureReceivedRaw raw_data;
};

union CaptureReceived {
	FTD3_3DSCaptureReceived ftd3_received;
	FTD3_3DSCaptureReceived_3D ftd3_received_3d;
	USB3DSCaptureReceived usb_received_3ds;
	USB3DSCaptureReceived_3D usb_received_3ds_3d;
	USBOldDSCaptureReceived usb_received_old_ds;
	FTD2OldDSCaptureReceived ftd2_received_old_ds;
	FTD2OldDSCaptureReceivedNormalPlusRaw ftd2_received_old_ds_normal_plus_raw;
	ISNitroCaptureReceived is_nitro_capture_received;
	ISTWLCaptureReceived is_twl_capture_received;
	CypressNisetroDSCaptureReceived cypress_nisetro_capture_received;
	USB5653DSOptimizeCaptureReceived cypress_optimize_received_565;
	USB5653DSOptimizeCaptureReceived_3D cypress_optimize_received_565_3d;
	USB5653DSOptimizeOldFirmwareCaptureReceived cypress_optimize_old_firmware_received_565;
	USB8883DSOptimizeCaptureReceived cypress_optimize_received_888;
	USB8883DSOptimizeCaptureReceived_3D cypress_optimize_received_888_3d;
	USB8883DSOptimizeCaptureReceived_3D_Forced2DSingleScreen cypress_optimize_received_888_3d_2d;
	USB8883DSOptimizeOldFirmwareCaptureReceived cypress_optimize_old_firmware_received_888;
	USB5653DSOptimizeCaptureReceivedExtraHeader cypress_optimize_received_565_extra_header;
	USB8883DSOptimizeCaptureReceivedExtraHeader cypress_optimize_received_888_extra_header;
};

struct CaptureDevice {
	// This is so messy... Wish there was a clearer way...
	CaptureDevice(std::string serial_number, std::string name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, uint64_t max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type) : serial_number(serial_number), name(name), long_name(name), cc_type(cc_type), video_data_type(video_data_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y) {}
	CaptureDevice(std::string serial_number, std::string name, std::string long_name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, uint64_t max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type, std::string path = "") : serial_number(serial_number), name(name), long_name(long_name), path(path), cc_type(cc_type), video_data_type(video_data_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y) {}
	CaptureDevice(std::string serial_number, std::string name, std::string long_name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, uint64_t max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type, int firmware_id, bool is_rgb_888, std::string path = "") : serial_number(serial_number), name(name), long_name(long_name), path(path), cc_type(cc_type), video_data_type(video_data_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), firmware_id(firmware_id), is_rgb_888(is_rgb_888) {}
	CaptureDevice(std::string serial_number, std::string name, std::string long_name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int width_3d, int height_3d, uint64_t max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int second_top_screen_x, int second_top_screen_y, int bot_screen_x, int bot_screen_y, bool is_second_top_screen_right, InputVideoDataType video_data_type, uint32_t usb_speed, std::string path = "") : serial_number(serial_number), name(name), long_name(long_name), path(path), cc_type(cc_type), video_data_type(video_data_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), width_3d(width_3d), height_3d(height_3d), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), second_top_screen_x(second_top_screen_x), second_top_screen_y(second_top_screen_y), is_second_top_screen_right(is_second_top_screen_right), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), usb_speed(usb_speed) {}
	CaptureDevice() : usb_speed(0) {}

	std::string serial_number = "";
	std::string name = "";
	std::string long_name = "";
	std::string path = "";
	std::string key = "";
	CaptureConnectionType cc_type = CAPTURE_CONN_USB;
	InputVideoDataType video_data_type = VIDEO_DATA_RGB;
	const void* descriptor = NULL;
	uint64_t device_id = 0; // Different from serial_number...
	bool is_3ds = false;
	bool has_3d = false;
	bool has_audio = false;
	int width = 0;
	int height = 0;
	int width_3d = 0;
	int height_3d = 0;
	uint64_t max_samples_in = 0;
	int base_rotation = 0;
	int top_screen_x = 0;
	int top_screen_y = 0;
	int second_top_screen_x = 0;
	int second_top_screen_y = 0;
	bool is_second_top_screen_right = false;
	int bot_screen_x = 0;
	int bot_screen_y = 0;
	int firmware_id = 0;
	uint32_t usb_speed = 0x200;
	bool is_rgb_888 = false;
	bool is_horizontally_flipped = false;
	bool is_vertically_flipped = false;
	bool continuous_3d_screens = true;
	AudioSampleRate sample_rate = SAMPLE_RATE_DS;
};

struct CaptureOptimizeOldFirmwareConfigCase {
	uint8_t low_screen_clock = 4; // 0...15
	uint8_t top_screen_clock = 5; // 0...7
	uint8_t top_screen_data = 3; // 0...7
	uint8_t top_screen_sync = 2; // 0...5
}; // Covered by sanitize_capture_optimize_old_fw_config_case

struct CaptureOptimizeOldFirmwareConfig {
	CaptureOptimizeOldFirmwareConfigCase mode_2d_only;
	CaptureOptimizeOldFirmwareConfigCase mode_regular;
};

struct CaptureStatusOptimize {
	// Capture-device specific data...
	// Optimize devices
	bool request_low_bw_format = true;
	bool request_low_bw_format_old_2ds = false;
	bool key_updated = false;
	CaptureOptimizeOldFirmwareConfig old_fw_config_full_bw_format = {};
	CaptureOptimizeOldFirmwareConfig old_fw_config_low_bw_format = {};
};

struct CaptureStatusIS {
	// Capture-device specific data...
	// IS devices
	volatile int curr_delay = 0;
	volatile bool reset_hardware = false;
	CaptureScreensType capture_type;
	CaptureSpeedsType capture_speed;
	int battery_percentage;
	bool ac_adapter_connected;
};

struct CaptureStatusPartnerCTR {
	// Capture-device specific data...
	// Partner CTR Capture
	volatile bool reset_hardware = false;
	int battery_percentage;
	bool ac_adapter_connected;
	bool ac_adapter_charging;
};

struct CaptureStatusDeviceSpecific {
	// Capture-device specific data...
	CaptureStatusOptimize optimize_status;
	CaptureStatusIS is_status;
	CaptureStatusPartnerCTR partner_ctr_status;
};

struct CaptureStatus {
	CaptureDevice device;
	std::string graphical_error_text;
	std::string detailed_error_text;
	bool new_error_text = false;
	int title_check_id = 0;
	volatile int cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM;
	volatile bool connected = false;
	volatile bool running = true;
	volatile bool close_success = true;
	bool requested_3d = false;
	CaptureStatusDeviceSpecific device_specific_status;
	// Needed for possible compatibility issues
	bool devices_allowed_scan[CC_POSSIBLE_DEVICES_END];
	ConsumerMutex video_wait;
	ConsumerMutex audio_wait;
};

struct CaptureDataSingleBuffer {
	CaptureScreensType capture_type;
	uint64_t read;
	size_t unused_offset;
	CaptureReceived capture_buf;
	double time_in_buf;
	uint32_t inner_index;
	bool is_3d;
	bool should_be_3d;
	InputVideoDataType buffer_video_data_type;
	std::chrono::time_point<std::chrono::high_resolution_clock> last_write_time;
};

class CaptureDataBuffers {
public:
	CaptureDataBuffers();
	CaptureDataSingleBuffer* GetReaderBuffer(CaptureReaderType reader_type);
	void ReleaseReaderBuffer(CaptureReaderType reader_type);
	void WriteToBuffer(CaptureReceived* buffer, uint64_t read, double time_in_buf, CaptureDevice* device, CaptureScreensType capture_type, size_t offset, int index, bool is_3d = false, bool should_be_3d = false);
	void WriteToBuffer(CaptureReceived* buffer, uint64_t read, double time_in_buf, CaptureDevice* device, CaptureScreensType capture_type, int index, bool is_3d = false, bool should_be_3d = false);
	void WriteToBuffer(CaptureReceived* buffer, uint64_t read, double time_in_buf, CaptureDevice* device, size_t offset, int index, bool is_3d = false, bool should_be_3d = false);
	void WriteToBuffer(CaptureReceived* buffer, uint64_t read, double time_in_buf, CaptureDevice* device, int index, bool is_3d = false, bool should_be_3d = false);
	CaptureDataSingleBuffer* GetWriterBuffer(int index = 0);
	void ReleaseWriterBuffer(int index = 0, bool update_last_curr_in = true);
private:
	std::mutex access_mutex;
	int last_curr_in;
	int curr_writer_pos[NUM_CONCURRENT_DATA_BUFFER_WRITERS];
	int curr_reader_pos[NUM_CONCURRENT_DATA_BUFFER_READERS];
	bool is_being_written_to[NUM_CONCURRENT_DATA_BUFFERS];
	int num_readers[NUM_CONCURRENT_DATA_BUFFERS];
	bool has_read_data[NUM_CONCURRENT_DATA_BUFFERS][NUM_CONCURRENT_DATA_BUFFER_READERS];
	CaptureDataSingleBuffer buffers[NUM_CONCURRENT_DATA_BUFFERS];
};

struct CaptureData {
	void* handle;
	CaptureDataBuffers data_buffers;
	CaptureStatus status;
};
#endif
