/*
 *  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/>.
 */

#ifndef __INFINITY_H
#define __INFINITY_H

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


typedef struct {
  gfloat x,y;
} t_complex;

typedef struct {
  guint32 coord ; /**< Coordinates of the top left pixel. */
  guint32 weight; /**< 32 bits = 4*8 = weights of the four corners */
} t_interpol;

typedef struct {
  gint32 width;       /**< number of vectors */
  gint32 height;      /**< length of each vector */
  t_interpol *vector; /**< pointer to the vector field */
} vector_field_t;

typedef struct {
  uint8_t nb_fct;
  void   (*new)(struct Context_s *); /* constructor */
  t_complex (*fct)(t_complex, guint32, gint32, gint32);
  vector_field_t *vector_field;
} VectorField_t;

struct args_s {
  guint32 f;
  guint32 height;
  VectorField_t *vf;
};


static void
compute_generate_sector(guint32 g, guint32 f, guint32 p1, guint32 p2,
                        guint32 debut, guint32 step, VectorField_t *vf)
{
  vector_field_t *vector_field = vf->vector_field;
  const guint32 width = (guint32)vector_field->width;
  const guint32 height = (guint32)vector_field->height;
  const guint32 prop_transmitted = 249;
  const guint32 b_add = g * width * height;
  t_interpol *vector = vector_field->vector;
  guint32 fin = debut + step;
  guint32 cx, cy;

  if (fin > height) {
    fin = height;
  }

  for (cy = debut; cy < fin; cy++)
    for (cx = 0; cx < width; cx++) {
      t_complex a;
      gfloat fpy;
      guint32 rw, lw, add;
      guint32 w1, w2, w3, w4;
      guint32 x, y;

      a.x = (gfloat)cx;
      a.y = (gfloat)cy;
      a = vf->fct(a, f, p1, p2);
      add = cx + cy * width;
      x = (guint32)(a.x);
      y = (guint32)(a.y);
      vector[b_add+add].coord = (x << 16) | y;

      fpy = a.y - floor (a.y);
      rw = (guint32)((a.x - floor (a.x)) * prop_transmitted);
      lw = prop_transmitted - rw;
      w4 = (guint32)(fpy * rw);
      w2 = rw - w4;
      w3 = (guint32)(fpy * lw);
      w1 = lw - w3;
      vector[b_add + add].weight = (w1 << 24) | (w2 << 16) | (w3 << 8) | w4;
    }
}


static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static uint8_t thrs = 0;


void *
compute_generate_vector_field_loop(void *ptr)
{
  struct args_s *args = (struct args_s *)ptr;
  guint32 i;

  for (i = 0; i < args->height; i += 10) {
    compute_generate_sector(args->f, args->f, 2, 2, i, 10, args->vf);
  }

  xfree(args);
  if (!xpthread_mutex_lock(&mutex)) {
    thrs--;
    VERBOSE(printf(" %d", thrs));
    fflush(stdout);
    if (!thrs) { // all done
      VERBOSE(printf("\n"));
      pthread_cond_signal(&cond);
    }
    xpthread_mutex_unlock(&mutex);
  }
  pthread_exit(NULL);
}


void
compute_generate_vector_field(VectorField_t *vf)
{
  guint32 f, height;
  pthread_t *threads = NULL;

  threads = xcalloc(vf->nb_fct, sizeof(pthread_t));

  height = (guint32)vf->vector_field->height;

  VERBOSE(printf("[i] infinity: Launching %d threads\n", vf->nb_fct));
  thrs = vf->nb_fct;
  if (!xpthread_mutex_lock(&mutex)) {
    for (f = 0; f < vf->nb_fct; f++) {
      struct args_s *args = xcalloc(1, sizeof(struct args_s));

      args->f = f;
      args->height = height;
      args->vf = vf;

      xpthread_create(&threads[f], NULL, (void *(*)(void *))compute_generate_vector_field_loop, (void *)args);
    }
    VERBOSE(printf("[i] infinity: Waiting for %d threads:", vf->nb_fct));
    while (thrs) {
      pthread_cond_wait(&cond, &mutex);
    }
    xpthread_mutex_unlock(&mutex);
  }

  for (f = 0; f < vf->nb_fct; f++) {
    xpthread_join(threads[f], NULL);
  }
  xfree(threads);
}


vector_field_t *
VectorField_vector_field_new(const uint8_t nb_fct)
{
  vector_field_t *field;

  field = g_new0(vector_field_t, 1);
  field->vector = g_new0(t_interpol, BUFFSIZE * nb_fct);
  field->width = WIDTH;
  field->height = HEIGHT;

  return field;
}


VectorField_t *
VectorField_new(const uint8_t nb_fct, t_complex (*fct)(t_complex, guint32, gint32, gint32))
{
  VectorField_t *vf = xcalloc(1, sizeof(VectorField_t));

  vf->nb_fct = nb_fct;
  vf->fct = fct;
  vf->vector_field = VectorField_vector_field_new(nb_fct);

  compute_generate_vector_field(vf);

  return vf;
}


void
VectorField_delete(VectorField_t *vf)
{
  if (NULL != vf) {
    g_free(vf->vector_field->vector);
    g_free(vf->vector_field);
    xfree(vf);
  }
}


void
VectorField_compute_surface(Context_t *ctx, t_interpol *vector, gint32 width, gint32 height)
{
  gint32 i,j;
  gint32 add_dest = 0;
  guint32 add_src;
  t_interpol *interpol;
  Pixel_t *ptr_pix;
  guint32 color;
  Pixel_t *surface1 = active_buffer(ctx)->buffer;
  Pixel_t *surface2 = passive_buffer(ctx)->buffer;

  for (j = 0; j < height; j++)
    for (i = 0; i < width; i++) {
      interpol = &vector[add_dest];
      add_src = (interpol->coord & 0xFFFF) * width + (interpol->coord >> 16);
      ptr_pix = &((Pixel_t *)surface1)[add_src];
      color = ((guint32)(*(ptr_pix)) * (interpol->weight >> 24)
               + (guint32)(*(ptr_pix+1)) * ((interpol->weight & 0xFFFFFF) >> 16)
               + (guint32)(*(ptr_pix+width)) * ((interpol->weight & 0xFFFF) >> 8)
               + (guint32)(*(ptr_pix+width+1)) * (interpol->weight & 0xFF)) >> 8;
      if (color > 255) {
        surface2[add_dest] = (Pixel_t)255;
      } else {
        surface2[add_dest] = (Pixel_t)color;
      }
      add_dest++;
    }
}


void
VectorField_run(VectorField_t *vf, Context_t *ctx, uint32_t vector_index)
{
  vector_index *= BUFFSIZE;
  VectorField_compute_surface(ctx, &(vf->vector_field->vector[vector_index]),
                              vf->vector_field->width, vf->vector_field->height);
}

#endif /* __INFINITY_H */
