/*
 *  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 "colormaps.h"
#include "images.h"
#include "pthread_utils.h"


static void
Context_display_random(const Context_t *ctx)
{
  printf("[A] Auto random is ");
  if (ctx->random_mode == BR_NONE) {
    printf("off\n");
  } else if (ctx->random_mode == BR_SEQUENCES) {
    printf("auto sequences\n");
  } else if (ctx->random_mode == BR_SCHEMES) {
    printf("auto schemes\n");
  } else if (ctx->random_mode == BR_BOTH) {
    printf("auto schemes/sequences\n");
  }
}


static json_t *
random_modes_result(const Context_t *ctx)
{
  json_t *res = json_pack("{sb sb sb sb"
#ifdef WITH_WEBCAM
                          " sb"
#endif
                          "}"
                          , "randomSchemes", (ctx->random_mode == BR_SCHEMES) || (ctx->random_mode == BR_BOTH),
                          "randomSequences", (ctx->random_mode == BR_SEQUENCES) || (ctx->random_mode == BR_BOTH),
                          "autoColormaps", ctx->auto_colormaps,
                          "autoImages", ctx->auto_images
#ifdef WITH_WEBCAM
                          , "autoWebcams", ctx->auto_webcams
#endif
                          );

  return res;
}


static json_t *
Context_command(Context_t *ctx, const enum Command cmd)
{
  json_t *res = NULL;
  GSList *outputs = ctx->outputs;

  switch (cmd) {
    case CMD_APP_CLEAR_SCREEN:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_CLEAR_SCREEN\n");
#endif
      Buffer8_clear(active_buffer(ctx));
      Buffer8_clear(passive_buffer(ctx));
      break;

    case CMD_APP_DISPLAY_COLORMAP:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_DISPLAY_COLORMAP\n");
#endif
      ctx->display_colormap = !ctx->display_colormap;
      break;

    case CMD_APP_FIRST_SEQUENCE: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_FIRST_SEQUENCE\n");
#endif
      Context_first_sequence(ctx);
      res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, ctx->sm->cur->name));
    }
      break;

    case CMD_APP_FREEZE_INPUT:
      if (NULL != ctx->input) {
#ifdef DEBUG_COMMANDS
        printf(">>> CMD_APP_FREEZE_INPUT\n");
#endif
        res = json_pack("{sb}", "mute", Input_toggle_mute(ctx->input));
      }
      break;

    case CMD_APP_LAST_SEQUENCE: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_LAST_SEQUENCE\n");
#endif
      Context_last_sequence(ctx);
      res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, ctx->sm->cur->name));
    }
      break;

    case CMD_APP_LOCK_SELECTED_PLUGIN:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_LOCK_SELECTED_PLUGIN\n");
#endif
      res = json_pack("{ss}", "selectedPlugin", plugins->selected->name);
      if (ctx->locked != plugins->selected) {
        ctx->locked = plugins->selected;
        json_object_set_new(res, "lockedPlugin", json_string(ctx->locked->name));
#ifdef DEBUG
        printf("[i] Lock plugin '%s' (%s)\n", ctx->locked->name, plugins->selected->dname);
#endif
      } else {
        json_object_set_new(res, "lockedPlugin", json_null());
#ifdef DEBUG
        printf("[i] Unlock plugin '%s' (%s)\n", ctx->locked->name, plugins->selected->dname);
#endif
        ctx->locked = NULL;
      }
      if (NULL != plugins->selected->parameters) {
        json_object_set_new(res, "parameters", plugins->selected->parameters(ctx, NULL, 0));
      } else {
        json_object_set_new(res, "parameters", json_null());
      }
      break;

    case CMD_APP_PREVIOUS_RANDOM_MODE:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_PREVIOUS_RANDOM_MODE\n");
#endif
      if (ctx->random_mode == BR_NONE) {
        ctx->random_mode = BR_BOTH;
      } else {
        --ctx->random_mode;
      }

      if (ctx->random_mode == BR_SCHEMES) {
        if ((NULL == schemes) || (!Shuffler_ok(schemes->shuffler))) {
          printf("[i] Skipping random schemes since there are no schemes available\n");
          ctx->random_mode = BR_SEQUENCES;
        } else {
          Schemes_random(ctx, 0);
          Alarm_init(ctx->a_random);
        }
      }

      if (ctx->random_mode == BR_SEQUENCES) {
        if ((NULL == sequences->seqs) || !g_list_length(sequences->seqs)) {
          printf("[i] Skipping random sequences since there are no sequences available\n");
          ctx->random_mode = BR_NONE;
        } else {
          Context_random_sequence(ctx);
          Alarm_init(ctx->a_random);
        }
      }

      Context_display_random(ctx);
      res = random_modes_result(ctx);
      break;

    case CMD_APP_PREVIOUS_SEQUENCE: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_PREVIOUS_SEQUENCE\n");
#endif
      Context_previous_sequence(ctx);
      res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, ctx->sm->cur->name));
    }
      break;

    case CMD_APP_NEXT_3D_BOUNDARY:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_NEXT_3D_BOUNDARY\n");
#endif
      ctx->params3d.boundary = (ctx->params3d.boundary + 1) % NB_BOUNDARIES;
      res = Params3d_to_json(&ctx->params3d);
      break;

    case CMD_APP_NEXT_RANDOM_MODE:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_NEXT_RANDOM_MODE\n");
#endif
      ctx->random_mode = (ctx->random_mode + 1) % BR_NB;

      if (ctx->random_mode == BR_SEQUENCES) {
        if ((NULL == sequences->seqs) || !g_list_length(sequences->seqs)) {
          printf("[i] Skipping random sequences since there are no sequences available\n");
          ctx->random_mode = BR_SCHEMES;
        } else {
          Context_random_sequence(ctx);
          Alarm_init(ctx->a_random);
        }
      }

      if (ctx->random_mode == BR_SCHEMES) {
        if ((NULL == schemes) || (!Shuffler_ok(schemes->shuffler))) {
          printf("[i] Skipping random schemes since there are no schemes available\n");
          ctx->random_mode = BR_BOTH;
        } else {
          Schemes_random(ctx, 0);
          Alarm_init(ctx->a_random);
        }
      }

      Context_display_random(ctx);
      res = random_modes_result(ctx);
      break;

    case CMD_APP_NEXT_SEQUENCE: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_NEXT_SEQUENCE\n");
#endif
      Context_next_sequence(ctx);
      res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, ctx->sm->cur->name));
    }
      break;

#ifdef WITH_WEBCAM
    case CMD_APP_NEXT_WEBCAM:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_NEXT_WEBCAM\n");
#endif
      if (ctx->webcams) {
        ctx->cam = (ctx->cam + 1) % ctx->webcams;
      } else {
        printf("[i] No webcams in use\n");
      }
      break;
#endif

    case CMD_APP_RANDOM_SCHEME: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_RANDOM_SCHEME\n");
#endif
      if (NULL != schemes) {
        Schemes_random(ctx, 0);
        Alarm_init(ctx->a_random);
        res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, UNSAVED_SEQUENCE));
      }
    }
      break;

    case CMD_APP_RANDOM_SEQUENCE: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_RANDOM_SEQUENCE\n");
#endif
      assert(NULL != sequences);
      if (sequences->size > 1) {
        Context_random_sequence(ctx);
        Alarm_init(ctx->a_random);
        res = json_pack("{so}", "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, ctx->sm->cur->name));
      }
    }
      break;

    case CMD_APP_RANDOMIZE_3D_ROTATIONS:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_RANDOMIZE_3D_ROTATIONS\n");
#endif
#ifdef DEBUG
      printf("[i] Randomizing rotations\n");
#endif
      Params3d_randomize(&ctx->params3d);
      res = Params3d_to_json(&ctx->params3d);
      break;

    case CMD_APP_RANDOMIZE_SCREEN:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_RANDOMIZE_SCREEN\n");
#endif
      Buffer8_randomize(active_buffer(ctx));
      Buffer8_randomize(passive_buffer(ctx));
      break;

    case CMD_APP_SCREENSHOT:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SCREENSHOT\n");
#endif
      ctx->take_screenshot = 1;
      break;

#ifdef WITH_WEBCAM
    case CMD_APP_SET_WEBCAM_REFERENCE:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SET_WEBCAM_REFERENCE\n");
#endif
      if (ctx->webcams) {
        if (!xpthread_mutex_lock(&ctx->cam_mtx[ctx->cam])) {
          Buffer8_copy(ctx->cam_save[ctx->cam][0], ctx->cam_ref[ctx->cam]);
          xpthread_mutex_unlock(&ctx->cam_mtx[ctx->cam]);
        }
      } else {
        printf("[i] No webcams in use\n");
      }
      break;
#endif

    case CMD_APP_STOP_AUTO_MODES:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_STOP_AUTO_MODES\n");
#endif
      printf("[!] *** EMERGENCY STOP *** button pressed !!!\n");
      /* Emergency stop, all auto_* modes -> disabled */
      ctx->auto_colormaps = ctx->auto_images = 0;
      ctx->sm->cur->auto_colormaps = ctx->sm->cur->auto_images = -1;
      Context_update_auto(ctx);
      Sequence_changed(ctx->sm->cur);
      ctx->random_mode = BR_NONE;
      ctx->locked = NULL;

      /* Stop and reset 3D rotations, remove boundary */
      zero_3d(&ctx->params3d);
      ctx->params3d.boundary = BOUNDARY_NONE;
      break;

    case CMD_APP_SWITCH_BYPASS:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SWITCH_BYPASS\n");
