#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <Imlib2.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#define presenter_error(...) {				\
    fprintf(stderr, "%s:%d: ", __FUNCTION__, __LINE__);	\
    fprintf(stderr, __VA_ARGS__);			\
    fprintf(stderr, "\n");				\
    exit(EXIT_FAILURE); 				\
  }

#ifndef TRUE
#define TRUE (0==0)
#endif
#ifndef FALSE
#define FALSE (0!=0)
#endif
#ifndef MIN
#define MIN(a,b) ((a)>(b)?(b):(a))
#endif
#ifndef MAX
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif

#define VIDEO_IDS 10

/* Structs */

struct ffmpeg_video {
  AVFormatContext *pFormatCtx;
  int videoStream;
  AVCodecContext *pCodecCtx;
  AVFrame *pFrame;
  AVFrame *pFrameBGRA;
  uint8_t *buffer;
  struct SwsContext *img_convert_ctx;
  AVPacket packet;
  int frame;
  int videoFinished;
};

// The renderer processes a sequence of timepoints, and executes a
// list of commands at each time point

// The internal frame is at the resolution of the first display
// target. Best to make that the one the subject is seeing.
// It will be resized for the other displays.

enum renderer_command {
  /* advance the sequence in at most this many s computed form the start of
     the current time step */
  ADVANCE,
  /* sleep, won't delay past renderer_advance */
  /* always sleeps from the beginning of the current iteration */
  SLEEP,
  /* issue a festival command */
  FESTIVAL,
  /* flood fill a rectangle */
  FILL_RECTANGLE,
  /* render an image, coordinates are normalized to the viewer size */
  IMAGE,
  /* draw text to the screen, coordinates and size are normalized to the
     viewer screen */
  /* size=1 text height is equal to screen height */
  TEXT,
  /* render to the screen, without this it will leave up the last
     display and all draws will go into the framebuffer */
  RENDER,
  /* wake up the gui with an X event */
  WAKE_GUI,
  /* load a video file, currently only one can be active at one time */
  /* coordinates are normalized to the viewer size */
  LOAD_VIDEO,
  SHOW_VIDEO_FRAME,
  ADVANCE_VIDEO_FRAME,
  /* repeat the commands for n timepoints */
  LOOP,
  /* reset brain volume counter, do before WAIT_FOR_VOLUME */
  START_VOLUME,
  /* wait for a new brain volume to start */
  WAIT_FOR_VOLUME,
  /* stop the current iteration (ignoring loop counters), optionally clear the
     volume flag */
  STOP_ON_VOLUME_WITHOUT_CLEARING,
  STOP_ON_VOLUME_AND_CLEAR,
};

struct advance {
  double s;
};

struct sleep {
  double s;
};

struct festival {
  char *text;
};

struct fill_rectangle {
  double x, y, width, height;
  int r, g, b, a;
};

struct image {
  Imlib_Image image;
  double x, y, width, height;
};

// remember to set up imlib_add_path_to_font_path so that imlib can
// find the fonts
struct text {
  char *text; char *font;
  // Note that you need to encode the size in the font name!
  // And that this is not independent of the framebuffer size unlike
  // all other measurements
  int r, g, b, a;
  int direction; double angle;
  double x, y;
};

struct load_video {
  struct ffmpeg_video *ffmpeg_video;
  int id;
};

struct show_video_frame {
  int id;
  double x, y, width, height;
  int a;
};

struct advance_video_frame {
  int id;
};

struct loop {
  unsigned int iterations;
};

union data {
  struct advance *advance;
  struct sleep *sleep;
  struct festival *festival;
  struct fill_rectangle *fill_rectangle;
  struct image *image;
  struct text *text;
  struct load_video *load_video;
  struct show_video_frame *show_video_frame;
  struct advance_video_frame *advance_video_frame;
  struct loop *loop;
};

struct renderer_commands {
  enum renderer_command type;
  union data data;
  struct renderer_commands *next;
};

struct renderer_sequence {
  struct renderer_commands *commands;
  struct renderer_sequence *next;
};

struct renderer_target {
  Window window;
  int x, y, width, height;
};

