/* Goom Project
 * Copyright (C) <2003> iOS-Software
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "goom_fx.h"
#include "goom_plugin_info.h"
#include "goom_config.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define CONV_MOTIF_W 32
//#define CONV_MOTIF_WMASK 0x1f

/* Define if you like the wacky GOOM logo: */
#undef DRAW_MOTIF

#define CONV_MOTIF_W 128
#define CONV_MOTIF_WMASK 0x7f

typedef char Motif[CONV_MOTIF_W][CONV_MOTIF_W];

#include "motif_goom1.h"
#include "motif_goom2.h"

#define NB_THETA 512

typedef struct _CONV_DATA
{
  PluginParam light;
  PluginParam factor_adj_p;
  PluginParam factor_p;
  PluginParameters params;

  /* rotozoom */
  int theta;
  float ftheta;
  int h_sin[NB_THETA];
  int h_cos[NB_THETA];
  int h_height;
  float visibility;
  Motif conv_motif;
  int inverse_motif;

} ConvData;

/* init rotozoom tables */
static void
compute_tables (VisualFX * _this, PluginInfo * info)
{
  ConvData *data = (ConvData *) _this->fx_data;
  double screen_coef;
  int i;
  double h;
  double radian;

  if (data->h_height == info->screen.height)
    return;

  screen_coef = 2.0 * 300.0 / (double) info->screen.height;
  data->h_height = info->screen.height;

  for (i = 0; i < NB_THETA; i++) {
    radian = 2 * i * G_PI / NB_THETA;
    h = (0.2 + cos (radian) / 15.0 * sin (radian * 2.0 + 12.123)) * screen_coef;
    data->h_cos[i] = 0x10000 * (-h * cos (radian) * cos (radian));
    data->h_sin[i] = 0x10000 * (h * sin (radian + 1.57) * sin (radian));
  }
}

static void
set_motif (ConvData * data, Motif motif)
{
  int i, j;

  for (i = 0; i < CONV_MOTIF_W; ++i)
    for (j = 0; j < CONV_MOTIF_W; ++j)
      data->conv_motif[i][j] =
          motif[CONV_MOTIF_W - i - 1][CONV_MOTIF_W - j - 1];
}

static void
convolve_init (VisualFX * _this, PluginInfo * info)
{
  ConvData *data;

  data = (ConvData *) malloc (sizeof (ConvData));
  _this->fx_data = (void *) data;

  secure_f_param (&data->light, "Screen Brightness");
  data->light.param.fval.max = 300.0f;
  data->light.param.fval.step = 1.0f;
  data->light.param.fval.value = 100.0f;

  secure_f_param (&data->factor_adj_p, "Flash Intensity");
  data->factor_adj_p.param.fval.max = 200.0f;
  data->factor_adj_p.param.fval.step = 1.0f;
  data->factor_adj_p.param.fval.value = 70.0f;

  secure_f_feedback (&data->factor_p, "Factor");

  plugin_parameters (&data->params, "Bright Flash", 5);
  data->params.params[0] = &data->light;
  data->params.params[1] = &data->factor_adj_p;
  data->params.params[2] = 0;
  data->params.params[3] = &data->factor_p;
  data->params.params[4] = 0;

  data->h_height = 0;

  /* init rotozoom tables */
  compute_tables (_this, info);
  data->theta = 0;
  data->ftheta = 0.0;
  data->visibility = 1.0;
  set_motif (data, CONV_MOTIF2);
  data->inverse_motif = 0;

  _this->params = &data->params;
}

static void
convolve_free (VisualFX * _this)
{
  ConvData *data = (ConvData *) _this->fx_data;

  goom_plugin_parameters_free (&data->params);

  free (_this->fx_data);
}

