#ifndef VIS_HEADER
#define VIS_HEADER
#include <stdint.h>
#include <stdbool.h>

#ifndef VIS_MemoryZero
    #include <string.h>
    #define VIS_MemoryZero(p, s) memset(p, 0, s)
#endif

#ifndef VIS_MemoryCopy
    #include <string.h>
    #define VIS_MemoryCopy(dst, src, size) memcpy(dst, src, size)
#endif

#ifndef VIS_ASSERT
    #include <assert.h>
    #define VIS_ASSERT assert
#endif

#define VIS_BUFFER_SIZE (1024 * 1024 * 32)
#define VIS_BUFFER_NAME L"Hello, inter process communication!"
#define VIS_DATA_VERSION 3
#define VIS_ROLLING_COMMAND_COUNT 8
#ifdef __cplusplus
    #define VIS_FUNCTION extern "C"
#else
    #define VIS_FUNCTION
#endif

typedef struct VIS_Header VIS_Header;
struct VIS_Header { // @on_update: update VIS__Clear!
    uint32_t version;
    uint32_t written;
    uint32_t strings_written; //@debug_info

    uint32_t synchronization_counter_write;
    uint32_t synchronization_counter_read;
    uint8_t  rolling_commands[VIS_ROLLING_COMMAND_COUNT];

    // @cache
    // For caching, we keep track of names of the
    // plots. We don't want to send a ton of wasteful string data
    uint32_t first_plot;
    uint32_t last_plot;
    uint32_t current_plot;
};

#ifdef __cplusplus
extern "C" VIS_Header *VIS_HeaderPointer;
#else
extern VIS_Header *VIS_HeaderPointer;
#endif

enum {
    VIS_PlotKind_Line,
    VIS_PlotKind_Scatter,
    VIS_PlotKind_Histogram,
    VIS_PlotKind_Bar,
};

typedef uint16_t VIS_Kind;
enum {
    VIS_Kind_Null,
    VIS_Kind_Plot,
    VIS_Kind_Series,
    VIS_Kind_Point,
    VIS_Kind_NamedPoint,
    VIS_Kind_Measure,
};

typedef struct VIS_String VIS_String;
struct VIS_String {
    int32_t len;
    char    str[];
};

// @cache
// For caching, we keep track of names of the
// plots. We don't want to send a ton of wasteful string data
typedef struct VIS_PlotData VIS_PlotData;
struct VIS_PlotData {
    uint32_t plot_kind;
    uint32_t xlabel;
    uint32_t ylabel;
    uint32_t title;

    uint64_t xlabel_hash;
    uint64_t ylabel_hash;
    uint64_t title_hash;

    int32_t  histogram_bin_count;
    uint32_t first_series;
    uint32_t last_series;
    uint32_t current_series;
};

typedef struct VIS_SeriesData VIS_SeriesData;
struct VIS_SeriesData {
    uint32_t first_event;
    uint32_t last_event;
    uint32_t name;
    uint64_t name_hash;
};

#define VIS_BASIC_NODE_SIZE (sizeof(uint64_t)) // @todo: maybe remove kind and id from here
typedef struct VIS_Node VIS_Node;
struct VIS_Node {
    uint32_t next;
    VIS_Kind kind;
    uint16_t id; // @todo: maybe remove kind and id from here

    union {
        double         point[2];
        VIS_PlotData   plot;
        VIS_SeriesData series;
        struct {
            double   point[2];
            uint64_t string_hash;
            uint32_t string;
        } named_value;
    };
};

typedef struct VIS_Current VIS_Current;
struct VIS_Current {
    VIS_Node *plot;
    VIS_Node *series;
};

enum {
    VIS_COMMAND_NONE,
    VIS_COMMAND_CONNECTED,
    VIS_COMMAND_CLEAR,
};

#define VISH_Cast8(base) ((uint8_t *)(base))
#define VISH_Get(base, offset) ((offset) == 0 ? 0 : (VISH_Cast8(base) + (offset)))
#define VISH_GetStruct(base, offset, T) ((T *)(VISH_Get(base, offset)))
#define VISH_CalcOffset(base, pointer) ((uint32_t)((uint8_t *)(pointer)-VISH_Cast8(base)))

#define VIS_Get(offset) VISH_Get(VIS_HeaderPointer, offset)
#define VIS_CalcOffset(pointer) VISH_CalcOffset(VIS_HeaderPointer, pointer)
#define VIS_GetNode(offset) VISH_GetStruct(VIS_HeaderPointer, offset, VIS_Node)

