/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include <SDL2/SDL.h>
#include "context.h"
#include "images.h"
#include "colormaps.h"
#include "osd.h"
#include "ttf.h"
#include "src/defaults.h"

#define PB_WIDTH 3 /* progress bar width (%) */
#define SHOW 18    /* number of plugins to display before/after the current plugin */

extern uint16_t fontlineskip;

SDL_Window *osd_window = NULL;

#define BORDER      5
#define ARROW       "->"
#define OSD_BUFFLEN 512

#define OSD_WIDTH  600
#define OSD_HEIGHT 900

extern uint8_t max_fps;
extern uint8_t enable_osd;


static int
osd_init(void)
{
  int res = ttf_init();

  if (res) {
    char *icon_file;
    SDL_Surface *icon = NULL;

    osd_window = SDL_CreateWindow("Le Biniou", 0, 0, OSD_WIDTH, OSD_HEIGHT, 0);
    if (NULL == osd_window) {
      xerror("Couldn't set %dx%d video mode: %s\n", OSD_WIDTH, OSD_HEIGHT, SDL_GetError());
    }

    icon_file = g_strdup_printf("%s/lebiniou.bmp", DEFAULT_DATADIR);
#ifdef DEBUG
    printf("[i] Loading icon from %s\n", icon_file);
#endif
    icon = SDL_LoadBMP(icon_file);
    g_free(icon_file);
    if (NULL != icon) {
      Uint32 colorkey = SDL_MapRGB(icon->format, 0, 0, 0);
      SDL_SetColorKey(icon, SDL_TRUE, colorkey);
      SDL_SetWindowIcon(osd_window, icon);
      SDL_FreeSurface(icon);
    }
  }

  return res;
}


static void
osd_quit(void)
{
  SDL_DestroyWindow(osd_window);
  ttf_quit();
}


static void
osd_info(const Context_t *ctx)
{
  char buff[OSD_BUFFLEN+1];
  int dst_y = 0;

  struct timeval now;
  char *now_str;

  float elapsed;
  uint16_t d, h, m, s;

  const Sequence_t *cur_seq = ctx->sm->cur;

  /* Display readable localtime */
  /* TODO error checking */
  gettimeofday(&now, NULL);
  /* XXX TODO error + bounds checking */
  now_str = ctime((time_t *)&now.tv_sec);
  now_str[strlen(now_str)-1] = '\0';

  dst_y = osd_print(BORDER, dst_y, 0, 0, "%s", now_str);

  /* Uptime */
  elapsed = Timer_elapsed(ctx->timer);
  d = (uint16_t)(elapsed / (3600*24));
  elapsed -= d*3600*24;
  h = (uint16_t)(elapsed / 3600);
  elapsed -= h*3600;
  m = (uint16_t)(elapsed / 60);
  elapsed -= m*60;
  s = (uint16_t)elapsed;
  elapsed -= s;
  dst_y = osd_print(BORDER, dst_y, 0, 0, "Up: %1dd %02d:%02d:%02d.%02d",
                    d, h, m, s, (uint16_t)(elapsed*100));

  if (NULL != cur_seq->name) {
    /* Display sequence name */
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Sequence: %s", cur_seq->name);
  } else {
    /* Display sequence id */
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Sequence: %"PRIu64, cur_seq->id);
  }

  /* Display current bankset:bank */
  dst_y = osd_print(BORDER, dst_y, 0, 0, "Sequences bank: %d-%d",
                    ctx->bank_set + 1, ctx->bank + 1);
  /* Display colormap and image (if any) */
  if (NULL != colormaps) {
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Colormap: %s",
                      (cur_seq->cmap_id) ? Colormaps_name(cur_seq->cmap_id) : "(default)");
  }

  if (NULL != ctx->imgf) {
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Image: %s",
                      (cur_seq->image_id) ? Images_name(cur_seq->image_id) : "(default)");
  }

  /* Display auto* stuff */
  if (ctx->random_mode != BR_NONE) {
    const char *what = NULL;

    if (ctx->random_mode == BR_SCHEMES) {
      what = "Schemes";
    } else if (ctx->random_mode == BR_SEQUENCES) {
      what = "Sequences";
    } else if (ctx->random_mode == BR_BOTH) {
      what = "Schemes+Sequences";
    }
    snprintf(buff, OSD_BUFFLEN * sizeof(char), "Auto mode: %s", what);
  } else {
    snprintf(buff, OSD_BUFFLEN * sizeof(char), "Auto mode: Off");
  }
  dst_y = osd_print(BORDER, dst_y, 0, 0, "%s", buff);

  /* Display random cmap/image */
  if ((NULL != colormaps) && (colormaps->size > 1))
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Random colormaps: %s",
                      ctx->auto_colormaps ? "On" : "Off");

  if ((NULL != ctx->imgf) && (ctx->imgf->shf->size > 1))
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Random images: %s",
                      ctx->auto_images ? "On" : "Off");

  /* Display locked plugin, if any */
  if (NULL != ctx->locked) {
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Locked: %s", ctx->locked->dname);
  }

  /* Volume scaling */
  if (NULL != ctx->input) {
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Volume scale: %.2f", Context_get_volume_scale(ctx));
  }

  /* Fade delay */
  dst_y = osd_print(BORDER, dst_y, 0, 0, "Fade delay: %.1f", fade_delay);

  /* 3D scale factor */
  dst_y = osd_print(BORDER, dst_y, 0, 0, "3D scale factor: %.2f", ctx->params3d.scale_factor);

  // Mute input
  if (NULL != ctx->input) {
    dst_y = osd_print(BORDER, dst_y, 0, 0, "Freeze input: %s", ctx->input->mute ? "on" : "off");
  }
}