#endif
      ctx->bypass = !ctx->bypass;
      break;

    case CMD_APP_SWITCH_CURSOR:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SWITCH_CURSOR\n");
#endif
      for ( ; NULL != outputs; outputs = g_slist_next(outputs)) {
        Plugin_t *output = (Plugin_t *)outputs->data;

        if (NULL != output->switch_cursor) {
          output->switch_cursor();
        }
      }
      break;

    case CMD_APP_SWITCH_FULLSCREEN:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SWITCH_FULLSCREEN\n");
#endif
      ctx->fullscreen = !ctx->fullscreen;
      printf("[S] Full-screen %s\n", ctx->fullscreen ? "on" : "off");

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

        if (NULL != output->fullscreen) {
          output->fullscreen(ctx->fullscreen);
        }
      }
      res = json_pack("{sb}", "fullscreen", ctx->fullscreen);
      break;

    case CMD_APP_TOGGLE_AUTO_COLORMAPS:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_TOGGLE_AUTO_COLORMAPS\n");
#endif
      if ((NULL != colormaps) && (colormaps->size > 1)) {
        ctx->sm->cur->auto_colormaps = ctx->auto_colormaps = !ctx->auto_colormaps;
        Sequence_changed(ctx->sm->cur);
        Context_update_auto(ctx);
      }
      res = json_pack("{sbsb}", "autoColormaps", ctx->auto_colormaps, "autoImages", ctx->auto_images);
      break;

    case CMD_APP_TOGGLE_AUTO_IMAGES:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_TOGGLE_AUTO_IMAGES\n");