VIS_FUNCTION void    VIS_WriteCommand(uint8_t command);
VIS_FUNCTION uint8_t VIS_ReadCommand();
VIS_FUNCTION void    VIS_IncrementReadCommand();
VIS_FUNCTION void    VIS_Synchronize();
VIS_FUNCTION void    VIS_Connect();
VIS_FUNCTION void    VIS_CreateSharedMemory();

static inline int32_t VIS_GetStringLength(char *string) {
    int32_t len = 0;
    while (*string++ != 0)
        len++;
    return len;
}

static inline VIS_Header *VIS_TryConnecting() {
    if (!VIS_HeaderPointer) VIS_Connect();
    return VIS_HeaderPointer;
}

static inline uint64_t VIS_HashBytes(void *data, int32_t size) {
    uint8_t *data8 = (uint8_t *)data;
    uint64_t hash  = (uint64_t)14695981039346656037ULL;
    for (int32_t i = 0; i < size; i++) {
        hash = hash ^ (uint64_t)(data8[i]);
        hash = hash * (uint64_t)1099511628211ULL;
    }
    if (hash == 0) hash += 1;
    return hash;
}

#define VIS_PushStruct(T) ((T *)VIS_Push(sizeof(T)))
#define VIS_PushArray(T, c) ((T *)VIS_Push((c) * sizeof(T)))
static inline void *VIS_Push(uint32_t size) {
    if (VIS_HeaderPointer->written + size <= VIS_BUFFER_SIZE) {
        void *result = VIS_Get(VIS_HeaderPointer->written);
        VIS_HeaderPointer->written += size;
        VIS_MemoryZero(result, size);
        return result;
    }
    return 0;
}

static inline VIS_Node *VIS_PushBasicNode(VIS_Kind kind) {
    VIS_Node *result = (VIS_Node *)VIS_Push(VIS_BASIC_NODE_SIZE);
    if (result) result->kind = kind;
    return result;
}

#define VIS_SLL_QUEUE_ADD(first, last, n)             \
    do {                                              \
        (n)->next = 0;                                \
        if ((last) == 0) {                            \
            (last)  = VIS_CalcOffset((n));            \
            (first) = (last);                         \
        } else {                                      \
            VIS_Node *the_last = VIS_GetNode((last)); \
            uint32_t  the_next = VIS_CalcOffset((n)); \
            the_last->next     = the_next;            \
            (last)             = the_next;            \
        }                                             \
    } while (0)

static inline VIS_Current VIS_GetCurrent() {
    VIS_Current current;
    current.plot   = VIS_GetNode(VIS_HeaderPointer->current_plot);
    current.series = VIS_GetNode(current.plot->plot.current_series);
    return current;
}

static inline void VIS_AddNode(VIS_Node *event) {
    if (event == 0) return;
    VIS_Current current = VIS_GetCurrent();
    VIS_SLL_QUEUE_ADD(current.series->series.first_event, current.series->series.last_event, event);
}

static inline uint32_t VIS_PushString(char *in_string, int32_t in_len) {
    int32_t *length = VIS_PushStruct(int32_t);
    char    *string = VIS_PushArray(char, in_len + 1);
    if (length && string) {
        length[0] = in_len;
        VIS_MemoryCopy(string, in_string, in_len + 1);
        VIS_HeaderPointer->strings_written += 1;
    }
    return VIS_CalcOffset(length);
}

static inline void VIS_PushPoint(double x, double y) {
    double *value = VIS_PushArray(double, 2);
    if (value) {
        value[0] = x;
        value[1] = y;
    }
}

static inline void VIS_Point(double x, double y) {
    if (!VIS_TryConnecting()) return;
    VIS_Node *event = VIS_PushBasicNode(VIS_Kind_Point);
    VIS_PushPoint(x, y);
    VIS_AddNode(event);
}

static inline void VIS_Value(double y) {
    VIS_Point(0, y);
}

static inline void VIS_NamedPoint(char *str, double x, double y) {
    if (!VIS_TryConnecting()) return;
    VIS_ASSERT(str && "Passed in string is null");

    int32_t     len     = VIS_GetStringLength(str);
    VIS_Current current = VIS_GetCurrent();
    uint64_t    hash    = VIS_HashBytes(str, len);

    // Try updating the node
    for (VIS_Node *it = VIS_GetNode(current.series->series.first_event); it; it = VIS_GetNode(it->next)) {
        if (it->kind == VIS_Kind_NamedPoint && it->named_value.string_hash == hash) {
            it->named_value.point[0] = x;
            it->named_value.point[1] = y;
            return;
        }
    }

    // Node not found, new node
    VIS_Node *event = VIS_PushBasicNode(VIS_Kind_NamedPoint);
    VIS_PushPoint(x, y);

    uint64_t *hash_space   = VIS_PushStruct(uint64_t);
    uint32_t *string_space = VIS_PushStruct(uint32_t);
    if (hash_space) *hash_space = hash;
    if (string_space) *string_space = VIS_PushString(str, len);

    VIS_AddNode(event);
}

