#include <GLFW/glfw3.h>
#include <alsa/asoundlib.h>
#ifdef VIDEO_IN
#include <glad/egl.h>
#include <linux/videodev2.h>
#endif /* VIDEO_IN */
#include <glad/gl.h>
#include <hashmap.h>
#include <linmath.h>
#include <stdbool.h>
#include <time.h>

#include "config.h"

#ifndef TYPES_H
#define TYPES_H

#define ARRAY(X, Y)                                                            \
  struct X {                                                                   \
    Y values[ARRAY_SIZE];                                                      \
    unsigned int length;                                                       \
  } X

typedef ARRAY(UintArray, unsigned int);
typedef ARRAY(Vec3Array, vec3);
typedef ARRAY(GLuintArray, GLuint);

typedef struct StringArray {
  char values[ARRAY_SIZE][STR_LEN];
  unsigned int length;
} StringArray;

// args.c

typedef struct Parameters {
  char project_path[STR_LEN];
  char config_file[STR_LEN];
  bool hot_reload;
  bool output;
  unsigned int output_screen;
  bool monitor;
  unsigned int monitor_screen;
  bool windowed;
  float base_tempo;
  bool demo;
  bool auto_random;
  unsigned int auto_random_cycle;
#ifdef VIDEO_IN
  StringArray video_in;
  unsigned int video_buffers;
  unsigned int video_size;
  bool video_reconnect;
#endif /* VIDEO_IN */
  unsigned int internal_size;
  bool load_state;
  bool save_state;
  bool midi_reconnect;
  bool trace_midi;
  bool trace_fps;
} Parameters;

// file.c

typedef struct File {
  char path[STR_LEN];
  char *content;
  unsigned int length;
  bool error;
  time_t last_write;
} File;

typedef ARRAY(FileArray, File);

// shaders.c

typedef struct Vertex {
  vec2 pos;
} Vertex;

typedef struct ShaderProgram {
  bool error;

  vec2 last_resolution;

  GLuint vertex_shader;

  GLuint vertex_buffer;
  GLuint vertex_array[2];

  unsigned int tex_count;
  unsigned int tex_map[ARRAY_SIZE];
  GLuint textures[ARRAY_SIZE];

  unsigned int frag_count;
  unsigned int frag_output_index;
  unsigned int frag_monitor_index;

  GLuint programs[ARRAY_SIZE];

  GLuint frame_buffers[ARRAY_SIZE];
  GLuint fragment_shaders[ARRAY_SIZE];

  GLuint itime_locations[ARRAY_SIZE];
  GLuint itempo_locations[ARRAY_SIZE];
  GLuint ibeats_locations[ARRAY_SIZE];
  GLuint ifps_locations[ARRAY_SIZE];
  GLuint ires_locations[ARRAY_SIZE];
  GLuint itexres_locations[ARRAY_SIZE];
  GLuint iinres_locations[ARRAY_SIZE];
  GLuint iinfmt_locations[ARRAY_SIZE];
  GLuint iinfps_locations[ARRAY_SIZE];
  GLuint idemo_locations[ARRAY_SIZE];
  GLuint iautorand_locations[ARRAY_SIZE];
  GLuint iautorandcycle_locations[ARRAY_SIZE];
  GLuint iseed_locations[ARRAY_SIZE];
  GLuint istate_locations[ARRAY_SIZE];
  GLuint ipage_locations[ARRAY_SIZE];
  GLuint iselected_locations[ARRAY_SIZE];
  GLuint iactive_locations[ARRAY_SIZE];

  UintArray midi_lengths;
  GLuint igroup_locations[ARRAY_SIZE];

  GLuint vpos_locations[ARRAY_SIZE];

  GLuint textures_locations[ARRAY_SIZE];

  unsigned int sub_type_count;
  unsigned int sub_variant_count;
  GLuint sub_locations[ARRAY_SIZE];

  unsigned int active_count;

  unsigned int in_count;
#ifdef VIDEO_IN
  unsigned int sub_video_count;
  unsigned int input_tex_map[MAX_VIDEO];
  EGLDisplay egl_display;
  EGLImageKHR dma_images[MAX_VIDEO];
#endif /* VIDEO_IN */
} ShaderProgram;

// window.c