static void
osd_fps(const Context_t *ctx)
{
  (void)osd_print(BORDER, 0, 1, 1, "%03d FPS (%03d)", (int)Context_fps(ctx), max_fps);
}


static void
osd_sequence(Context_t *ctx)
{
  char buff[OSD_BUFFLEN+1];
  const Sequence_t *cur_seq;
  GList *tmp;
  uint16_t dst_y = 0;
  uint8_t lens_there = 0;

  cur_seq = ctx->sm->cur;
  tmp = g_list_first(cur_seq->layers);

  while (NULL != tmp) {
    Layer_t *layer = (Layer_t *)tmp->data;
    Plugin_t *P = layer->plugin;

    if (NULL != P) {
      const char *mode = LayerMode_to_OSD_string(layer->mode);
      const char *arrow = (P == plugins->selected) ? ARROW : "";

      if ((NULL != cur_seq->lens) && (P == cur_seq->lens)) {
        lens_there = 1;
        snprintf(buff, OSD_BUFFLEN*sizeof(char), "%s %s - %s", arrow, P->dname, mode);
      } else {
        const char lens_there_c = (lens_there) ? ' ' : '|';
        snprintf(buff, OSD_BUFFLEN*sizeof(char), "%s %s %c %s", arrow, P->dname, lens_there_c, mode);
      }

      dst_y = osd_print(BORDER, dst_y, 1, 0, "%s", buff);
    }
    tmp = g_list_next(tmp);
  }

  if ((NULL != plugins) && (NULL != plugins->selected)) {
    if (NULL != plugins->selected->parameters) {
      json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
      /* find max parameter name size */
      uint8_t max_name_size = 0;
      const char *name;
      json_t *iter_value;

      json_object_foreach(j_params, name, iter_value) {
        if (json_is_integer(iter_value) || json_is_real(iter_value) || json_is_boolean(iter_value)) {
          max_name_size = MAX(max_name_size, strlen(name));
        }
      }

      dst_y = osd_print(BORDER, dst_y, 1, 0, " ");
      dst_y = osd_print(BORDER, dst_y, 1, 0, "%s parameters%*s", plugins->selected->dname,
                        MAX(0, 2 + (int)max_name_size + 7 - (11 + (int)strlen(plugins->selected->name))), "");

      uint8_t n = 0;
      json_object_foreach(j_params, name, iter_value) {
        json_t *j_value = json_object_get(iter_value, "value");
        const char *arrow = (n == plugins->selected->selected_param) ? ARROW : "";

        if (json_is_boolean(j_value)) {
          const char *value = json_boolean_value(j_value) ? "on  " : "off ";
          dst_y = osd_print(BORDER, dst_y, 1, 0, "%s %*s: %s", arrow, max_name_size, name, value);
        } else if (json_is_integer(j_value)) {
          int value = json_integer_value(j_value);
          dst_y = osd_print(BORDER, dst_y, 1, 0, "%s %*s: %-4d", arrow, max_name_size, name, value);
        } else if (json_is_real(j_value)) {
          double value = json_real_value(j_value);
          dst_y = osd_print(BORDER, dst_y, 1, 0, "%s %*s: %-4.2f", arrow, max_name_size, name, value);
        } else if (json_is_string(j_value)) {
          const char *str = json_string_value(j_value);
          dst_y = osd_print(BORDER, dst_y, 1, 0, "%s %*s: %s", arrow, max_name_size, name, str);
        }

        n++;
      }
      json_decref(j_params);
    }
  }
  cur_seq = ctx->sm->cur;
}