static inline void VIS_NamedValue(char *str, double y) {
    VIS_ASSERT(str && "Passed in string is null");
    VIS_NamedPoint(str, 0, y);
}

static inline void VIS_Title(char *title) {
    if (!VIS_TryConnecting()) return;
    VIS_ASSERT(title && "Passed in string is null");
    int32_t   len  = VIS_GetStringLength(title);
    VIS_Node *id   = VIS_GetNode(VIS_HeaderPointer->current_plot);
    uint64_t  hash = VIS_HashBytes(title, len);
    if (id->plot.title_hash == 0 || id->plot.title_hash != hash) {
        id->plot.title      = VIS_PushString(title, len);
        id->plot.title_hash = hash;
    }
}

static inline void VIS_YLabel(char *ylabel) {
    if (!VIS_TryConnecting()) return;
    VIS_ASSERT(ylabel && "Passed in string is null");
    int32_t   len  = VIS_GetStringLength(ylabel);
    VIS_Node *id   = VIS_GetNode(VIS_HeaderPointer->current_plot);
    uint64_t  hash = VIS_HashBytes(ylabel, len);
    if (id->plot.ylabel_hash != hash) {
        id->plot.ylabel      = VIS_PushString(ylabel, len);
        id->plot.ylabel_hash = hash;
    }
}

static inline void VIS_XLabel(char *xlabel) {
    if (!VIS_TryConnecting()) return;
    VIS_ASSERT(xlabel && "Passed in string is null");
    int32_t   len  = VIS_GetStringLength(xlabel);
    VIS_Node *id   = VIS_GetNode(VIS_HeaderPointer->current_plot);
    uint64_t  hash = VIS_HashBytes(xlabel, len);
    if (id->plot.xlabel_hash != hash) {
        id->plot.xlabel      = VIS_PushString(xlabel, len);
        id->plot.xlabel_hash = hash;
    }
}

static inline VIS_Node *VIS_AddGetSeries(VIS_Node *plot, uint16_t id) {
    for (VIS_Node *it = VIS_GetNode(plot->plot.first_series); it; it = VIS_GetNode(it->next)) {
        if (it->id == id) return it;
    }
    VIS_Node *series = VIS_PushStruct(VIS_Node);
    series->id       = id;
    VIS_SLL_QUEUE_ADD(plot->plot.first_series, plot->plot.last_series, series);
    return series;
}

static inline VIS_Node *VIS_AddGetPlot(uint16_t id) {
    for (VIS_Node *it = VIS_GetNode(VIS_HeaderPointer->first_plot); it; it = VIS_GetNode(it->next)) {
        if (it->id == id) return it;
    }
    VIS_Node *it                 = VIS_PushStruct(VIS_Node);
    it->id                       = id;
    it->plot.current_series      = VIS_CalcOffset(VIS_AddGetSeries(it, 0));
    it->plot.histogram_bin_count = 10;

    VIS_SLL_QUEUE_ADD(VIS_HeaderPointer->first_plot, VIS_HeaderPointer->last_plot, it);
    if (VIS_HeaderPointer->current_plot == 0) VIS_HeaderPointer->current_plot = VIS_CalcOffset(it);
    return it;
}

static inline void VIS_Series(uint16_t id) {
    if (!VIS_TryConnecting()) return;
    VIS_Node *plot            = VIS_GetNode(VIS_HeaderPointer->current_plot);
    VIS_Node *series          = VIS_AddGetSeries(plot, id);
    plot->plot.current_series = VIS_CalcOffset(series);
}

static inline void VIS_PlotKind(uint16_t kind_id) {
    if (!VIS_TryConnecting()) return;
    VIS_Node *id       = VIS_GetNode(VIS_HeaderPointer->current_plot);
    id->plot.plot_kind = kind_id;
}

static inline void VIS_Plot(uint16_t id) {
    if (!VIS_TryConnecting()) return;
    VIS_Node *plot                  = VIS_AddGetPlot(id);
    VIS_HeaderPointer->current_plot = VIS_CalcOffset(plot);
}

