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

uint32_t version = 0;
uint32_t options = BO_DISPLACE | BO_WARP | BO_LENS | BO_NOT_LENS;
char desc[] = "Warp effect";
char dname[] = "Warp";

struct warp {
  void  *offstable;
  short *disttable;
  void  *source;
  void  *framebuf;

  short ctable [1024];
  short sintable [1024+256];
} Warp;


static void
initSinTable(void)
{
  short *tptr, *tsinptr;
  float i;

  tsinptr = tptr = Warp.sintable;

  for (i = 0; i < 1024; i++) {
    *tptr++ = (int)(sin(i * M_PI / 512.0) * 32767.0);
  }

  for (i = 0; i < 256; i++) {
    *tptr++ = *tsinptr++;
  }
}


static void
initOffsTable(Pixel_t *source)
{
  void  **offptr;
  uint16_t y;

  offptr = (void**)Warp.offstable;

  for (y = 0; y < HEIGHT; y++) {
    *offptr++ = (void *)source;
    source += WIDTH;
  }
}


static void
initDistTable(void)
{
  short halfw, halfh, *distptr;
  float x,y,m;

  halfw = HWIDTH;
  halfh = HHEIGHT;

  distptr = Warp.disttable;

  m = sqrt ((float)(halfw*halfw + halfh*halfh));

  for (y = -halfh; y < halfh; y++)
    for (x= -halfw; x < halfw; x++) {
      *distptr++ = ((int) ( (sqrt ((float)(x*x+y*y)) * 511.9999) / m)) << 1;
    }
}


static void
initWarp(Context_t *ctx)
{
  Warp.offstable = xcalloc(HEIGHT, sizeof(char *));
  Warp.disttable = xcalloc(BUFFSIZE, sizeof(short));
  Warp.framebuf  = xcalloc(BUFFSIZE, sizeof(char));
  initSinTable();
  initDistTable();
}


static void
disposeWarp(void)
{
  xfree(Warp.framebuf);
  xfree(Warp.disttable);
  xfree(Warp.offstable);
}


static void
doWarp8bpp(int xw, int yw, int cw)
{
  short c, i, dx, dy;
  uint16_t x, y;
  short *ctable, *ctptr, *distptr;
  short *sintable, *disttable;
  Pixel_t  *destptr, **offstable;

  ctptr = ctable = &(Warp.ctable[0]);
  sintable = &(Warp.sintable[0]);
  offstable = (Pixel_t **)Warp.offstable;
  distptr = disttable = Warp.disttable;
  destptr = (Pixel_t *)Warp.framebuf;

  c = 0;

  for (x = 0; x < 512; x++) {
    i = (c >> 3) & 0x3FE;
    *ctptr++ = ((sintable[i] * yw) >> 15);
    *ctptr++ = ((sintable[i+256] * xw) >> 15);
    c += cw;
  }

  for (y = 0; y < HEIGHT; y++)
    for (x = 0; x < WIDTH; x++) {
      i = *distptr++;
      dx = ctable [i+1] + x;
      dy = ctable [i] + y;

      if (dx < MINX) {
        goto clipxmin;
      }
      if (dx > MAXX) {
        goto clipxmax;
      }
xclipok:
      if (dy < MINY) {
        goto clipymin;
      }
      if (dy > MAXY) {
        goto clipymax;
      }
yclipok:
      *destptr++ = * (offstable[dy] + dx);
    }
  return;

clipxmin:
  dx = 0;
  goto xclipok;
clipxmax:
  dx = MAXX;
  goto xclipok;
clipymin:
  dy = 0;
  goto yclipok;
clipymax:
  dy = MAXY;
  goto yclipok;
}


int8_t
create(Context_t *ctx)
{
  initWarp(ctx);

  return 1;
}


void
destroy(Context_t *ctx)
{
  disposeWarp();
}


void
run(Context_t *ctx)
{
  static short tval = 0;
  short xw, yw, cw;

  initOffsTable(active_buffer(ctx)->buffer);

  xw  = (int)(sin((float)(tval + 100.0) * M_PI / 128.0) * 30.0);
  yw  = (int)(sin((float)(tval) * M_PI / 256.0) * -35.0);
  cw  = (int)(sin((float)(tval - 70.0) * M_PI / 64.0) * 50.0);
  xw += (int)(sin((float)(tval - 10.0) * M_PI / 512.0) * 40.0);
  yw += (int)(sin((float)(tval + 30.0) * M_PI / 512.0) * 40.0);

  doWarp8bpp(xw, yw, cw);
  tval = (tval + 1) & 511;
  memcpy(passive_buffer(ctx)->buffer, Warp.framebuf, BUFFSIZE*sizeof(Pixel_t));
}
