/*
  Delay Grab video effect

  blockwise, controllable image delay

  Copyright (C) 1999/2000  A. Schiffler <aschiffler@home.com>
  Copyright (C) 2001/2002  Denis Roio <jaromil@dyne.org>
  
  original sourcecode is from libbgrab 2.1f
  ported to FreeJ, successively modified
  then ported to frei0r
  
  This program 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 3 of the  License, or
  (at your option) any later version.
  
  This program 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  this  program;  if  not, write  to  the  Free  Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <inttypes.h>

#include <frei0r.hpp>




#define QUEUEDEPTH 71 /* was 76 */
#define MODES 4

// freej compat facilitator
typedef struct {
  int16_t x; ///< x axis position coordinate
  int16_t y; ///< y axis position coordinate
  uint16_t w; ///< width of frame in pixels
  uint16_t h; ///< height of frame in pixels
  uint8_t bpp; ///< bits per pixel
  uint16_t pitch; ///< width of frame in bytes
  uint32_t size; ///< size of the whole frame in bytes
} ScreenGeometry;

class DelayGrab: public frei0r::filter {

public:
  DelayGrab(int wdt, int hgt);
  ~DelayGrab();

  virtual void update(double time,
                      uint32_t* out,
                      const uint32_t* in);


private:

  ScreenGeometry geo;

  void _init(int wdt, int hgt);

  void createDelaymap(int mode);
  void set_blocksize(int bs);
  int isqrt(unsigned int x);

  /* cheap & fast randomizer (by Fukuchi Kentarou) */
  uint32_t randval;
  uint32_t fastrand() { return (randval=randval*1103515245+12345); };
  void fastsrand(uint32_t seed) { randval = seed; };

  int x,y,i,xyoff,v;
  uint8_t *imagequeue,*curqueue;
  int curqueuenum;
  uint32_t *curdelaymap;
  uint8_t *curpos,*curimage;
  int curposnum;
  void *delaymap;

/* initialized from the init */
  int delaymapwidth;  /* width/blocksize */
  int delaymapheight; /* height/blocksize */
  int delaymapsize;   /* delaymapheight*delaymapwidth */
  
  int blocksize;
  int block_per_pitch;
  int block_per_bytespp;
  int block_per_res;
  
  int current_mode;
  
};

void DelayGrab::_init(int wdt, int hgt) {
  geo.w = wdt;
  geo.h = hgt;
  geo.bpp = 32;
  geo.size = geo.w*geo.h*(geo.bpp/8);
  geo.pitch = geo.w*(geo.bpp/8);
}




DelayGrab::DelayGrab(int wdt, int hgt) {

  delaymap = NULL;
  _init(wdt, hgt);

  imagequeue = (uint8_t *) malloc(QUEUEDEPTH*(geo.size));

  /* starting mode */
  current_mode = 4;
  /* starting blocksize */
  set_blocksize(2);
  
  curqueue=imagequeue;
  curqueuenum=0;

  fastsrand(::time(NULL));
}

DelayGrab::~DelayGrab() {
  free(delaymap);
  free(imagequeue);
}



void DelayGrab::update(double time,
                       uint32_t* out,
                       const uint32_t* in) {

  /* Update queue pointer */
  if (curqueuenum==0) {
    curqueuenum=QUEUEDEPTH-1;
    curqueue = imagequeue;
    curqueue += (geo.size*(QUEUEDEPTH-1));
  } else {
    curqueuenum--;
    curqueue -= geo.size;
  }

   /* Copy image to queue */
  memcpy(curqueue,in,geo.size);

     /* Copy image blockwise to screenbuffer */
  curdelaymap= (uint32_t *)delaymap;
  for (y=0; y<delaymapheight; y++) {
    for (x=0; x<delaymapwidth; x++) {

      curposnum=((curqueuenum + (*curdelaymap)) % QUEUEDEPTH);
      
      xyoff= (x*block_per_bytespp) + (y*block_per_pitch);
      /* source */
      curpos= imagequeue;
      curpos += (geo.size*curposnum);
      curpos += xyoff;
      /* target */
      curimage = (uint8_t *)out;
      curimage += xyoff;
      /* copy block */
      for (i=0; i<blocksize; i++) {
	memcpy(curimage,curpos,block_per_res);
	curpos += geo.pitch;
	curimage += geo.pitch;
      }
      curdelaymap++;
    }
  }

}