typedef GLFWwindow Window;

// tempo.c

typedef struct Tempo {
  long last_reset;
  long last_tap;
  unsigned int taps_in_chain;
  unsigned int tap_duration_index;
  unsigned int tap_durations[MAX_TAP_VALUES];
  bool last_tap_skipped;
  long beat_length;
  float tempo;
} Tempo;

// context.c

typedef struct Context {
  int fd;
  vec2 resolution;
  vec2 tex_resolution;
#ifdef VIDEO_IN
  vec2 input_resolutions[MAX_VIDEO];
  unsigned int input_formats[MAX_VIDEO];
  unsigned int input_fps[MAX_VIDEO];
  unsigned int input_index[MAX_VIDEO];
#endif /* VIDEO_IN */
  double time;
  Tempo tempo;
  double tempo_total;
  UintArray state;
  unsigned int page;
  unsigned int selected;
  unsigned int active[ARRAY_SIZE];
  vec3 values[ARRAY_SIZE];
  bool demo;
  bool auto_random;
  unsigned int auto_random_cycle;
  unsigned int seeds[MAX_FRAG];
  unsigned int fps;
  _Atomic bool stop;
} Context;

// video.c

#ifdef VIDEO_IN

typedef struct VideoCapture {
  char name[STR_LEN];
  _Atomic bool error;
  _Atomic bool disconnected;
  _Atomic bool needs_reload;
  unsigned int buf_count;
  unsigned int buf_index;
  int fd;
  int exp_fd[ARRAY_SIZE];
  unsigned int width;
  unsigned int height;
  unsigned int pixelformat;
  unsigned int bytesperline;
  struct v4l2_buffer buf[ARRAY_SIZE];
} VideoCapture;

typedef struct VideoCaptureArray {
  VideoCapture values[MAX_VIDEO];
  unsigned int length;
} VideoCaptureArray;

typedef struct VideoBackgroundReadArgs {
  VideoCapture *capture;
  Context *context;
  int input_index;
  bool trace_fps;
} VideoBackgroundReadArgs;

#endif /* VIDEO_IN */

// midi.c

typedef struct MidiDevice {
  _Atomic bool connected;
  char name[STR_LEN];
  snd_rawmidi_t *input;
  snd_rawmidi_t *output;
} MidiDevice;

typedef struct MidiBackgroundListenArgs {
  MidiDevice *device;
  Context *context;
  void (*event_callback)(unsigned char code, unsigned char value);
} MidiBackgroundReadArgs;

// state.c

typedef struct StateConfig {
  unsigned int state_max;

  UintArray select_page_codes;
  UintArray select_item_codes;
  UintArray select_frag_codes;
  UintArray group_active_counts;
  UintArray group_active_offsets;
  UintArray group_active_codes;
  UintArray group_counts;
  UintArray group_offsets;
  UintArray codes;
  UintArray fader_codes;
  UintArray values_offsets;

  unsigned int value_count;

  unsigned int tap_tempo_code;

  char save_file_prefix[STR_LEN];

  unsigned int key_randomize;
  unsigned int key_reset;
  unsigned int key_demo;
  unsigned int key_autorand;
  unsigned int key_autorand_up;
  unsigned int key_autorand_down;
  unsigned int key_tempo_up;
  unsigned int key_tempo_down;

  UintArray key_load;
  UintArray key_save;
} StateConfig;

typedef struct StateBackgroundWriteArgs {
  Context *context;
  StateConfig state_config;
  MidiDevice *midi;
} StateBackgroundWriteArgs;

// timer.c

typedef struct Timer {
  struct timeval start;
  unsigned int counter;
  unsigned int target;
} Timer;

// config.c

typedef struct ConfigFile {
  struct hashmap *map;
  bool error;
} ConfigFile;

typedef struct ConfigFileItem {
  char key[STR_LEN];
  char value[STR_LEN];
} ConfigFileItem;

// project.c

typedef struct Project {
  bool error;
  char path[STR_LEN];
  ConfigFile config;
  StateConfig state_config;
  unsigned int frag_count;
  unsigned int in_count;
  UintArray sub_counts;
  File fragment_shaders[MAX_FRAG][MAX_SUB_FILE + 1];
} Project;

#endif /* TYPES_H */
