/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2019-2022 Laurent Marsac
 *
 *  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/>.
 */

/*
 * This is an improvement of the original "foo" plugin
 *   - The snake is mainly traveling left to right and bottom to top
 *   - Its behaviour depends on the spectrum
 *     - Low frequency: longer and standard direction
 *     - High frequency: shorter and incleasingly chaotic direction and orientation
 *
 *   - Color: linked to sound amplitude
 */

#include "context.h"
#include "parameters.h"
#include "pthread_utils.h"


uint32_t version = 0;
uint32_t options = BO_GFX | BO_UNIQUE | BO_SFX | BO_SCHEMES;

enum LayerMode mode = LM_OVERLAY;

char dname[] = "Snake";
char desc[] = "Snake";

static uint16_t x = 0, y = 0;

static int snake_mode = 1;
static double length_min = 0.01; /* minimum length of the snake, in pixels, scales with WIDTH */
static double length_max = 0.08; /* maximum length of the snake, in pixels, scales with WIDTH */
static double spectrum_id_factor = 2.0;
static double color_factor = 3; /* scaling of the computed color */


json_t *
get_parameters(const uint8_t fetch_all)
{
  json_t *params = json_object();

  plugin_parameters_add_int(params, BPP_MODE, snake_mode, 0, 2, 1, "Mode");
  plugin_parameters_add_double(params, BPP_LENGTH_MIN, length_min, 0.01, 0.2, 0.01, "Minimum length");
  plugin_parameters_add_double(params, BPP_LENGTH_MAX, length_max, 0.02, 0.5, 0.01, "Maximum length");
  plugin_parameters_add_double(params, BPP_SPECTRUM_ID_FACTOR, spectrum_id_factor, 0, 4, 0.01, "Spectrum id factor");
  plugin_parameters_add_double(params, BPP_COLOR_FACTOR, color_factor, 0, 5, 0.01, "Color factor");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  plugin_parameter_parse_int_range(in_parameters, BPP_MODE, &snake_mode);

  double __length_min = length_min, __length_max = length_max;
  plugin_parameter_parse_double_range(in_parameters, BPP_LENGTH_MIN, &__length_min);
  plugin_parameter_parse_double_range(in_parameters, BPP_LENGTH_MAX, &__length_max);
  if (__length_min <= __length_max) {
    length_min = __length_min;
    length_max = __length_max;
  }

  plugin_parameter_parse_double_range(in_parameters, BPP_SPECTRUM_ID_FACTOR, &spectrum_id_factor);
  plugin_parameter_parse_double_range(in_parameters, BPP_COLOR_FACTOR, &color_factor);
}


json_t *
parameters(const Context_t *ctx, const json_t *in_parameters, const uint8_t fetch_all)
{
  if (NULL != in_parameters) {
    set_parameters(ctx, in_parameters);
  }

  return get_parameters(fetch_all);
}


void
on_switch_on(Context_t *ctx)
{
  /* Initialize parameters */
  snake_mode = 1;    /* 0: direction changes at each run, 1: direction randomy changes, 2: also randomly changes orientation */
  length_min = 0.01; /* minimum length of the snake, between 0 and 1, 1 meaning WIDTH */
  length_max = 0.08; /* maximum length of the snake, between 0 and 1, 1 meaning WIDTH */
  spectrum_id_factor = 2.0; /* snake length will be length_max_px - average_frequency * spectrum_id_factor */
  color_factor = 3.0; /* scaling of the computed color */
}