struct renderer_task_args {
  int sequence_length;
  struct renderer_sequence *sequence;
  int number_of_targets;
  struct renderer_target *targets;
  // when done send an xevent to this window, scheme will resume
  Window wakeup_target;
  // These next five allow the renderer to draw the countdown clock to the
  // message pane.
  unsigned int trs;
  double tr;
  int countdown;
  Window message_pane;
  int message_pane_y;
};

struct renderer_log {
  double start_timestamp;
  unsigned int volumes;
  unsigned int counter;
  unsigned int iteration;
  enum renderer_command type;
};

enum stop_reason {
  RENDERER_FINISHED_SEQUENCE,
  RENDERER_WAS_STOPPED
};

struct renderer_result {
  enum stop_reason stop_reason;
  int timepoints_processed;
  struct renderer_log *log;
};

struct trigger_task_args {
  int slices_per_tr;
};

/* Variables */

extern volatile int renderer_stopped;

/* C++ */

void c_festival_eval_command(char *text);
void c_festival_initialize(void);

/* Scheme->C */

#ifdef S2CINTBITS
void sc_error(char *symbol, char *format, TSCP args);

TSCP bool_tscp(int bool);

int tscp_bool(TSCP bool);

/* Fix Scheme->C signed integers */

TSCP sc_int_tscp(int n);

int sc_tscp_int(TSCP p);
#endif

/* stuff */

void *presenter_malloc(size_t size);

double current_time(void);

/* ffmpeg */

int ffmpeg_first_video_stream(struct ffmpeg_video *ffmpeg_video);

AVCodecContext *ffmpeg_get_codec(struct ffmpeg_video *ffmpeg_video);

int ffmpeg_next_frame(struct ffmpeg_video *ffmpeg_video);

char ffmpeg_video_finished(struct ffmpeg_video *ffmpeg_video);

void ffmpeg_close_and_free_video(struct ffmpeg_video *ffmpeg_video) ;

struct ffmpeg_video *ffmpeg_open_video(char *filename);

uint8_t *ffmpeg_get_frame(struct ffmpeg_video *ffmpeg_video);

unsigned int ffmpeg_video_width(struct ffmpeg_video *ffmpeg_video);

unsigned int ffmpeg_video_height(struct ffmpeg_video *ffmpeg_video);

double ffmpeg_video_frame_rate(struct ffmpeg_video *ffmpeg_video);

Imlib_Image ffmpeg_get_frame_as_imlib(struct ffmpeg_video *ffmpeg_video);

/* This is the unsafe version of the above. You must not advance the
   frame or free the ffmpeg video until you are done with the imlib image.
   It's faster because it doesn't copy the data. */

Imlib_Image ffmpeg_get_frame_as_imlib_unsafe
(struct ffmpeg_video *ffmpeg_video);

/* renderer thread */

void wakeup_gui(Display *display, Window wakeup_target);

/* return TRUE if we should rerun for another iteration */
int execute_commands(struct renderer_commands *commands,
		     struct ffmpeg_video **videos,
		     double start_timestamp,
		     Display *display,
		     unsigned int trs,
		     double tr,
		     int countdown,
		     Window message_pane,
		     GC gc,
		     int message_pane_y,
		     Window wakeup_target,
		     Imlib_Image framebuffer,
		     int number_of_targets,
		     struct renderer_target *targets,
		     int timepoint,
		     unsigned int iteration);

void *renderer_task(void *args);

void start_renderer_thread(struct renderer_task_args *renderer_task_args);

struct renderer_result *stop_renderer_thread(void);

void free_commands(struct renderer_commands *commands);

void free_sequence(struct renderer_sequence *sequence);

void free_renderer_task_args(struct renderer_task_args *renderer_task_args);

/* trigger thread */

/* can't use AnyModifier, it's too often unavailable */

void grab_key(Display *display, Window root, int keycode);

void un_grab_key(Display *display, Window root, int keycode);

void send_key(Display *display, Window root, int keycode);

void *trigger_task(void *args);

void start_trigger_thread(int slices_per_tr);
