#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <math.h>
#include <mpv/client.h>
#define LIB_TERM_IMPL
#include <libterm.h>
#define LIB_STR_IMPL
#include <libstr.h>

#include "../include/rx.h"
#define INI_IMPL
#include "../include/ini.h"

static int rx_ini_load(void *user, const char *section, const char *name, const char *value) {
  rx_t *self = (rx_t *) user;
  if (strcmp(section, "stations") == 0) {
    if (strcmp(name, "uid") == 0) {
      self->stations = realloc(self->stations, sizeof(rx_station_t) * (self->stations_len + 1));
      self->stations[self->stations_len] = (rx_station_t) {
        .addr = NULL,
        .url = NULL,
        .resolver = NULL,
      };
      self->stations_len++;
    }
    rx_station_t *station = &self->stations[self->stations_len - 1];
    if (strcmp(name, "uid") == 0) {
      size_t len = strlen(value);
      memcpy(station->uid, value, len);
      station->uid[len] = '\0';
    }
    if (strcmp(name, "name") == 0) {
      size_t len = strlen(value);
      memcpy(station->name, value, len);
      station->name[len] = '\0';
    }
    if (strcmp(name, "addr") == 0) {
      size_t len = strlen(value);
      station->addr = malloc(sizeof(char) * (len + 1));
      memcpy(station->addr, value, len);
      station->addr[len] = '\0';
    }
    if (strcmp(name, "url") == 0) {
      size_t len = strlen(value);
      station->url = malloc(sizeof(char) * (len + 1));
      memcpy(station->url, value, len);
      station->url[len] = '\0';
    }
    if (strcmp(name, "resolver") == 0) {
      size_t len = strlen(value);
      station->resolver = malloc(sizeof(char) * (len + 1));
      memcpy(station->resolver, value, len);
      station->resolver[len] = '\0';
    }
  }
  return 1;
}

void rx_init(rx_t *self) {
  self->stations = NULL;
  self->stations_len = 0;
  char *config_path = NULL;
  char *home_path = getenv("HOME");
  if (home_path != NULL) {
    str_appendf(&config_path, "%s/.config/rx/", home_path);
  }
  str_append(&config_path, "rx.ini");
  ini_parse(config_path, rx_ini_load, self);
  free(config_path);
  self->curr = NULL;
  self->ctx = mpv_create();
  mpv_initialize(self->ctx);
  self->idx = 0;
  self->count = 0;
  self->title = NULL;
  self->loading = 0;
  self->error = 0;
  term_enable_raw_mode();
  term_write("\x1b[?25l");
}

void rx_update(rx_t *self) {
  int key = term_poll_key(100);
  switch (key) {
    case 'q': {
      self->quit = true;
      break;
    }
    case 'j': {
      if ((size_t) self->idx < self->stations_len - 1) {
        self->idx++;
      }
      break;
    }
    case 'k': {
      if (self->idx > 0) {
        self->idx--;
      }
      break;
    }
    case KEY_ENTER: {
      const rx_station_t *station = &self->stations[self->idx];
      char *resolver = station->resolver;
      char *url = NULL;
      if (resolver != NULL) {
        char *resolver_path = NULL;
        char *home_path = getenv("HOME");
        if (home_path != NULL) {
          str_appendf(&resolver_path, "%s/.config/rx/mods/", home_path);
        }
        str_append(&resolver_path, resolver);
        void *handle = dlopen(resolver_path, RTLD_LAZY);
        free(resolver_path);
        if (handle == NULL) {
          fprintf(stderr, "%s\n", dlerror());
          break;
        }
        char *(*resolve_fn)(void) = (char *(*)(void)) dlsym(handle, "rx_resolve");
        const char *err = dlerror();
        if (err != 0) {
          fprintf(stderr, "%s\n", err);
          break;
        }
        url = resolve_fn();
        dlclose(handle);
      }
      else {
        size_t len = strlen(station->url);
        url = malloc(sizeof(char) * (len + 1));
        memcpy(url, station->url, len);
        url[len] = '\0';
      }
      if (url != NULL) {
        const char *cmd[] = { "loadfile", url, NULL };
        mpv_command(self->ctx, cmd);
        free(url);
      }
      self->curr = station;
      self->title = NULL;
      self->loading = 1;
      self->error = 0;
      break;
    }
    case KEY_BACKSPACE: {
      const char *cmd[] = { "stop", NULL };
      mpv_command(self->ctx, cmd);
      self->curr = NULL;
      self->loading = 0;
      self->error = 0;
      break;
    }
    default:
      break;
  }
  if (self->count == RX_UPDATE_COUNT) {
    if (self->title != NULL) {
      mpv_free(self->title);
      self->title = NULL;
    }
    self->title = mpv_get_property_string(self->ctx, "metadata/icy-title");
    self->count = 0;
  }
  self->count++;
  mpv_get_property(self->ctx, "core-idle", MPV_FORMAT_FLAG, &self->loading);
  mpv_event *event = mpv_wait_event(self->ctx, 0);
  if (event->event_id == MPV_EVENT_END_FILE) {
    mpv_event_end_file *end_file = (mpv_event_end_file *) event->data;
    if (end_file->reason == MPV_END_FILE_REASON_ERROR) {
      self->error = 1;
      const char* err = mpv_error_string(end_file->error);
      size_t len = strlen(err);
      memcpy(self->message, err, len);
      self->message[len] = '\0';
    }
  }
}

void rx_draw(rx_t *self) {
  term_write("\x1b[2J");
  term_write("\x1b[H");
  term_write("\r\n");
  for (size_t i = 0; i < self->stations_len; i++) {
    const rx_station_t *station = &self->stations[i];
    if (i == (size_t) self->idx) {
      term_write("> ");
    }
    else {
      term_write("  ");
    }
    int pad = 3; // floor(log10(self->stations_len) + 1);
    term_writef("%0*d ", pad, i + 1);
    term_writef("[%s] %s", station->uid, station->name);
    if (station->addr != NULL) {
      term_writef(" (%s)", station->addr);
    }
    term_write("\r\n");
  }
  term_write("\r\n");
  const rx_station_t *station = self->curr;
  if (station != NULL) {
    term_writef("[%s] %s\r\n", station->uid, station->name);
    if (self->error) {
      term_writef("error: %s\r\n", self->message);
    }
    else {
      if (self->loading) {
        term_write("loading...\r\n");
      }
      else {
        if (self->title != NULL && strlen(self->title) > 0) {
          term_writef("title: %s\r\n", self->title);
        }
        else {
          term_write("title: ???\r\n");
        }
      }
    }
  }
  term_flush();
}

void rx_free(rx_t *self) {
  mpv_terminate_destroy(self->ctx);
  term_write("\x1b[2J");
  term_write("\x1b[H");
  term_write("\x1b[?25h");
  term_flush();
  term_disable_raw_mode();
  for (size_t i = 0; i < self->stations_len; i++) {
    rx_station_t *station = &self->stations[i];
    if (station->addr != NULL) {
      free(station->addr);
    }
    if (station->url != NULL) {
      free(station->url);
    }
    if (station->resolver != NULL) {
      free(station->resolver);
    }
  }
  free(self->stations);
  self->stations_len = 0;
}