#endif
      if ((NULL != images) && (images->size > 1)) {
        ctx->sm->cur->auto_images = ctx->auto_images = !ctx->auto_images;
        Sequence_changed(ctx->sm->cur);
        Context_update_auto(ctx);
      }
      res = json_pack("{sbsb}", "autoColormaps", ctx->auto_colormaps, "autoImages", ctx->auto_images);
      break;

    case CMD_APP_TOGGLE_3D_ROTATIONS:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_TOGGLE_3D_ROTATIONS\n");
#endif
      if (Params3d_is_rotating(&ctx->params3d)) {
        zero_3d(&ctx->params3d);
      } else {
        /* By default, rotate around the Y axis */
        ctx->params3d.rotate_factor[X_AXIS] = 0;
        ctx->params3d.rotate_factor[Y_AXIS] = 5;
        ctx->params3d.rotate_factor[Z_AXIS] = 0;
      }
      res = Params3d_to_json(&ctx->params3d);
      break;

    case CMD_APP_TOGGLE_SELECTED_PLUGIN: {
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_TOGGLE_SELECTED_PLUGIN\n");
#endif
      if (NULL != Sequence_find(ctx->sm->cur, plugins->selected)) {
        Context_remove_plugin(ctx, plugins->selected);
      } else {
        if (g_list_length(ctx->sm->cur->layers) < MAX_SEQ_LEN) {
          Context_insert_plugin(ctx, plugins->selected);
        }
      }

      res = json_pack("{ss so}", "selectedPlugin", plugins->selected->name, "sequence", Sequence_to_json(ctx, ctx->sm->cur, 1, 0, (NULL == ctx->sm->cur->name) ? UNSAVED_SEQUENCE : ctx->sm->cur->name));
      if (NULL != plugins->selected->parameters) {
        json_object_set_new(res, "parameters", plugins->selected->parameters(ctx, NULL, 0));
        json_object_set_new(res, "selectedParam", json_integer(plugins->selected->selected_param));
      }
    }
      break;

    case CMD_APP_VOLUME_SCALE_DOWN:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_VOLUME_SCALE_DOWN\n");
