/*
 *  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 "globals.h"
#include "context.h"
#include "brandom.h"
#include "colormaps.h"
#include "images.h"
#include "sequences.h"
#include "defaults.h"
#include "biniou.h"
#ifdef WITH_WEBCAM
#include "webcam.h"
#endif
#include "pthread_utils.h"

extern uint64_t frames;
extern uint8_t usage_statistics;

#ifdef WITH_WEBCAM
extern char *video_base;

static webcam_t *cams[MAX_CAMS];
static pthread_t thr[MAX_CAMS];

#define MAX_TRIES 5


static void
Context_open_webcam(Context_t *ctx)
{
  uint8_t try = 0;

  parse_options();

  for (uint8_t i = 0; i < MAX_CAMS; i++) {
    pthread_mutex_init(&ctx->cam_mtx[i], NULL);
  }

  uint8_t first_cam = 0;
  int base_len = strlen(video_base);

  /* remove trailing digits to get the first cam: "/dev/video12" => video_base= "/dev/video", first_cam= 12 */
  char *last_char = &video_base[base_len-1];
  while ((base_len > 1) && isdigit((int)*last_char)) {
    last_char--;
    base_len--;
  }
  first_cam = xstrtol(last_char + sizeof(char));
  video_base[base_len] = '\0';

  for (uint8_t i = 0; (i < ctx->webcams) && (try < MAX_TRIES); i++) {
retry:
      cams[i] = xmalloc(sizeof(webcam_t));

      cams[i]->io = IO_METHOD_MMAP;
      cams[i]->fd = -1;
      cams[i]->ctx = ctx;
      cams[i]->cam_no = i;

      if (-1 != open_device(cams[i], first_cam + try)) {
        if (-1 != init_device(cams[i])) {
          enumerate_cids(cams[i]);
          list_inputs(cams[i]);
          start_capturing(cams[i]);

          xpthread_create(&thr[i], NULL, loop, (void *)cams[i]);
        } else {
          VERBOSE(printf("[i] Webcam #%d: failed to initialize\n", i));
          close_device(cams[i]);
          xfree(cams[i]);
          try++;
          goto retry;
        }
      } else {
        xfree(cams[i]);
        ctx->webcams--;
      }
    }
}
#endif


Context_t *
Context_new(const uint8_t desired_webcams)
{
  Context_t *ctx = xcalloc(1, sizeof(Context_t));

  ctx->running = 1;
  pthread_mutex_init(&ctx->frame_mutex, NULL);

  VERBOSE(printf("[+] Creating buffers... "));
  for (uint8_t i = 0; i < NSCREENS; i++) {
    VERBOSE(printf("%d ", i));
    ctx->buffers[i] = Buffer8_new();
  }

  ctx->webcams = desired_webcams;
#ifdef WITH_WEBCAM
  for (uint8_t k = 0; k < ctx->webcams; k++) {
    for (uint8_t i = 0; i < CAM_SAVE; i++) {
      ctx->cam_save[k][i] = Buffer8_new();
    }
    ctx->cam_ref[k] = Buffer8_new();
    ctx->cam_ref0[k] = Buffer8_new();
  }
  ctx->auto_webcams = 1;
#endif
  ctx->rgba_buffers[ACTIVE_BUFFER] = BufferRGBA_new();
  ctx->frame = xmalloc(RGB_BUFFSIZE * sizeof(Pixel_t));
  VERBOSE(printf("\n"));

#if WITH_GL
  glGenTextures(NSCREENS, ctx->textures); // TODO: delete on exit
  glGenTextures(MAX_CAMS, ctx->cam_textures); // TODO: delete on exit
#endif

  VERBOSE(printf("[+] Initializing sequence manager\n"));
  ctx->sm = SequenceManager_new();

  Context_load_banks(ctx);
  Context_load_shortcuts(ctx);

  VERBOSE(printf("[+] Initializing 3D engine\n"));
  Params3d_init(&ctx->params3d);

  ctx->timer = Timer_new("timer");
  ctx->fps_timer = Timer_new("fps_timer");

  ctx->target_pic = Image8_new();

  extern char *data_dir;
  gchar *tmp;

  /* load target pic */
  tmp = g_strdup_printf("%s/%s", data_dir, "images/z-biniou-tv-1.png");
  VERBOSE(printf("[+] Loading '%s'\n", tmp));
  int res = Image8_load_any(ctx->target_pic, tmp);
  g_free(tmp);
  if (res == -1) {
    Buffer8_randomize(ctx->target_pic->buff);
  }

#ifdef WITH_WEBCAM
  VERBOSE(printf("[i] Initializing %d webcams base: %s\n", desired_webcams, video_base));

  Context_open_webcam(ctx);

  for (uint8_t i = 0; i < desired_webcams; i++) {
    Buffer8_copy(ctx->target_pic->buff, ctx->cam_save[i][0]);
  }

  if (ctx->webcams > 1) {
    VERBOSE(printf("[+] Creating webcams shuffler (%d webcams)\n", ctx->webcams));
    ctx->webcams_shuffler = Shuffler_new(ctx->webcams);
    Shuffler_set_mode(ctx->webcams_shuffler, Context_get_shuffler_mode(BD_WEBCAMS));
#ifdef DEBUG
    Shuffler_verbose(ctx->webcams_shuffler);
#endif
  }
#endif

  ctx->random = Buffer8_new();
  for (uint32_t i = 0; i < BUFFSIZE; i++) {
    ctx->random->buffer[i] = b_rand_boolean();
  }

  // Video plugin
  for (uint8_t i = 0; i < CAM_SAVE; i++) {
    ctx->video_save[i] = Buffer8_new();
  }

  ctx->allow_random_changes = 1;
  ctx->allow_auto_colormaps = 1;
  ctx->allow_auto_images = 1;

  // Gray scale
  init_gray8();

  // Bandpass
  ctx->bandpass_min = 10;
  ctx->bandpass_max = 245;

  // Commands
  ctx->commands = g_async_queue_new();

  return ctx;
}


