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


uint32_t version = 0;
uint32_t options = BO_SFX | BO_LAST | BO_SCHEMES;
char dname[] = "Recurrence plot";
enum LayerMode mode = LM_OVERLAY;
char desc[] = "Regular/cross recurrence plot of the input";

/* parameters */
static int cross = 0;
static void set_run_ptr(void);


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

  plugin_parameters_add_boolean(params, BPP_MODE, cross, "Normal or cross-recurrence plot");

  return params;
}


void
set_parameters(const Context_t *ctx, const json_t *in_parameters)
{
  int change_ptr = 0;

  // v1 API compat
  change_ptr |= plugin_parameter_parse_int_range(in_parameters, BPP_MODE, &cross) & PLUGIN_PARAMETER_CHANGED;

  // v2 API
  change_ptr |= plugin_parameter_parse_boolean(in_parameters, BPP_MODE, &cross) & PLUGIN_PARAMETER_CHANGED;

  if (change_ptr) {
    set_run_ptr();
  }
}


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


/*
 * Les datas sont dans [-1.0..+1.0] donc la distance
 * max entre 2 points 4d dans l'espace des phases est:
 * d= sqrt (dx2+dy2+dz2+dt2) avec dx, dy, dz et dt valant au max 2 (1 - -1)
 *
 * donc max = sqrt(16) -> 4
 */
#define DMAX 4.0

/*
 * Regular recurrence plot;
 *
 * http://astronomy.swin.edu.au/~pbourke/fractals/recurrence/
 *
 * More precisely, we draw a point at coordinate (i,j) if the i'th and j'th
 * embedded vectors are less than some distance r apart, eg: a point is drawn if
 * ||yi - yj|| < r
 *
 * i is plotted along the horizontal axis, j on the vertical axis.
 *
 * we do this in 4D
 */
static inline Pixel_t
get_color(const Input_t *input, const int i, const int j)
{
  float dist;

  /* get distance between the two vectors */
  float dx = input->data[A_MONO][i+0] - input->data[A_MONO][j+0];
  float dy = input->data[A_MONO][i+1] - input->data[A_MONO][j+1];
  float dz = input->data[A_MONO][i+2] - input->data[A_MONO][j+2];
  float dt = input->data[A_MONO][i+3] - input->data[A_MONO][j+3];

  dx *= dx;
  dy *= dy;
  dz *= dz;
  dt *= dt;

  dist = sqrtf(dx + dy + dz + dt);
  dist /= DMAX;

  return (255 - (Pixel_t)(255 * dist));
}


static void
recurrence_plot(Context_t *ctx)
{
  int ii, jj;
  Buffer8_t *dst = passive_buffer(ctx);

  Buffer8_clear(dst);

  for (jj = 0; jj < MINSCREEN; jj++) {
    int j = (int)((float)jj
                  / (float)MINSCREEN
                  * (float)(ctx->input->size-3.0));
    int last_i = -1;
    Pixel_t last_c = 0;

    for (ii = jj; ii < MINSCREEN; ii++) {
      Pixel_t c = last_c;
      int i = (int)((float)ii
                    / (float)MINSCREEN
                    * (float)(ctx->input->size-3.0));

      if (i != last_i) {
        c = get_color(ctx->input, i, j);
        last_i = i;
        last_c = c;
      }

      set_pixel_nc(dst, CENTERX-HMINSCREEN+ii+1, jj, c);
      set_pixel_nc(dst, CENTERX-HMINSCREEN+jj+1, ii, c);
    }
  }
}


/*
 * Cross recurrence plot:
 *
 * http://arxiv.org/pdf/physics/0201062 (.PDF)
 * http://www.agnld.uni-potsdam.de/~marwan/rp/crps.php
 */
static inline Pixel_t
get_color_cross(const Input_t *input, const int i, const int j)
{
  float dist;

  /* get distance between the two vectors */
  float dx = input->data[A_LEFT][i+0]-input->data[A_RIGHT][j+0];
  float dy = input->data[A_LEFT][i+1]-input->data[A_RIGHT][j+1];
  float dz = input->data[A_LEFT][i+2]-input->data[A_RIGHT][j+2];
  float dt = input->data[A_LEFT][i+3]-input->data[A_RIGHT][j+3];

  dx *= dx;
  dy *= dy;
  dz *= dz;
  dt *= dt;

  dist = sqrtf(dx + dy + dz + dt);
  dist /= DMAX;

  return (255 - (Pixel_t)(255 * dist));
}


static void
cross_recurrence_plot(Context_t *ctx)
{
  int ii, jj;
  Buffer8_t *dst = passive_buffer(ctx);

  Buffer8_clear(dst);

  for (jj = 0; jj < MINSCREEN; jj++) {
    int j = (int)((float)jj
                  / (float)MINSCREEN
                  * (float)(ctx->input->size-3.0));
    int last_i = -1;
    Pixel_t last_c = 0;

    for (ii = 0; ii < MINSCREEN; ii++) {
      Pixel_t c = last_c;
      int i = (int)((float)ii
                    / (float)MINSCREEN
                    * (float)(ctx->input->size-3.0));

      if (i != last_i) {
        c = get_color_cross(ctx->input, i, j);
        last_i = i;
        last_c = c;
      }

      set_pixel_nc(dst, CENTERX-HMINSCREEN+ii+1, jj, c);
    }
  }
}


static void (*run_ptr)(struct Context_s *) = &recurrence_plot;


static void
set_run_ptr(void)
{
  if (cross) {
    run_ptr = &cross_recurrence_plot;
  } else {
    run_ptr = &recurrence_plot;
  }
}


void
on_switch_on(Context_t *ctx)
{
  int rnd_cross = b_rand_boolean();

  if (cross != rnd_cross) {
    cross = rnd_cross;
    set_run_ptr();
  }
}


void
run(Context_t *ctx)
{
  if (!xpthread_mutex_lock(&ctx->input->mutex)) {
    run_ptr(ctx);
    xpthread_mutex_unlock(&ctx->input->mutex);
  }
}
