/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2019-2022 Laurent Marsac
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou is free software: you can rediswindowing_factoribute 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 diswindowing_factoributed 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/>.
 */

/*
 * Plot selected path, oscillo version
 */

#include "context.h"
#include "paths.h"
#include "parameters.h"
#include "path.h"
#include "oscillo.h"
#include "pthread_utils.h"


uint32_t options = BO_GFX | BO_SFX | BO_SCHEMES;
uint32_t version = 0;
enum LayerMode mode = LM_OVERLAY;

char dname[] = "Path oscillo";
char desc[]  = "Path oscillo";

static Porteuse_t *P = NULL;

static int    plot_length  = 50; /* length of path to plot on each run */
static double volume_scale = 1;
static int    oscillo_length_factor = 1; /* used to define oscillo sampling */
static double windowing_factor = 0.2; /* Tukey window constant, between 0 and 1 */
static pthread_mutex_t mutex;


void
init_path(uint16_t id)
{
  if (!xpthread_mutex_lock(&mutex)) {
    xfree(path);

    path_length = paths->paths[id]->size;
    path        = xcalloc(path_length, sizeof(Path_point_t));

    Path_scale_and_center(path, paths->paths[id]->data, path_length, scale);
    xpthread_mutex_unlock(&mutex);
  }
}


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

  plugin_parameters_add_double(params, BPP_VOLUME_SCALE, volume_scale, 0.1, 10, 0.1, "Volume scale");
  plugin_parameters_add_int(params, BPP_LENGTH, plot_length, 1, 1000, 1, "Length");
  plugin_parameters_add_int(params, BPP_OSCILLO_LENGTH_FACTOR, oscillo_length_factor, 1, 10, 1, "Oscilloscope length factor"); // to check, compare to length
  plugin_parameters_add_double(params, BPP_WINDOWING_FACTOR, windowing_factor, 0, 1, 0.01, "Windowing factor");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  uint8_t reinit_path = 0;

  reinit_path |= set_parameters_path(ctx, in_parameters);
  plugin_parameter_parse_double_range(in_parameters, BPP_VOLUME_SCALE, &volume_scale);
  plugin_parameter_parse_int_range(in_parameters, BPP_LENGTH, &plot_length);
  plugin_parameter_parse_int_range(in_parameters, BPP_OSCILLO_LENGTH_FACTOR, &oscillo_length_factor);
  plugin_parameter_parse_double_range(in_parameters, BPP_WINDOWING_FACTOR, &windowing_factor);

  if (reinit_path) {
    init_path(path_id);
  }
}


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);
}


uint8_t
create(Context_t *ctx)
{
  if (NULL == paths) {
    return 0;
  } else {
    xpthread_mutex_init(&mutex, NULL);
    init_path(path_id);

    return 1;
  }
}


void
destroy(Context_t *ctx)
{
  Porteuse_delete(P);
  xfree(path);
  xpthread_mutex_destroy(&mutex);
}


