/*
 * Copyright (c) 1999, MODS
 *
 */

#include "math.h"
#include "plugin.h"

#define MO_DEBUG
#include "mo_debug.h"

#ifndef M_E
#define M_E            2.7182818284590452354   /* e */
#endif


typedef float REAL;

typedef float SCR_TYPE;
typedef struct { uint anz; int n,w,h; double w2,h2; SCR_TYPE *scr; } SCREEN;

#define SCR_INIT  { 0,-1,-1,-1,-1.,-1., NULL }

SCREEN screens[]=
{ SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,
  SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,
  SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,
  SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT,SCR_INIT
};

#define MAX_GLOW_OBJECTS ((sizeof(screens)/sizeof(SCREEN))-1)

/* ******************** GLOBAL VARIABLES ***************** */

char seq_name[]= "Afterglow";
char tex_name[]= "SaveScrCoords";

enum { SAVE_ALL=0, ALPHA_NE_0=1, ALPHA_THRESHOLD=2 };

#define NR_TYPES       3
char stnames[NR_TYPES][16]= {"Save all", "Alpha!=0", "Alpha Threshold"};
#define TEX_NR_INDEX 9

VarStruct seq_varstr[]= {
  {  LABEL,   "After Glow",0, 0, 0, "Hier wird's heisz!" },
  {  LABEL,   "width", 0., 0., 0., "Width of the glow effect"},
  {NUMSLI|FLO,"q=",     0.1, 0., 1., "Width of the glow effect"},
  {NUMSLI|FLO,"extern=", 0.5, 0., 1., "Influence of the glow effect (outside)"},
  {NUMSLI|FLO,"intern=", 0.2, 0., 1., "Influence of the glow effect (inside)"},
  {NUMSLI|FLO,"i=",     2., 0., 25., "Glow intensity"},
  {NUMSLI|FLO,"r=",     1., 0., 1., "Red"},
  {NUMSLI|FLO,"g=",     1., 0., 1., "Green"},
  {NUMSLI|FLO,"b=",     1., 0., 1., "Blue"},
  {NUMSLI|INT,"TexturNr.=", -1., -1., MAX_GLOW_OBJECTS, 
	"Num of the texture which can be used."},
  { TOG|INT,  "Animated",	 0., 0., 1., "For (Ipo) animated glowing"},
  { TOG|INT,  "Show weights",	 0., 0., 1., "Show weights"},
  { TOG|INT,  "Use Background",	 0., 0., 1., "Use background pixels (alpha=0)"},
};

VarStruct tex_varstr[]= {
  {  LABEL,   "Save Coords",0, 0, 0, 
	"Save the visible object coordinates for later use" },
  {NUMSLI|INT,"TexturNr.=", 0., 0., MAX_GLOW_OBJECTS, 
	"Num of the texture which can be used."},
  {NUMSLI|FLO,"a-threshold=", 0., 0.,  1., 
	"Only pixel with alpha>threshold are saved" },
};

/* The cast struct is for input in the main doit function
   Varstr and Cast must have the same variables in the same order */

typedef struct Seq_Cast {
  int sapas;
  float w;
  float q;
  float le,li;
  float i,r,g,b;
  int tex_nr;
  int anim;
  int show_weights;
  int use_background;
} Seq_Cast;

typedef struct Tex_Cast {
  int sapas;
  int tex_nr;
  float alpha_threshold;
} Tex_Cast;

/* cfra: the current frame */

float cfra;
float result[8];


int save_pixel_pos(int, Tex_Cast *, float *, float *, float *);
void afterglow(Seq_Cast *, float, float, int, int, ImBuf *, ImBuf *, 
	ImBuf *, ImBuf *);

/* ******************** Fixed functions ***************** */

int sequence_plugin;

int plugin_seq_getversion(void) { 
	sequence_plugin=TRUE;  
	return B_PLUGIN_VERSION; 
}

