//
// Created by Lennart Walger on 2019-08-27.
//
#ifndef AEDAT3TYPES_HPP
#define AEDAT3TYPES_HPP

#include <inttypes.h>
#include <iomanip>
#include <regex>
#include <sstream>
#include <stdint.h>

#if defined(__GNUC__) || defined(__clang__)
#	define PACKED_STRUCT(STRUCT_DECLARATION) STRUCT_DECLARATION __attribute__((__packed__))
#elif defined(_MSC_VER)
#	define PACKED_STRUCT(STRUCT_DECLARATION) __pragma(pack(push, 1)) STRUCT_DECLARATION __pragma(pack(pop))
#else
#	define PACKED_STRUCT(STRUCT_DECLARATION) STRUCT_DECLARATION
#	warning "Unable to ensure structures are properly packed."
#endif

/**
 * Cast argument to uint8_t (8bit unsigned integer).
 */
#define U8T(X) ((uint8_t) (X))
/**
 * Cast argument to uint16_t (16bit unsigned integer).
 */
#define U16T(X) ((uint16_t) (X))
/**
 * Cast argument to uint32_t (32bit unsigned integer).
 */
#define U32T(X) ((uint32_t) (X))
/**
 * Cast argument to uint64_t (64bit unsigned integer).
 */
#define U64T(X) ((uint64_t) (X))
/**
 * Cast argument to int8_t (8bit signed integer).
 */
#define I8T(X) ((int8_t) (X))
/**
 * Cast argument to int16_t (16bit signed integer).
 */
#define I16T(X) ((int16_t) (X))
/**
 * Cast argument to int32_t (32bit signed integer).
 */
#define I32T(X) ((int32_t) (X))
/**
 * Cast argument to int64_t (64bit signed integer).
 */
#define I64T(X) ((int64_t) (X))

static const std::regex offsetExtract{"^\\s*\\(TZ([0-9+-:Z]+)\\)$"};

static inline int64_t parseTimezonePortable(const char *tz) {
	if (tz == nullptr) {
		return 0;
	}

	// Extract time-zone info in a cross-platform manner.
	std::cmatch matches;

	if (std::regex_search(tz, matches, offsetExtract) && (matches.size() == 2)) {
		auto tzString = matches[1].str();

		// Recognize same cases as glibc strptime(), which would have generated
		// them with its strftime(), so should be a complete list.
		if (tzString == "Z") {
			return 0; // Z alone is equal to +0000.
		}

		// Then we have +/- followed by two hour digits, an optional colon, and optional two minute digits.
		const auto sign = tzString[0];
		if ((sign != '+') && (sign != '-')) {
			return 0;
		}

		tzString.erase(0, 1);

		// Take out colon if exists.
		if (tzString[2] == ':') {
			tzString.erase(2, 1);
		}

		int64_t offset = 0;

		// Now we're left either with 2 or 4 digits.
		if (tzString.size() == 2) {
			// Only hours.
			const auto hours = std::stoi(tzString.substr(0, 2));

			offset = (hours * 3600);
		}
		else if (tzString.size() == 4) {
			// Hours and Minutes.
			const auto hours   = std::stoi(tzString.substr(0, 2));
			const auto minutes = std::stoi(tzString.substr(2));

			offset = (hours * 3600) + (minutes * 60);
		}

		// Apply sign.
		if (sign == '-') {
			offset = -offset;
		}

		return offset;
	}

	return 0;
}

#ifdef OS_WINDOWS

// Define the simplest substitute to strptime() on Windows.
// It will not be able to parse TZ%z, so no time-zone adjustment.
static inline const char *strptime(const char *s, const char *f, struct tm *tm) {
	// Isn't the C++ standard lib nice? std::get_time is defined such that its
	// format parameters are the exact same as strptime. Of course, we have to
	// create a string stream first, and imbue it with the current C locale, and
	// we also have to make sure we return the right things if it fails, or
	// if it succeeds, but this is still far simpler an implementation than any
	// of the versions in any of the C standard libraries.
	std::istringstream input(s);
	input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
	input >> std::get_time(tm, f);
	if (input.fail()) {
		return nullptr;
	}
	return (s + input.tellg());
}