#endif
      if (NULL != ctx->input) {
        Input_volume_downscale(ctx->input);
        res = json_pack("{sf}", "volumeScale", ctx->input->volume_scale);
      }
      break;

    case CMD_APP_VOLUME_SCALE_UP:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_VOLUME_SCALE_UP\n");
#endif
      if (NULL != ctx->input) {
        Input_volume_upscale(ctx->input);
        res = json_pack("{sf}", "volumeScale", ctx->input->volume_scale);
      }
      break;

    case CMD_APP_QUIT:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_QUIT\n");
#endif
      ctx->running = 0;
      break;

    case CMD_APP_SAVE_QUIT:
#ifdef DEBUG_COMMANDS
      printf(">>> CMD_APP_SAVE_QUIT\n");
#endif
      Sequence_save(ctx, 0, TRUE, ctx->auto_colormaps, ctx->auto_images);
      ctx->running = 0;
      break;

    default:
      break;
  }

  return res;
}


static json_t *
Banks_command(Context_t *ctx, const enum Command cmd)
{
  json_t *res = NULL;

  // Clear a bank
  if ((cmd >= CMD_APP_CLEAR_BANK_1) && (cmd <= CMD_APP_CLEAR_BANK_20)) {
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_APP_CLEAR_BANK\n");
#endif
    uint8_t bank = cmd - CMD_APP_CLEAR_BANK_1;
#ifdef DEBUG
    printf("[i] Clear bank %d-%d\n", ctx->bank_set + 1, bank + 1);
#endif
    Context_clear_bank(ctx, bank);
    Context_save_banks(ctx);
    res = Context_get_bank_set(ctx, ctx->bank_set);
  }

  // Store in a bank
  if ((cmd >= CMD_APP_STORE_BANK_1) && (cmd <= CMD_APP_STORE_BANK_24)) {
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_APP_STORE_BANK\n");
#endif
    uint8_t bank = cmd - CMD_APP_STORE_BANK_1;
    if (NULL == ctx->sm->cur->name) {
      Sequence_save(ctx, 0, FALSE, ctx->sm->cur->auto_colormaps, ctx->sm->cur->auto_images);
    }
#ifdef DEBUG
    printf("[i] Store bank %d-%d\n", ctx->bank_set + 1, ctx->bank + 1);
#endif
    Context_store_bank(ctx, bank);
    Context_save_banks(ctx);
    res = Context_get_bank_set(ctx, ctx->bank_set);
  }

  // Use a bank
  if ((cmd >= CMD_APP_USE_BANK_1) && (cmd <= CMD_APP_USE_BANK_24)) {
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_APP_USE_BANK\n");
#endif
    Context_use_bank(ctx, cmd - CMD_APP_USE_BANK_1);
    res = Context_get_bank_set(ctx, ctx->bank_set);
  }

  // Change bank set
  if ((cmd >= CMD_APP_USE_BANK_SET_1) && (cmd <= CMD_APP_USE_BANK_SET_24)) {
    ctx->bank_set = cmd - CMD_APP_USE_BANK_SET_1;
    ctx->bank = 0;
#ifdef DEBUG
    printf("[i] Using bank set %d\n", ctx->bank_set + 1);
#endif
    res = Context_get_bank_set(ctx, ctx->bank_set);
  }

  return res;
}