#ifdef WITH_WEBCAM
static void
Context_close_webcam(Context_t *ctx, const uint8_t cam_no)
{
  if (NULL != cams[cam_no]) {
    xpthread_join(thr[cam_no], NULL);
    stop_capturing(cams[cam_no]);
    uninit_device(cams[cam_no]);
    close_device(cams[cam_no]);
    xfree(cams[cam_no]);
    xpthread_mutex_destroy(&ctx->cam_mtx[cam_no]);
  }
}
#endif


void
Context_delete(Context_t *ctx)
{
  GSList *outputs = ctx->outputs, *_outputs = outputs;

  if (usage_statistics) {
    Context_statistics(ctx);
  }

#ifdef WITH_WEBCAM
  VERBOSE(printf("[i] Closing %d webcam%s\n", ctx->webcams, (ctx->webcams > 1) ? "s" : ""));
  if (desired_webcams)
    for (uint8_t i = 0; i < desired_webcams; i++) {
      Context_close_webcam(ctx, i);
    }
  xfree(video_base);
  Shuffler_delete(ctx->webcams_shuffler);
  Alarm_delete(ctx->a_webcams);
#endif

  VERBOSE(printf("[i] %"PRIu64" frames\n", frames));

  if (NULL != ctx->input_plugin) {
    Plugin_delete(ctx->input_plugin);
  }

  for ( ; NULL != outputs; outputs = g_slist_next(outputs)) {
    Plugin_t *output = (Plugin_t *)outputs->data;

    Plugin_delete(output);
  }
  g_slist_free(_outputs);

  VERBOSE(printf("[+] Freeing buffers... "));
  for (uint8_t i = 0; i < NSCREENS; i++) {
    VERBOSE(printf("%d ", i));
    Buffer8_delete(ctx->buffers[i]);
  }
#ifdef WITH_WEBCAM
  {
    for (uint8_t k = 0; k < desired_webcams; k++) {
      for (uint8_t i = 0; i < CAM_SAVE; i++) {
        Buffer8_delete(ctx->cam_save[k][i]);
      }
      Buffer8_delete(ctx->cam_ref[k]);
      Buffer8_delete(ctx->cam_ref0[k]);
    }
  }
#endif
  BufferRGBA_delete(ctx->rgba_buffers[ACTIVE_BUFFER]);
  VERBOSE(printf("\n"));

  if (NULL != ctx->imgf) {
    VERBOSE(printf("[+] Freeing images fader\n"));
    ImageFader_delete(ctx->imgf);

    VERBOSE(printf("[+] Freeing images timer\n"));
    Alarm_delete(ctx->a_images);
  }

  if (NULL != ctx->cf) {
    VERBOSE(printf("[+] Freeing colormaps fader\n"));
    CmapFader_delete(ctx->cf);

    VERBOSE(printf("[+] Freeing colormaps timer\n"));
    Alarm_delete(ctx->a_cmaps);
  }

  Alarm_delete(ctx->a_random);
  SequenceManager_delete(ctx->sm);
  Timer_delete(ctx->timer);
  Timer_delete(ctx->fps_timer);

  if (NULL != ctx->target_pic) {
    Image8_delete(ctx->target_pic);
  }

  Buffer8_delete(ctx->random);

  json_decref(ctx->playlist);
  Timer_delete(ctx->track_timer);
  Shuffler_delete(ctx->playlist_shuffler);

  for (uint8_t i = 0; i < CAM_SAVE; i++) {
    Buffer8_delete(ctx->video_save[i]);
  }

  xfree(ctx->frame);
  xpthread_mutex_destroy(&ctx->frame_mutex);

  Context_free_commands(ctx);

  xfree(ctx);
}