static void
osd_plugins(const Context_t *ctx)
{
  short n;
  short start;
  uint16_t skip = fontlineskip - 1;
  uint16_t dst_y = 2 * SHOW * skip + 2 * skip;

  start = plugins->selected_idx - SHOW;
  while (start < 0) {
    start += plugins->size;
  }

  if (NULL != plugins->plugins) {
    for (n = 0; (n < 2 * SHOW + 1) && (n < plugins->size); ) {
      const char *arrow;
      char in_sequence;
      Plugin_t *plugin = plugins->plugins[start];

      if (NULL != plugin) {
        arrow = (n == SHOW) ? ARROW : "  ";
        in_sequence = Sequence_find(ctx->sm->cur, plugin) ? '*' : ' ';
        (void)osd_print(BORDER, dst_y, 0, 1, "%c|%c|%c|%c|%c %s %c %s",
                        (*plugin->options & BO_SFX) ? 'S' : ' ',
                        (*plugin->options & BO_GFX) ? 'G' : ' ',
                        ((*plugin->options & BO_BLUR) || (*plugin->options & BO_DISPLACE)
                         || (*plugin->options & BO_ROLL))  ? 'F' : ' ',
                        ((*plugin->options & BO_IMAGE) || (*plugin->options & BO_SPLASH))  ? 'I' : ' ',
                        (*plugin->options & BO_LENS)  ? 'L' : ' ',
                        arrow, in_sequence, plugin->dname);
        dst_y -= skip;
      }
      n++;

      start++;
      if (start == plugins->size) {
        start = 0;
      }
    }
  }
}


static void
osd_plugin_desc(const Context_t *ctx)
{
  char *dsc = NULL;
  int skip = fontlineskip - 1;

  if (NULL != plugins->selected) {
    if (NULL == plugins->selected->desc) {
      dsc = "NO DESCRIPTION";
    } else {
      dsc = plugins->selected->desc;
    }

    (void)osd_print(BORDER, skip, 1, 1, "%s", dsc);
  }
}


static void
osd_random_mode_elapsed(const Context_t *ctx)
{
  float pct = Alarm_elapsed_pct(ctx->a_random);
  uint8_t color = 255; /* TODO colormap->max */
  uint16_t width;
  uint16_t height;
  SDL_Rect r;

  int w, h;
  SDL_GetWindowSize(osd_window, &w, &h);

  height = (uint16_t)((1.0 - pct) * h);
  width = (uint16_t)(w * PB_WIDTH / 100.0);
  r.x = w - width;
  r.y = h - height;
  r.w = width;
  r.h = height;

  SDL_FillRect(SDL_GetWindowSurface(osd_window), &r, color);
}


static void
osd(Context_t *ctx)
{
  SDL_Rect r;
  int w, h;

  SDL_GetWindowSize(osd_window, &w, &h);
  r.x = r.y = 0;
  r.w = w;
  r.h = h;
  SDL_FillRect(SDL_GetWindowSurface(osd_window), &r, 0);

  if (!SequenceManager_lock(ctx->sm)) {
    osd_info(ctx);
    osd_sequence(ctx);
    SequenceManager_unlock(ctx->sm);
  }
  if ((NULL == ctx->playlist) && (ctx->random_mode != BR_NONE)) {
    osd_random_mode_elapsed(ctx);
  }

  osd_fps(ctx);

  if (NULL != plugins) {
    osd_plugins(ctx);
    osd_plugin_desc(ctx);
  }

  if (SDL_UpdateWindowSurface(osd_window) < 0) {
    SDL_Log("[3] SDL_UpdateWindowSurface failed: %s", SDL_GetError());
    exit(1);
  }
}


void *
osd_thread(void *attr)
{
  if (enable_osd && osd_init()) {
    Context_t *ctx = (Context_t *)attr;

    while (ctx->running) {
      osd(ctx);
      ms_sleep(THREADS_DELAY);
    }
    osd_quit();
  }

  return NULL;
}