void seq_plugin_init(void)      { _db("\t\tAfter Glow:\tPlugin_init()"); }

void seq_plugin_but_changed(int but)
{ /* Here the code would have to give to the memory freely with changing
texture No!  The function is however not called. */
  _db("\t\tAfter Glow:\tPlugin_but_changed()");
  i_db(but);
  if (but==TEX_NR_INDEX) _db("JETZT!");
}

int plugin_tex_getversion(void) { 
	sequence_plugin=FALSE; 
	return B_PLUGIN_VERSION; 
}

void tex_plugin_init(void)      { _db("\t\tSave Coords:\tPlugin_init()"); }

void tex_plugin_but_changed(int but)
{ _db("\t\tSave Coords:\tPlugin_but_changed()"); i_db(but);
}

void plugin_getinfo(PluginInfo *info)
{ _db1("Getinfo: %s-plugin\n",sequence_plugin?"Sequenz":"Texure");
  if (sequence_plugin)
  { info->name= seq_name;
    info->nvars= sizeof(seq_varstr)/sizeof(VarStruct);
    info->varstr= seq_varstr;
    info->init= seq_plugin_init;
    info->callback= seq_plugin_but_changed;
  } else
  { info->name= tex_name;
    info->nvars= sizeof(tex_varstr)/sizeof(VarStruct);
    info->varstr= tex_varstr;
    info->init= tex_plugin_init;
    info->callback= tex_plugin_but_changed;
  };

  info->stypes= NR_TYPES;
  info->snames= stnames[0];
  info->result= result;
  info->tex_doit=  (TexDoit) save_pixel_pos;

  info->cfra= &cfra;
  info->seq_doit= (SeqDoit) afterglow;
}


/* Afterglow sequence functions
/ ---------------------------- */

/* #define intens_color(c,dc) CLAMP((c*(1.f+dc)*i255),0.f,1.f) */
#define intens_color(c,dc) (c*(1.f+dc)*i255)

const REAL i255=1./255.f;

#define FREE_MEMORY() \
{ freemem(glow); freemem(aij); freemem(bij); freemem(gij); freemem(rij); \
  freemem(ftemp); if (scr) memset(scr,0,n*sizeof(SCR_TYPE)); \
}

#define EXIT() { FREE_MEMORY(); cancel_pict(sx,sy,(RGBA*)(out->rect)); return; }

void cancel_pict(int w,int h,RGBA *s)
{ int a=MIN2(w,h),i;  int x0=(w-a)>>1,y0=(h-a)>>1;
  if (!s) return;
  memset(s,0x10,w*h*sizeof(RGBA));
  for(i=0;i<a;i++)
  { RGBA *c=&s[x0+i+w*(y0+i)]; c->r=255-2*i; c->b=3*i; c->g=5*i; c->a=0xff;
    s[x0+a-i+w*(y0+i)]=*c;
  }
}

void compute_weights(REAL abfall,REAL *new_v, REAL *old_v, int w, int h, 
	int li, int ci)
{ REAL old, *fp, *fp2; int i,j;
  for(j=0; j<h; j++)
  { old=0; fp=new_v+j*li; fp2=old_v+j*li;
    for(i=0; i<w; i++,fp+=ci,fp2+=ci) fp[0]=old=fp2[0]+abfall*old;
    fp-=ci; fp2-=ci; old=0;
    for(i=0; i<w; i++,fp-=ci,fp2-=ci) { fp[0]+=old*=abfall; old+=fp2[0]; }
  }
}

