/*
 *  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 "pthread_utils.h"


uint32_t version = 0;
uint32_t options = BO_DISPLACE | BO_LENS | BO_NOT_LENS;
enum LayerMode mode = LM_NORMAL;
char desc[] = "Old-school rotozoom effect";
char dname[] = "Rotozoom";

// Adapted from http://www.hugi.scene.org/online/coding/hugi%2012%20-%20corzoom.htm
// See also https://seancode.com/demofx/:
// https://github.com/mrkite/demofx/blob/master/src/rotozoom.ts

#define TABLES 360

static uint32_t *tables[TABLES];
static uint16_t angle = 0;

static pthread_t thread;
static int16_t ready = -1;
static void warmup(Context_t *ctx);
static void full_throttle(Context_t *ctx);
static void (*run_ptr)(Context_t *) = &warmup;


static void *
compute_tables(void *args)
{
  Context_t *ctx = (Context_t *)args;
  float t_rad = 0;
  float dt_rad = M_PI / 180;

  for (uint16_t t = 0; t < TABLES; t++, t_rad += dt_rad) {
    const float c = cosf(t_rad);
    const float s = sinf(t_rad);
    uint32_t i = 0;

    if (!ctx->running) {
      return NULL;
    }

    tables[t] = xcalloc(BUFFSIZE, sizeof(uint32_t));

    const float s1 = s + 1.0f;
    float s_s1 = s * s1;
    float c_s1 = c * s1;

    float m_y_s_s1 = 0;
    float y_c_s1 = 0;

    for (uint16_t y = 0; y < HEIGHT; y++, m_y_s_s1 -= s_s1, y_c_s1 += c_s1) {
      float u0 = m_y_s_s1;
      float v0 = y_c_s1;
      for (uint16_t x = 0; x < WIDTH; x++, u0 += c_s1, v0 += s_s1) {
        int32_t u = (int32_t)roundf(u0) % WIDTH;
        int32_t v = (int32_t)roundf(v0) % HEIGHT;

        if (u < 0) {
          u += WIDTH;
        }
        if (v < 0) {
          v += HEIGHT;
        }
        tables[t][i++] = v * WIDTH + u;
      }
    }

    ready++;
  }

  run_ptr = &full_throttle;

  return NULL;
}


int
create(Context_t *ctx)
{
  for (uint16_t i = 0; i < TABLES; i++) {
    tables[i] = NULL;
  }
  xpthread_create(&thread, NULL, compute_tables, (void *)ctx);

  return 1;
}


void
on_switch_on(Context_t *ctx)
{
  angle = 0;
}


static void
rotozoom(Context_t *ctx)
{
  const Buffer8_t *src = active_buffer(ctx);
  Buffer8_t *dst = passive_buffer(ctx);
  const uint32_t *t = tables[angle];

  Buffer8_clear(dst);
  for (uint32_t i = 0; i < BUFFSIZE; i++) {
    dst->buffer[i] = src->buffer[t[i]];
  }

  angle++;
  angle %= 360;
}


static void
full_throttle(Context_t *ctx)
{
  rotozoom(ctx);
}


static void
warmup(Context_t *ctx)
{
  if (angle <= ready) {
    rotozoom(ctx);
  }
}


void
run(Context_t *ctx)
{
  run_ptr(ctx);
}


void
destroy(Context_t *ctx)
{
  xpthread_join(thread, NULL);
  for (uint16_t t = 0; t < TABLES; t++) {
    xfree(tables[t]);
  }
}