json_t *
Context_process_command(Context_t *ctx, const enum Command cmd)
{
  json_t *res = NULL;

  if ((cmd >= CMD_COL_USE_SHORTCUT_1) && (cmd <= CMD_COL_USE_SHORTCUT_10)) {
    // Shortcuts
    uint8_t shortcut = cmd - CMD_COL_USE_SHORTCUT_1;
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_COL_USE_SHORTCUT_%d\n", shortcut + 1);
#endif

    uint32_t id = ctx->shortcuts[SH_COLORMAP][shortcut];
    if (id) {
      ctx->sm->cur->cmap_id = id;
      ctx->cf->fader->target = Colormaps_index(id);
      CmapFader_set(ctx->cf);
      res = json_pack("{ss}", "colormap", Colormaps_name(id));
    }
  } else if ((cmd >= CMD_COL_STORE_SHORTCUT_1) && (cmd <= CMD_COL_STORE_SHORTCUT_10)) {
    uint8_t shortcut = cmd - CMD_COL_STORE_SHORTCUT_1;
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_COL_STORE_SHORTCUT_%d: id= %d\n", shortcut + 1, ctx->sm->cur->cmap_id);
#endif
    ctx->shortcuts[SH_COLORMAP][shortcut] = ctx->sm->cur->cmap_id;
    Context_save_shortcuts(ctx);
    res = json_pack("{si si}", "shortcut", shortcut, "id", ctx->shortcuts[SH_COLORMAP][shortcut]);
  } else if ((cmd >= CMD_IMG_USE_SHORTCUT_1) && (cmd <= CMD_IMG_USE_SHORTCUT_10)) {
    uint8_t shortcut = cmd - CMD_IMG_USE_SHORTCUT_1;
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_IMG_USE_SHORTCUT_%d\n", shortcut + 1);
#endif

    uint32_t id = ctx->shortcuts[SH_IMAGE][shortcut];
    if (id) {
      ctx->sm->cur->image_id = id;
      ctx->imgf->fader->target = Images_index(id);
      ImageFader_set(ctx->imgf);
      res = json_pack("{ss}", "image", Images_name(id));
    }
  } else if ((cmd >= CMD_IMG_STORE_SHORTCUT_1) && (cmd <= CMD_IMG_STORE_SHORTCUT_10)) {
    uint8_t shortcut = cmd - CMD_IMG_STORE_SHORTCUT_1;
#ifdef DEBUG_COMMANDS
    printf(">>> CMD_IMG_STORE_SHORTCUT_%d: id= %d\n", shortcut + 1, ctx->sm->cur->image_id);
#endif
    ctx->shortcuts[SH_IMAGE][shortcut] = ctx->sm->cur->image_id;
    res = json_pack("{si si}", "shortcut", shortcut, "id", ctx->shortcuts[SH_IMAGE][shortcut]);
    Context_save_shortcuts(ctx);
  } else if ((cmd > START_Application) && (cmd < END_Application)) {
    // Other commands
    res = Context_command(ctx, cmd);
  } else if ((cmd > START_Plugins) && (cmd < END_Plugins)) {
    res = Plugins_command(ctx, plugins, cmd);
  } else if ((cmd > START_Colormaps) && (cmd < END_Colormaps)) {
    res = CmapFader_command(ctx->cf, cmd);
    if (NULL != res) {
      Sequence_changed(ctx->sm->cur);
    }
  } else if ((cmd > START_Images) && (cmd < END_Images)) {
    res = ImageFader_command(ctx->imgf, cmd);
    if (NULL != res) {
      Sequence_changed(ctx->sm->cur);
    }
  } else if ((cmd > START_Sequences) && (cmd < END_Sequences)) {
    // SequenceManager_command() will handle the SequenceManager lock
    res = SequenceManager_command(ctx, cmd, ctx->auto_colormaps, ctx->auto_images);
  } else if ((cmd > START_Banks) && (cmd < END_Banks)) {
    res = Banks_command(ctx, cmd);
  } else {
    fprintf(stderr, "[!] Unhandled command %d\n", cmd);
    res = json_pack("{ss}", "error", "No such command");
  }

  return res;
}