// On Windows, mkgmtime does the same as timegm.
#	define timegm _mkgmtime

#endif

#define MAX_HEADER_LINE_SIZE 1024

// Used for all low-level structs.

// Common Packet Header struct

PACKED_STRUCT(struct Aedat3PacketHeader {
	/// Numerical type ID, unique to each event type (see 'enum caer_default_event_types').
	int16_t eventType;
	/// Numerical source ID, unique inside a process, identifies who generated the events.
	int16_t eventSource;
	/// Size of one event in bytes.
	int32_t eventSize;
	/// Offset from the start of an event, in bytes, at which the main 32 bit time-stamp can be found.
	int32_t eventTSOffset;
	/// Overflow counter for the standard 32bit event time-stamp. Used to generate the 64 bit time-stamp.
	int32_t eventTSOverflow;
	/// Maximum number of events this packet can store.
	int32_t eventCapacity;
	/// Total number of events present in this packet (valid + invalid).
	int32_t eventNumber;
	/// Total number of valid events present in this packet.
	int32_t eventValid;
});

/**
 * Type for pointer to EventPacket header data structure.
 */
typedef struct Aedat3PacketHeader *Aedat3PacketHeader_;
typedef const struct Aedat3PacketHeader *Aedat3PacketHeaderConst_;

// Polarity Event

PACKED_STRUCT(struct PolarityEvent {
	/// Event data. First because of valid mark.
	uint32_t data;
	/// Event timestamp.
	int32_t timestamp;
});

/**
 * Type for pointer to polarity event data structure.
 */
typedef struct PolarityEvent *PolarityEvent_;
typedef const struct PolarityEvent *PolarityEventConst_;

// Frame Packet struct

/**
 * List of all frame event color channel identifiers.
 * Used to interpret the frame event color channel field.
 */
enum class frameEventColorChannels {
	GRAYSCALE = 1, //!< Grayscale, one channel only.
	RGB       = 3, //!< Red Green Blue, 3 color channels.
	RGBA      = 4, //!< Red Green Blue Alpha, 3 color channels plus transparency.
};

/**
 * List of all frame event color filter identifiers.
 * Used to interpret the frame event color filter field.
 */
enum class frameEventColorFilter {
	MONO = 0, //!< No color filter present, all light passes.
	RGBG = 1, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 1.
	GRGB = 2, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 2.
	GBGR = 3, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 3.
	BGRG = 4, //!< Standard Bayer color filter, 1 red 2 green 1 blue. Variation 4.
	RGBW = 5, //!< Modified Bayer color filter, with white (pass all light) instead of extra green. Variation 1.
	GRWB = 6, //!< Modified Bayer color filter, with white (pass all light) instead of extra green. Variation 2.
	WBGR = 7, //!< Modified Bayer color filter, with white (pass all light) instead of extra green. Variation 3.
	BWRG = 8, //!< Modified Bayer color filter, with white (pass all light) instead of extra green. Variation 4.
};
PACKED_STRUCT(struct FrameEvent {
	/// Event information (ROI region, color channels, color filter). First because of valid mark.
	uint32_t info;
	/// Start of Frame (SOF) timestamp.
	int32_t ts_startframe;
	/// End of Frame (EOF) timestamp.
	int32_t ts_endframe;
	/// Start of Exposure (SOE) timestamp.
	int32_t ts_startexposure;
	/// End of Exposure (EOE) timestamp.
	int32_t ts_endexposure;
	/// X axis length in pixels.
	int32_t lengthX;
	/// Y axis length in pixels.
	int32_t lengthY;
	/// X axis position (upper left offset) in pixels.
	int32_t positionX;
	/// Y axis position (upper left offset) in pixels.
	int32_t positionY;
	/// Pixel array, 16 bit unsigned integers, normalized to 16 bit depth.
	/// The pixel array is laid out row by row (increasing X axis), going
	/// from top to bottom (increasing Y axis). This prevents simple copy!
	uint16_t pixels[1]; // size 1 here for C++ compatibility.
});
/**
 * Type for pointer to frame event data structure.
 */