void compute_colors(int c_offs,REAL dc,REAL li,REAL le,REAL q,int sx,int sy,
   int n,uint *in, REAL *aij,REAL *glow,REAL *cij,REAL *ftemp,
   int use_background)
{ int i;
  REAL c_org, l,al;
  VRGBA *cin=(VRGBA*)in;
  for(i=0;i<n;i++)
    cij[i]=cin[i][ALPHA]*i255*glow[i]*intens_color(cin[i][c_offs],dc);
  compute_weights(q,ftemp,cij,sx,sy,sx,1);
  compute_weights(q,cij,ftemp,sy,sx,1,sx);
  for(i=0;i<n;i++) if (cin[i][ALPHA]||use_background)  
	/* Falls kein Hintergrund, volle Farbe lassen */
  { c_org=cin[i][c_offs]*i255;
    if (c_org<cij[i])
    { l=glow[i] ? li : le;
      al=CLAMP(aij[i],0.f,1.f)*l;
      cij[i]+=(1.f-al)*(c_org-cij[i]);
    } else cij[i]=c_org;
  }
}

void compute_zbuf(REAL li,REAL le,REAL q,int sx,int sy,int n,uint *in,
   int *zin,int *zout, REAL *aij,REAL *glow,REAL *cij,REAL *ftemp,
   int use_background)
{ 
  int i;
  REAL z_org, l, al;
  /* for(i=0;i<n;i++) cij[i]=cin[i][ALPHA]*i255*glow[i]*i2d_z(zin[i]); */
  for(i=0;i<n;i++) cij[i]=(glow[i]*i2d_z(zin[i]));
  compute_weights(q,ftemp,cij,sx,sy,sx,1);
  compute_weights(q,cij,ftemp,sy,sx,1,sx);
  for(i=0;i<n;i++)
  { z_org=(float)i2d_z(zin[i]);
    if (z_org<cij[i])
    { l=glow[i] ? li : le;
      al=CLAMP(aij[i],0.f,1.f)*l;
      cij[i]+=(1.f-al)*(z_org-cij[i]);
      zout[i]=d2i_z(cij[i]);
    } else zout[i]=zin[i];
  }
}