void
run(Context_t *ctx)
{
  Buffer8_t *dst = passive_buffer(ctx);
  Buffer8_clear(dst);

  uint16_t original_fft_size = 513; /* FFT size used when below parameters were set */
  uint16_t length_min_px = round(length_min * WIDTH); /* minimum length of the snake, in pixels, scales with WIDTH */
  uint16_t length_max_px = round(length_max * WIDTH); /* maximum length of the snake, in pixels, scales with WIDTH */
  double spectrum_low_treshold_factor = 0.1; /* spectrum value higher than this treshold will be used, between 0 and 1 */
  uint16_t spectrum_id_orientation_factor = 40; /* smaller means changing orientation more often */

  /* choose direction and increment mode */
  uint16_t mode = snake_mode; /* 0: direction changes at each run, 1: direction randomy changes, 2: also randomly changes orientation */
  uint16_t change_inc_on_hf = 1; /* 0: no change, 1: change orientation more often on high frequency */

  if (!xpthread_mutex_lock(&ctx->input->mutex)) {
    uint16_t average_freq_id = compute_avg_freq_id(ctx->input, spectrum_low_treshold_factor);

    /* scale average frequency id depending of input->spectrum_size */
    average_freq_id = round((double)average_freq_id * (double)original_fft_size / (double)ctx->input->spectrum_size);

    /* compute snake length based on average frequency */
    uint16_t length = length_max_px - average_freq_id * spectrum_id_factor;
    if (length < length_min_px) {
      length = length_min_px;
    }
    if (length > length_max_px) {
      length = length_max_px;
    }

    static uint16_t dir = 0; /* direction: 0 is Y and 1 is X */
    short inc = 1; /* increment: 1 or -1 */

    switch (mode) {
    default:
    case 0:
      dir = !dir;
      break;

    case 1:
      dir = drand48() < .5;
      break;

    /* random dir and inc */
    case 2:
      dir = drand48() < .5;
      inc = drand48() < .5 ? -1 : 1;
      break;
    }

    /* if set, change orientation on high frequency */
    static short inc_hf = 1;
    if (change_inc_on_hf && (drand48() < average_freq_id / (double)spectrum_id_orientation_factor)) {
      inc_hf = -inc_hf;
      inc = inc_hf;
    }

    /* avoid going back on previous path */
    static uint16_t last_dir = 0;
    static uint16_t last_inc = -1;
    if (last_dir == dir) {
      inc = last_inc ;
    }
    last_dir = dir;
    last_inc = inc;

    /* remove length bias due to different HEIGHT and WIDTH */
    if (!dir) {
      length = (uint16_t)ceil((double)length * (double)HEIGHT / (double)WIDTH);
    }

    double win_avg = 0.0;
    /* approx */
    uint16_t win_overlap = ctx->input->size >> 1;
    uint16_t win_size = floor((double)(ctx->input->size - win_overlap) / (double)length) + win_overlap;

    /* X direction */
    if (dir) {
      if (y >= HEIGHT) {
        y = 0;
      } else if (y == 0) {
        y = MAXY;
      }

      for (uint16_t l = 0; l < length; l++) {
        /* compute color */
        if (l == length-1) {
          win_avg = compute_avg_abs(ctx->input->data[A_MONO], l*(win_size-win_overlap), ctx->input->size);
        } else {
          win_avg = compute_avg_abs(ctx->input->data[A_MONO], l*(win_size-win_overlap), l*(win_size-win_overlap)+win_size);
        }

        win_avg = color_factor * win_avg ;

        if (win_avg > 1.0) {
          win_avg = 1.0;
        }
        Pixel_t c = win_avg * PIXEL_MAXVAL;

        /* on border, go to the opposite border */
        if (x >= WIDTH) {
          x = 0;
        } else if (x == 0) {
          x = MAXX;
        }

        set_pixel_nc(dst, x, y, c);
        x += inc;
      }
    } else {
      /* Y direction */
      if (x >= WIDTH) {
        x = 0;
      } else if (x == 0) {
        x = MAXX;
      }

      for (uint16_t l = 0; l < length; l++) {
        /* compute color */
        if (l == length-1) {
          win_avg = compute_avg_abs(ctx->input->data[A_MONO], l*(win_size-win_overlap), ctx->input->size);
        } else {
          win_avg = compute_avg_abs(ctx->input->data[A_MONO], l*(win_size-win_overlap), l*(win_size-win_overlap)+win_size);
        }

        win_avg = color_factor * win_avg;

        if (win_avg > 1.0) {
          win_avg = 1.0 ;
        }
        Pixel_t c = win_avg * PIXEL_MAXVAL ;

        /* on border, go to the opposite border */
        if (y >= HEIGHT) {
          y = 0;
        } else if (y == 0) {
          y = MAXY;
        }

        set_pixel_nc(dst, x, y, c);
        y += inc;
      }
    }
    xpthread_mutex_unlock(&ctx->input->mutex);
  }
}