// int kbd_input(char key) {
//   int res = 1;
//   switch(key) {
//   case 'w':
//     if(current_mode<MODES) createDelaymap(current_mode+1);
//     break;
//   case 'q':
//     if(current_mode>1) createDelaymap(current_mode-1);
//     break;
//   case 's':
//     set_blocksize(blocksize+1);
//     break;
//   case 'a':
//     if(blocksize>2) set_blocksize(blocksize-1);
//     break;
//   default:
//     res = 0;
//     break;
//   }
//   return res;
// }

void DelayGrab::createDelaymap(int mode) {
  double d;

  curdelaymap=(uint32_t *)delaymap;
  fastsrand(::time(NULL));

  for (y=delaymapheight; y>0; y--) {
    for (x=delaymapwidth; x>0; x--) {
      switch (mode) {
      case 1:	
	/* Random delay with square distribution */
	d = (double)fastrand()/(double)RAND_MAX;
	*curdelaymap = (int)(d*d*16.0);
	break;
      case 2:
	/* Vertical stripes of increasing delay outward from center */
	if (x<(delaymapwidth/2)) {
	  v=(delaymapwidth/2)-x;
	} else if (x>(delaymapwidth/2)) {
	  v=x-(delaymapwidth/2);
	} else {
	  v=0;
	}
	*curdelaymap=v/2;
	break;
      case 3:
	/* Horizontal stripes of increasing delay outward from center */
	if(y<(delaymapheight/2)) {
	  v = (delaymapheight/2)-y;
	} else if(y>(delaymapheight/2)) {
	  v = y-(delaymapheight/2);
	} else {
	  v=0;
	}
	*curdelaymap=v/2;
	break;
      case 4:
	/* Rings of increasing delay outward from center */
	v = (int)isqrt((unsigned int)((x-(delaymapwidth/2))*
				      (x-(delaymapwidth/2))+
				      (y-(delaymapheight/2))*
				      (y-(delaymapheight/2))));
	*curdelaymap=v/2;
	break;
      } // switch
      /* Clip values */
      if ((int)(*curdelaymap)<0) {
	*curdelaymap=0;
      } else if (*curdelaymap>(QUEUEDEPTH-1)) {
	*curdelaymap=(QUEUEDEPTH-1);
      }
      curdelaymap++;
    }
  }
  current_mode = mode;
}

void DelayGrab::set_blocksize(int bs) {

  blocksize = bs;
  block_per_pitch = blocksize*(geo.pitch);
  block_per_bytespp = blocksize*(geo.bpp>>3);
  block_per_res = blocksize<<(geo.bpp>>4);
  
  delaymapwidth = (geo.w)/blocksize;
  delaymapheight = (geo.h)/blocksize;
  delaymapsize = delaymapheight*delaymapwidth;

  free(delaymap);
  delaymap = malloc(delaymapsize*4);

  createDelaymap(current_mode);
}

/* i learned this on books // by jaromil */
int DelayGrab::isqrt(unsigned int x) {
  unsigned int m, y, b; m = 0x40000000;
  y = 0; while(m != 0) { b = y | m; y = y>>1;
  if(x>=b) { x=x-b; y=y|m; }
  m=m>>2; } return y;
}



frei0r::construct<DelayGrab> plugin("Delaygrab",
				  "delayed frame blitting mapped on a time bitmap",
				  "Bill Spinhover, Andreas Schiffler, Jaromil",
				  3,1);