void afterglow(Seq_Cast *cast, float facf0, float facf1, int sx, int sy,
                     ImBuf *ibuf1, ImBuf *ibuf2, ImBuf *out, ImBuf *use)
{ 
  int n=sx*sy,i,j,nr_glowpixels=0;

  REAL intens=(cast->anim ? facf1 : 1) * cast->i*cast->i;
  REAL r=intens*cast->r,g=intens*cast->g,b=intens*cast->b;
  REAL q=(cast->anim ? facf1 : 1) * pow(M_E,-2./(cast->q*sx));
/*  REAL alpha_scal=(1.f-q)*(1.f-q)/(1.f+q); */
  REAL alpha_scal=(1.f-q)/((1+q)*(1-pow(q,sx/15.)));
  REAL le=cast->le,li=cast->li;

  REAL *glow=NULL;
  REAL *aij=NULL, *rij=NULL, *gij=NULL, *bij=NULL, *ftemp=NULL;

  SCR_TYPE *scr=NULL;

  _db4("\t\tafterklo(%g,%g,%dx%d)\n",facf0,facf1,sx,sy);
  _db7("\ti=%g, r=%g, g=%g, b=%g, a=%g, le=%g, li=%g\n",intens,r,g,b,
	alpha_scal,le,li);

  /* global memory supply or check whether sufficient */

  cast->w=-2./log(q);
  sprintf(seq_varstr[1].name,"w=%.2g",cast->w);
  alpha_scal*=alpha_scal;

  getmem(glow,n);
  if (cast->tex_nr!=-1)   /* read glowing pixels into field glow */
  { /* beforehand prepare everything for further save_scr_coords */
    SCREEN *screen=&screens[cast->tex_nr];
    screen->w2=0.5*(screen->w=sx); screen->h2=0.5*(screen->h=sy);
    if (screen->n!=n)
    { freemem(screen->scr); getmem(screen->scr, screen->n=n);
      scr=screen->scr; EXIT();
    }
    scr=screen->scr;
    for(i=0;i<n;i++) glow[i]=scr[i];
  } else
  { uchar *c;
    for(c=(uchar*)((ibuf2?ibuf2:ibuf1)->rect),i=0;i<n;i++,c+=4) if (c[0]>0)
    { glow[i]=1.f; nr_glowpixels++; }
  }

  for(i=0;i<n;i++) glow[i]*=alpha_scal;  /* Skalierung für Summe aij=1 
	an Kanten */

  getmem0(aij,n); getmem0(bij,n); getmem0(gij,n); getmem0(rij,n); 
  getmem0(ftemp,n);

  compute_weights(q,ftemp,glow,sx,sy,sx,1);
  compute_weights(q,aij,ftemp,sy,sx,1,sx);

  if (!out->zbuf) getmem(out->zbuf,n);
  compute_zbuf(li,le,q,sx,sy,n,ibuf1->rect,ibuf1->zbuf,out->zbuf,
	aij,glow,rij,ftemp,cast->use_background);

  compute_colors(ROT,r,li,le,q,sx,sy,n,ibuf1->rect,aij,glow,rij,ftemp,
	cast->use_background);
  compute_colors(GRUEN,g,li,le,q,sx,sy,n,ibuf1->rect,aij,glow,gij,ftemp,
	cast->use_background);
  compute_colors(BLAU,b,li,le,q,sx,sy,n,ibuf1->rect,aij,glow,bij,ftemp,
	cast->use_background);

  { RGBA *cout=(RGBA*)(out->rect),*cin=(RGBA*)(ibuf1->rect);
    for(i=0;i<n;i++)
    { REAL scal,max_color;
      max_color=rij[i];
      if (gij[i]>max_color) max_color=gij[i];
      if (bij[i]>max_color) max_color=bij[i];
      scal=max_color>1.f ? 255.f/max_color : 255.f;
      cout[i].r=scal*rij[i]+0.5f;
      cout[i].g=scal*gij[i]+0.5f;
      cout[i].b=scal*bij[i]+0.5f;
      cout[i].a=255.f*CLAMP(aij[i],0.f,1.f)+0.5f;
      if (cin[i].a>cout[i].a) cout[i].a=cin[i].a;
    }
  }

  if (cast->show_weights)
  { RGBA *c=(RGBA*)(out->rect);
    REAL *fp=aij, *gl=glow;
    REAL max=-1;
    for(j=0;j<sy;j++) for(i=0;i<sx;i++,fp++,c++,gl++)
    { 
      c->b=c->g=c->r=CLAMP(fp[0]*255.f,0,255)+0.5f;
      if (gl[0]&&(((i<0)||(!gl[-1]))||((i>=n)||!(gl[1])))) { c->b=c->g=c->r=0; }
      c->a=255;
      if (fp[0]>max) max=fp[0];
    }
    printf("%d\t%g\t%g\t%g\t%g\n",nr_glowpixels,q,cast->q,alpha_scal,max);
  }

  _db("afterglow:\t\tDas war's");
  FREE_MEMORY();
}

/* Save screen coordinates
   ----------------------- */

#define f2i_x(x) (int)(((x)+1.)*screen->w2+0.5)
#define f2i_y(y) (int)(((y)+1.)*screen->h2+0.5)

int save_pixel_pos(int stype, Tex_Cast *cast, float *texvec, float *dxt, 
	float *dyt)
{ SCREEN *screen=&screens[cast->tex_nr];
  SCR_TYPE *scr=screen->scr;

  if (scr)
  { if ((stype==SAVE_ALL)||
        ((stype==ALPHA_NE_0)&&(result[4]!=0))||
        (result[4]>=cast->alpha_threshold))
    { int index=f2i_x(texvec[0])+screen->w*f2i_y(texvec[1]);
      if ((index>=0)&&(index<screen->n)) scr[index]=1.f;
      else fprintf(stderr,
	 "Point (%g,%g,%g) is situated in the top half of the display!\n",
	 texvec[0],texvec[1],texvec[2]);
    }
  }
  return(0);
}