#ifdef DRAW_MOTIF
static void
create_output_with_brightness (VisualFX * _this, Pixel * src, Pixel * dest,
    PluginInfo * info, int iff)
{
  ConvData *data = (ConvData *) _this->fx_data;

  int x, y;
  int i = 0;                    //info->screen.height * info->screen.width - 1;

  const int c = data->h_cos[data->theta];
  const int s = data->h_sin[data->theta];

  const int xi = -(info->screen.width / 2) * c;
  const int yi = (info->screen.width / 2) * s;

  const int xj = -(info->screen.height / 2) * s;
  const int yj = -(info->screen.height / 2) * c;

  int xprime = xj;
  int yprime = yj;

  int ifftab[16];

  if (data->inverse_motif) {
    int i;

    for (i = 0; i < 16; ++i)
      ifftab[i] = (double) iff *(1.0 + data->visibility * (15.0 - i) / 15.0);
  } else {
    int i;

    for (i = 0; i < 16; ++i)
      ifftab[i] = (double) iff / (1.0 + data->visibility * (15.0 - i) / 15.0);
  }

  for (y = info->screen.height; y--;) {
    int xtex, ytex;

    xtex = xprime + xi + CONV_MOTIF_W * 0x10000 / 2;
    xprime += s;

    ytex = yprime + yi + CONV_MOTIF_W * 0x10000 / 2;
    yprime += c;

#ifdef HAVE_MMX
    __asm__ __volatile__ ("\n\t pxor  %%mm7,  %%mm7"    /* mm7 = 0   */
        "\n\t movd %[xtex],  %%mm2" "\n\t movd %[ytex],  %%mm3" "\n\t punpckldq %%mm3, %%mm2"   /* mm2 = [ ytex | xtex ] */
        "\n\t movd %[c],     %%mm4" "\n\t movd %[s],     %%mm6" "\n\t pxor  %%mm5,   %%mm5" "\n\t psubd %%mm6,   %%mm5" "\n\t punpckldq %%mm5, %%mm4"   /* mm4 = [ -s | c ]      */
        "\n\t movd %[motif], %%mm6"     /* mm6 = motif           */
        ::[xtex] "g" (xtex),[ytex] "g" (ytex)
        ,[c] "g" (c),[s] "g" (s)
        ,[motif] "g" (&data->conv_motif[0][0]));

    for (x = info->screen.width; x--;) {
      __asm__ __volatile__ ("\n\t movd  %[src], %%mm0"  /* mm0 = src */
          "\n\t paddd %%mm4, %%mm2"     /* [ ytex | xtex ] += [ -s | s ] */
          "\n\t movd  %%esi, %%mm5"     /* save esi into mm5 */
          "\n\t movq  %%mm2, %%mm3" "\n\t psrld  $16,  %%mm3"   /* mm3 = [ (ytex>>16) | (xtex>>16) ] */
          "\n\t movd  %%mm3, %%eax"     /* eax = xtex' */
          "\n\t psrlq $25,   %%mm3" "\n\t movd  %%mm3, %%ecx"   /* ecx = ytex' << 7 */
          "\n\t andl  $127, %%eax" "\n\t andl  $16256, %%ecx" "\n\t addl  %%ecx, %%eax" "\n\t movd  %%mm6, %%esi"       /* esi = motif */
          "\n\t xorl  %%ecx, %%ecx" "\n\t movb  (%%eax,%%esi), %%cl" "\n\t movl  %[ifftab], %%eax" "\n\t movd  %%mm5, %%esi"    /* restore esi from mm5 */
          "\n\t movd  (%%eax,%%ecx,4), %%mm1"   /* mm1 = [0|0|0|iff2] */
          "\n\t punpcklwd %%mm1, %%mm1"
          "\n\t punpcklbw %%mm7, %%mm0"
          "\n\t punpckldq %%mm1, %%mm1"
          "\n\t psrlw     $1,    %%mm0"
          "\n\t psrlw     $2,    %%mm1"
          "\n\t pmullw    %%mm1, %%mm0"
          "\n\t psrlw     $5,    %%mm0"
          "\n\t packuswb  %%mm7, %%mm0"
          "\n\t movd      %%mm0, %[dest]":[dest] "=g" (dest[i].val)
          :[src] "g" (src[i].val)
          ,[ifftab] "g" (&ifftab[0])
          :"eax", "ecx");

      i++;
    }
#else
    for (x = info->screen.width; x--;) {

      int iff2;
      unsigned int f0, f1, f2, f3;

      xtex += c;
      ytex -= s;

      iff2 =
          ifftab[(int) data->conv_motif[(ytex >> 16) & CONV_MOTIF_WMASK][(xtex
                  >> 16) & CONV_MOTIF_WMASK]];

#define sat(a) ((a)>0xFF?0xFF:(a))
      f0 = src[i].val;
      f1 = ((f0 >> R_OFFSET) & 0xFF) * iff2 >> 8;
      f2 = ((f0 >> G_OFFSET) & 0xFF) * iff2 >> 8;
      f3 = ((f0 >> B_OFFSET) & 0xFF) * iff2 >> 8;
      dest[i].val =
          (sat (f1) << R_OFFSET) | (sat (f2) << G_OFFSET) | (sat (f3) <<
          B_OFFSET);
/*
      f0 = (src[i].cop[0] * iff2) >> 8;
      f1 = (src[i].cop[1] * iff2) >> 8;
      f2 = (src[i].cop[2] * iff2) >> 8;
      f3 = (src[i].cop[3] * iff2) >> 8;

      dest[i].cop[0] = (f0 & 0xffffff00) ? 0xff : (unsigned char)f0;
      dest[i].cop[1] = (f1 & 0xffffff00) ? 0xff : (unsigned char)f1;
      dest[i].cop[2] = (f2 & 0xffffff00) ? 0xff : (unsigned char)f2;
      dest[i].cop[3] = (f3 & 0xffffff00) ? 0xff : (unsigned char)f3;
*/
      i++;
    }
#endif
  }
#ifdef HAVE_MMX
  __asm__ __volatile__ ("\n\t emms");
#endif

  compute_tables (_this, info);
}
#endif