static inline void VIS_SeriesName(char *name) {
    if (!VIS_TryConnecting()) return;
    VIS_ASSERT(name && "Passed in string is null");

    int32_t     len  = VIS_GetStringLength(name);
    VIS_Current c    = VIS_GetCurrent();
    uint64_t    hash = VIS_HashBytes(name, len);

    if (c.series->series.name_hash != hash) {
        c.series->series.name      = VIS_PushString(name, len);
        c.series->series.name_hash = hash;
    }
}

static inline void VIS_Clear() {
    if (!VIS_TryConnecting()) return;
    VIS_WriteCommand(VIS_COMMAND_CLEAR);
    VIS_Synchronize();
}

static inline void VIS__Clear() {
    VIS_HeaderPointer->strings_written = 0;
    VIS_HeaderPointer->first_plot      = 0;
    VIS_HeaderPointer->last_plot       = 0;
    VIS_HeaderPointer->current_plot    = 0;
    VIS_HeaderPointer->written         = sizeof(VIS_Header);
    VIS_HeaderPointer->version         = VIS_DATA_VERSION;
    VIS_AddGetPlot(0);
}
#endif // VIS_HEADER

#ifdef VIS_IMPLEMENTATION
#ifndef NOMINMAX
    #define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
    #define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

VIS_Header *VIS_HeaderPointer;

VIS_FUNCTION void VIS_WriteCommand(uint8_t command) {
    uint32_t i = VIS_HeaderPointer->synchronization_counter_write % VIS_ROLLING_COMMAND_COUNT;
    VIS_ASSERT(i >= 0 && i < VIS_ROLLING_COMMAND_COUNT);
    VIS_HeaderPointer->rolling_commands[i] = command;
    InterlockedIncrement(&VIS_HeaderPointer->synchronization_counter_write);
}

VIS_FUNCTION uint8_t VIS_ReadCommand() {
    if (!VIS_HeaderPointer) return 0;
    if (VIS_HeaderPointer->synchronization_counter_write == VIS_HeaderPointer->synchronization_counter_read) return 0;
    uint32_t i = VIS_HeaderPointer->synchronization_counter_read % VIS_ROLLING_COMMAND_COUNT;
    VIS_ASSERT(i >= 0 && i < VIS_ROLLING_COMMAND_COUNT);
    uint8_t result = VIS_HeaderPointer->rolling_commands[i];
    return result;
}

VIS_FUNCTION void VIS_IncrementReadCommand() {
    InterlockedIncrement(&VIS_HeaderPointer->synchronization_counter_read);
}

VIS_FUNCTION void VIS_Synchronize() {
    while (VIS_HeaderPointer->synchronization_counter_write != VIS_HeaderPointer->synchronization_counter_read) {
    }
}

VIS_FUNCTION void VIS_Connect() {
    HANDLE handle = OpenFileMappingW(
        FILE_MAP_ALL_ACCESS, // read/write access
        FALSE,               // do not inherit the name
        VIS_BUFFER_NAME);

    if (handle == NULL) {
        // VIS_ASSERT(!"Failed to open shared memory. Windows error:%d", GetLastError());
        return;
    }

    VIS_HeaderPointer = (VIS_Header *)MapViewOfFile(handle,              // handle to map object
                                                    FILE_MAP_ALL_ACCESS, // read/write permission
                                                    0,
                                                    0,
                                                    VIS_BUFFER_SIZE);

    if (VIS_HeaderPointer == NULL) {
        // VIS_ASSERT(!"Failed to map shared memory to a buffer");
        CloseHandle(handle);
        return;
    }

    VIS_WriteCommand(VIS_COMMAND_CONNECTED);
    VIS_Synchronize();
}

VIS_FUNCTION void VIS_CreateSharedMemory() {
    HANDLE VIS_SharedMemoryHandle = CreateFileMappingW(
        INVALID_HANDLE_VALUE, // use paging file
        NULL,                 // default security
        PAGE_READWRITE,       // read/write access
        0,                    // maximum object size (high-order DWORD)
        VIS_BUFFER_SIZE,      // maximum object size (low-order DWORD)
        VIS_BUFFER_NAME);     // name of mapping object

    if (VIS_SharedMemoryHandle == NULL) {
        VIS_ASSERT(!"Failed to create shared memory.");
        return;
    }

    VIS_HeaderPointer = (VIS_Header *)MapViewOfFile(VIS_SharedMemoryHandle, // handle to map object
                                                    FILE_MAP_ALL_ACCESS,    // read/write permission
                                                    0, 0, VIS_BUFFER_SIZE);

    if (VIS_HeaderPointer == NULL) {
        VIS_ASSERT(!"Failed to create named shader memory buffer.");
        CloseHandle(VIS_SharedMemoryHandle);
        return;
    }

    VIS__Clear();
}

#endif // VIS_IMPLEMENTATION