typedef struct FrameEvent *FrameEvent_;
typedef const struct FrameEvent *FrameEventConst_;

// IMU6 Packet struct

PACKED_STRUCT(struct IMU6Event {
	/// Event information. First because of valid mark.
	uint32_t info;
	/// Event timestamp.
	int32_t timestamp;
	/// Acceleration in the X axis, measured in g (9.81m/s²).
	float accel_x;
	/// Acceleration in the Y axis, measured in g (9.81m/s²).
	float accel_y;
	/// Acceleration in the Z axis, measured in g (9.81m/s²).
	float accel_z;
	/// Rotation in the X axis, measured in °/s.
	float gyro_x;
	/// Rotation in the Y axis, measured in °/s.
	float gyro_y;
	/// Rotation in the Z axis, measured in °/s.
	float gyro_z;
	/// Temperature, measured in °C.
	float temp;
});

/**
 * Type for pointer to IMU 6-axes event data structure.
 */
typedef struct IMU6Event *IMU6Event_;
typedef const struct IMU6Event *IMU6EventConst_;

// special event struct

enum class SpecialEventTypes {
	TIMESTAMP_WRAP              = 0, //!< A 32 bit timestamp wrap occurred.
	TIMESTAMP_RESET             = 1, //!< A timestamp reset occurred.
	EXTERNAL_INPUT_RISING_EDGE  = 2, //!< A rising edge was detected (External Input module on device).
	EXTERNAL_INPUT_FALLING_EDGE = 3, //!< A falling edge was detected (External Input module on device).
	EXTERNAL_INPUT_PULSE        = 4, //!< A pulse was detected (External Input module on device).
	DVS_ROW_ONLY = 5, //!< A DVS row-only event was detected (a row address without any following column addresses).
	EXTERNAL_INPUT1_RISING_EDGE     = 6,  //!< A rising edge was detected (External Input 1 module on device).
	EXTERNAL_INPUT1_FALLING_EDGE    = 7,  //!< A falling edge was detected (External Input 1 module on device).
	EXTERNAL_INPUT1_PULSE           = 8,  //!< A pulse was detected (External Input 1 module on device).
	EXTERNAL_INPUT2_RISING_EDGE     = 9,  //!< A rising edge was detected (External Input 2 module on device).
	EXTERNAL_INPUT2_FALLING_EDGE    = 10, //!< A falling edge was detected (External Input 2 module on device).
	EXTERNAL_INPUT2_PULSE           = 11, //!< A pulse was detected (External Input 2 module on device).
	EXTERNAL_GENERATOR_RISING_EDGE  = 12, //!< A rising edge was generated (External Input Generator module on device).
	EXTERNAL_GENERATOR_FALLING_EDGE = 13, //!< A falling edge was generated (External Input Generator module on device).
	APS_FRAME_START                 = 14, //!< An APS frame capture has started (Frame Event will follow).
	APS_FRAME_END                   = 15, //!< An APS frame capture has completed (Frame Event is alongside).
	APS_EXPOSURE_START              = 16, //!< An APS frame exposure has started (Frame Event will follow).
	APS_EXPOSURE_END                = 17, //!< An APS frame exposure has completed (Frame Event will follow).
};

PACKED_STRUCT(struct SpecialEvent {
	/// Event data. First because of valid mark.
	uint32_t data;
	/// Event timestamp.
	int32_t timestamp;
});
/**
 * Type for pointer to special event data structure.
 */
typedef struct SpecialEvent *SpecialEvent_;
typedef const struct SpecialEvent *SpecialEventConst_;

#endif // AEDAT3TYPES_HPP