/*#include <stdint.h>

static uint64_t GetTick()
{
  uint64_t x;
  asm volatile ("RDTSC" : "=A" (x));
  return x;
}*/


static void
convolve_apply (VisualFX * _this, Pixel * src, Pixel * dest, PluginInfo * info)
{

  ConvData *data = (ConvData *) _this->fx_data;
#ifdef DRAW_MOTIF
  float ff;
  int iff;

  ff = (FVAL (data->factor_p) * FVAL (data->factor_adj_p) +
      FVAL (data->light)) / 100.0f;
  iff = (unsigned int) (ff * 256);
#endif

  {
    double fcycle = (double) info->cycle;
    double rotate_param, rotate_coef;
    float INCREASE_RATE = 1.5;
    float DECAY_RATE = 0.955;

    if (FVAL (info->sound.last_goom_p) > 0.8)
      FVAL (data->factor_p) += FVAL (info->sound.goom_power_p) * INCREASE_RATE;
    FVAL (data->factor_p) *= DECAY_RATE;

    rotate_param = FVAL (info->sound.last_goom_p);
    if (rotate_param < 0.0)
      rotate_param = 0.0;
    rotate_param += FVAL (info->sound.goom_power_p);

    rotate_coef = 4.0 + FVAL (info->sound.goom_power_p) * 6.0;
    data->ftheta = (data->ftheta + rotate_coef * sin (rotate_param * 6.3));
    data->theta = ((unsigned int) data->ftheta) % NB_THETA;
    data->visibility =
        (cos (fcycle * 0.001 + 1.5) * sin (fcycle * 0.008) +
        cos (fcycle * 0.011 + 5.0) - 0.8 + info->sound.speedvar) * 1.5;
    if (data->visibility < 0.0)
      data->visibility = 0.0;
    data->factor_p.change_listener (&data->factor_p);
  }

  if (data->visibility < 0.01) {
    switch (goom_irand (info->gRandom, 300)) {
      case 1:
        set_motif (data, CONV_MOTIF1);
        data->inverse_motif = 1;
        break;
      case 2:
        set_motif (data, CONV_MOTIF2);
        data->inverse_motif = 0;
        break;
    }
  }
#ifdef DRAW_MOTIF
  if ((ff > 0.98f) && (ff < 1.02f))
    memcpy (dest, src, info->screen.size * sizeof (Pixel));
  else
    create_output_with_brightness (_this, src, dest, info, iff);
#else
  memcpy (dest, src, info->screen.size * sizeof (Pixel));
#endif

/*
//   Benching suite...
   {
    uint64_t before, after;
    double   timed;
    static double stimed = 10000.0;
    before = GetTick();
    data->visibility = 1.0;
    create_output_with_brightness(_this,src,dest,info,iff);
    after  = GetTick();
    timed = (double)((after-before) / info->screen.size);
    if (timed < stimed) {
      stimed = timed;
      printf ("CLK = %3.0f CPP\n", stimed);
    }
  }
*/
}

void
convolve_create (VisualFX * vfx)
{
  vfx->init = convolve_init;
  vfx->free = convolve_free;
  vfx->apply = convolve_apply;
  vfx->fx_data = NULL;
  vfx->params = NULL;
}