void
init_oscillo(Context_t *ctx, uint16_t length)
{
  Point2d_t last;

  /* reinit path if selection changed */
  if (path_idx == 0) {
    if (path_id_changed) {
      init_path(path_id);
      path_id_changed = 0;
    }

    last.x = path[path_length - 1].x;
    last.y = path[path_length - 1].y;
  } else {
    /* used to connect to to previous run */
    last.x = path[path_idx - 1].x;
    last.y = path[path_idx - 1].y;
  }

  /* reset plot_length if too big (usefull for osd) */
  plot_length = MIN((uint32_t)plot_length, path_length);

  /* if end of path is crossed durring this round, reduce length
     so that the for loop ends exactly at path_length-1 */
  length = MIN((unsigned int)plot_length, path_length - path_idx);

  /* ensure oscillo_length_factor * length <= ctx->input->size/2 */
  if (length > 0) {
    oscillo_length_factor = MAX(MIN((uint32_t)oscillo_length_factor, ctx->input->size/2 / length), 1);
  }

  if (!xpthread_mutex_lock(&mutex)) {
    Porteuse_delete(P);
    P = Porteuse_new(length * oscillo_length_factor, A_MONO);

    /* oscillo */
    Transform_t t;
    memset(&t, 0, sizeof(t));

    t.v_j_factor = HMAXY * volume_scale;

    /* we want to divide "ctx->input->size" input in "length * oscillo_length_factor" overlapping windows */
    /* estimation of window overlap and size for color computation */
    uint32_t wo = ctx->input->size >> 1; /* overlap */
    uint32_t ws = floor((double)(ctx->input->size - wo) / (double)(length * oscillo_length_factor)) + wo;

    uint16_t r           = floor((double)P->size * windowing_factor);
    uint16_t factor_orig = t.v_j_factor;

    Point2d_t next;

    if (path[path_idx].connect == 0) {
      last.x = path[path_idx].x;
      last.y = path[path_idx].y;
    }
    P->origin = last;

    /* for each point to plot in "length" */
    for (uint16_t l = 0; l < length; l++, path_idx++) {
      uint16_t next_path_idx = (path_idx + 1) % path_length;
      next.x = path[next_path_idx].x;
      next.y = path[next_path_idx].y;

      float dist_coef = 1 / (float)oscillo_length_factor;

      Point2d_t diff_to_next = p2d_sub(&next, &last);
      last = next;

      for (uint16_t l2 = 0; l2 < (unsigned int)oscillo_length_factor; l2++) {
        uint16_t i = l * oscillo_length_factor + l2;

        P->connect[i] = path[path_idx].connect;

        /* compute vector used for current alpha and next origin */
        t.v_i = diff_to_next;
        if (path[next_path_idx].connect == 0) {
          if (l2 != (unsigned int)oscillo_length_factor - 1) {
            t.v_i.x = 0;
            t.v_i.y = 0;
          }
        } else {
          t.v_i.x *= dist_coef;
          t.v_i.y *= dist_coef;
        }

        /* Fix usefull when i == 0 and last and next are identical */
        if (fabs(t.v_i.x) < 1e-6 && fabs(t.v_i.y) < 1e-6) {
          t.v_i.x += 0.01;
        }

        /* tukey win */
        double tc;
        if (i < r / 2) {
          tc = cos(2 * M_PI * (i - r / 2) / r) / 2.0 + 0.5;
        } else if (i > P->size - r / 2) {
          tc = cos(2 * M_PI * (i - 1.0 + r / 2) / r) / 2.0 + 0.5;
        } else {
          tc = 1.0;
        }

        t.v_j_factor = floor((double)factor_orig * tc);

        uint32_t start = i * (ws - wo);
        uint32_t end = (i == length * oscillo_length_factor - 1) ? ctx->input->size : start + ws;
        end = MIN(start + ws, ctx->input->size);

        double win_avg = compute_avg_abs(ctx->input->data[A_MONO], start, end);

        P->color[i] = MIN(1.0, color_scale * win_avg) * PIXEL_MAXVAL;
        P->trans[i] = t;
      }
    }
    xpthread_mutex_unlock(&mutex);
  }

  if (path_idx == path_length) {
    path_idx = 0;
  }

  Porteuse_init_alpha(P);
}

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

  Buffer8_clear(dst);
  init_oscillo(ctx, plot_length);
  Porteuse_draw(P, ctx, 2);
}


void
on_switch_on(Context_t *ctx)
{
  // generic path parameters
  path_id = Shuffler_get(paths->shuffler);
  path_idx    = 0;
  color_scale = 1;
  scale = 1;
  // path_oscillo parameters
  plot_length   = 50;
  volume_scale = 1;
  oscillo_length_factor = 1;
  windowing_factor = 0.2;
  // re-init
  init_path(path_id);
}
