#include "supmorph.hpp"
#include "specs.hpp"
#include "timage.hpp"
#include "timing.hpp"
#include "filter.hpp"
#include "video.hpp"
#include "jrand.hpp"

#define p_swap(x,y) { x^=y; y^=x; x^=y; }
#define p_dist(x1,y1,x2,y2) (((int)(x1)-(int)x2)*((int)(x1)-(int)x2)+      \
                             ((int)(y1)-(int)y2)*((int)(y1)-(int)y2))

super_morph::super_morph(trans_image *hint1, trans_image *hint2, 
			 int aneal_steps, void (*stat_fun)(int))
{
  int x,y,w1=hint1->width(),
          h1=hint1->height(),
          w2=hint2->width(),
          h2=hint2->height();
  if (w1>w2) w=w1; else w=w2;
  if (h1>h2) h=h1; else h=h2;
  unsigned char *dp;

  /************************ count up the number of hints ****************************/
  unsigned short hints1[256],hints2[256];
  memset(hints1,0,256*2);
  memset(hints2,0,256*2);

  dp=hint1->t_data();
  for (y=0;y<h1;y++)
  {
    x=0;
    while (x<w1)
    {      
      x+=*dp;      // skip over space
      dp++;
      if (x<w1)
      {
	int rl=*(dp++);
	while (rl--) { hints1[*(dp++)]++; x++; }
      }
    }
  }

  // hint2 image2
  dp=hint2->t_data();
  for (y=0;y<h2;y++)
  {
    x=0;
    while (x<w2)
    {      
      x+=*dp;      // skip over space
      dp++;
      if (x<w2)
      {
	int rl=*(dp++);
	while (rl--) { hints2[*(dp++)]++; x++; }
      }
    }
  }


  /****************** now sum up hints and alloc memory for movers *************/
  unsigned short start1[256],start2[256],
                 totals[256],
                 start=0,*h1p=hints1,*h2p=hints2;
  unsigned char hint_color[256],total_hints=0;

  for (y=0;y<256;y++,h1p++,h2p++)
    if (*h1p)
    {
      if (*h2p==0)  
      {
	t=0; 
	return ;
      }
      start1[y]=start2[y]=start;       // specify start of hint range
      if (*h1p>*h2p)
        totals[y]=*h1p;
      else totals[y]=*h2p;
      start+=totals[y];
      hint_color[total_hints++]=y;      
    }

  t=start;
  movers=(unsigned char *)jmalloc(t*4,"morph movers");
  
  
  /**************** Now scan the images again setup hints *********************/
  dp=hint1->t_data();
  for (y=0;y<h1;y++)
  {
    x=0;
    while (x<w1)
    {      
      x+=*dp;      // skip over space
      dp++;
      if (x<w1)
      {
	int rl=*(dp++);
	while (rl--) 
	{
	  int maddr=(start1[*(dp++)]++)*4;
	  movers[(maddr++)]=x;
	  movers[maddr]=y;
	  x++;
	}
      }
    }
  }  

  dp=hint2->t_data();
  for (y=0;y<h2;y++)
  {
    x=0;
    while (x<w2)
    {      
      x+=*dp;      // skip over space
      dp++;
      if (x<w2)
      {
	int rl=*(dp++);
	while (rl--) 
	{
	  int maddr=(start2[*(dp++)]++)*4+2;
	  movers[(maddr++)]=x;
	  movers[maddr]=y;
	  x++;
	}
      }
    }
  }  

  /********* if hint sizes don't match duplicate the smaller until sizes are equal **********/
  for (start=0,x=0;x<total_hints;x++)
  {
    y=hint_color[x];
    int dups;
    for (dp=movers+start1[y]*4,dups=totals[y]-hints1[y];dups;dups--)
    {
      *dp=*(dp-4); dp++;      // copy previous x,y position
      *dp=*(dp-4); dp++;
      dp+=2;	
    }
    start1[y]-=2*totals[y]-hints1[y];        // set the start back to the begining of hint range
  }

  for (start=0,x=0;x<total_hints;x++)
  {
    y=hint_color[x];
    int dups;
    for (dp=movers+start2[y]*4+2,dups=totals[y]-hints2[y];dups;dups--)
    {
      *dp=*(dp-4); dp++;      // copy previous x,y position
      *dp=*(dp-4); dp++;
      dp+=2;	
    }
    start2[y]-=hints2[y];        // set the start back to the begining of hint range
  }


  /******* Now apply simulated annealing to solve for a smaller total distance ********/ 
  int rand_on=0;
  for (y=0;y<aneal_steps;y++)
  {
    if (stat_fun)
      stat_fun(y);
    dp=movers;    
    for (x=0;x<total_hints;x++)
    {
      int hc=hint_color[x];
      int a,z=totals[hc];
      unsigned char *range_start=dp;
      for (a=0;a<z;a++,dp+=4)
      {
	unsigned char *swap=range_start+(rtable[((rand_on++)&(RAND_TABLE_SIZE-1))]%z)*4; 
	int d_old=p_dist(dp[0],dp[1],dp[2],dp[3])+p_dist(swap[0],swap[1],swap[2],swap[3]);
	int d_new=p_dist(dp[0],dp[1],swap[2],swap[3])+p_dist(swap[0],swap[1],dp[2],dp[3]);
	if (d_new<d_old)
	{
	  unsigned char s;
	  s=swap[2]; swap[2]=dp[2]; dp[2]=s;
	  s=swap[3]; swap[3]=dp[3]; dp[3]=s;
	}
      }      
    }    
  }
}

smorph_player::smorph_player(super_morph *m, palette *pal, image *i1, image *i2, int frames, int dir)
{
  int i,x1,y1,x2,y2;
  unsigned char *d=m->movers,*paddr=(unsigned char *)pal->addr(),*pa;
  stepper *p;
  p=steps=(stepper *)jmalloc(sizeof(stepper)*m->t,"smorph steps");
  f_left=frames;
  frames--;
  t=m->t;
  w=m->w; h=m->h;

  for (i=0;i<t;i++,p++)
  {    
    x1=*(d++);
    y1=*(d++);
    x2=*(d++);
    y2=*(d++);
    
    unsigned char r1,g1,b1,r2,g2,b2;
    pa=paddr+(int)(*(i1->scan_line(y1)+x1))*3;
    r1=*(pa++);
    g1=*(pa++);
    b1=*(pa++);
    
    pa=paddr+(int)(*(i2->scan_line(y2)+x2))*3;
    r2=*(pa++);
    g2=*(pa++);
    b2=*(pa++);

    p->r=r1<<16;
    p->g=g1<<16;
    p->b=b1<<16;

    p->dr=(long)(((int)r2-(int)r1)<<16)/frames;
    p->dg=(long)(((int)g2-(int)g1)<<16)/frames;
    p->db=(long)(((int)b2-(int)b1)<<16)/frames;

    if (dir<0)
    {
      x1=w-x1-1;
      x2=w-x2-1;
    }
    p->dx=((x2-x1)<<16)/frames;
    p->dy=((y2-y1)<<16)/frames;
    p->x=x1<<16;
    p->y=y1<<16;
  }

  hole=(unsigned char *)jmalloc(w*h,"hole image");
}





int smorph_player::show(image *screen, int x, int y, color_filter *fil, palette *pal,
			int blur_threshold)
{  
  if (f_left)
  {
    int i,px,py,ix,iy;
    short x1,y1,x2,y2;
    screen->get_clip(x1,y1,x2,y2);
    screen->add_dirty(x,y,x+w-1,y+h-1);
    stepper *ss;
    memset(hole,0,w*h);
    unsigned char *paddr=(unsigned char *)pal->addr();
    for (ss=steps,i=0;i<t;i++,ss++)
    {
      ix=(ss->x>>(16));
      iy=(ss->y>>(16));
      px=ix+x;
      py=iy+y;
      if (px>=x1 && px<=x2 && py>=y1 && py<=y2)                 
      {
        hole[ix+iy*w]=*(screen->scan_line(py)+px)=fil->lookup_color(ss->r>>(19),
								    ss->g>>(19),
								    ss->b>>(19));
      }
      ss->x+=ss->dx;
      ss->y+=ss->dy;
      ss->r+=ss->dr;
      ss->g+=ss->dg;
      ss->b+=ss->db;          
    }
    f_left--;
    if (!f_left)    // skip hole fills and smoothing on last frame
      return 1;

    unsigned char *ll=hole+1,*tl=hole+w+1,*nl=hole+w*2+1;
    for (iy=1;iy<h-1;iy++)    // now scan the for holes to fill
    {
      for (ix=1;ix<w-1;ix++,ll++,tl++,nl++)
      {
	if (x+ix>=x1 && x+ix<=x2 && y+iy>=y1 && y+iy<=y2)
	{
	  int t=0;
	  unsigned char *pa;
	  int r=0,g=0,b=0;
/*	  if (*(tl-1)) t++;
	  if (*(tl+1)) t++;
	  if (*ll) t++;
	  if (*nl) t++;*/

	  if (*(tl-1)) { t++; pa=paddr+(*(tl-1))*3; r+=*(pa++); g+=*(pa++); b+=*(pa++); }
	  if (*(tl+1)) { t++; pa=paddr+(*(tl+1))*3; r+=*(pa++); g+=*(pa++); b+=*(pa++); }
	  if (*(ll)) { t++; pa=paddr+(*ll)*3; r+=*(pa++); g+=*(pa++); b+=*(pa++); }
	  if (*(nl)) { t++; pa=paddr+(*nl)*3; r+=*(pa++); g+=*(pa++); b+=*(pa++); }	    

	  if (*tl)
	  {
	    if (t)
	    {
	      pa=paddr+(*tl)*3;
	      r/=t; g/=t; b/=t;
	      int dist=((int)(*pa)-r)*((int)(*pa)-r); pa++;
	      dist+=((int)(*pa)-g)*((int)(*pa)-g); pa++;
	      dist+=((int)(*pa)-b)*((int)(*pa)-b);
	      if (dist>blur_threshold)
	        *(tl)=*(screen->scan_line(y+iy)+x+ix)=fil->lookup_color(r>>3,g>>3,b>>3);
	    } else *(tl)=*(screen->scan_line(y+iy)+x+ix)=0; // kill single pixels
	  }
	  else if (t>=3)
	    *(tl)=*(screen->scan_line(y+iy)+x+ix)=fil->lookup_color((r/t)>>3,(g/t)>>3,(b/t)>>3);
	}
      }
      ll+=2;
      tl+=2;
      nl+=2;      
    }
    return 1;
  } else return 0;
}




/*void free_up_memory() { dprintf("you're screwed\n"); }

main(int argc, char **argv)
{
  image_init();
  jrand_init();
  FILE *fp=fopen("art/mrphmask.spe","rb"); 
  spec_directory sd(fp);
  image *h1=new image(sd.find("20 h"),fp),
        *h2=new image(sd.find("1h"),fp),
        *i1=new image(sd.find("20"),fp),
        *i2=new image(sd.find("1"),fp);
  palette *pal=new palette(sd.find(SPEC_PALETTE),fp);
  color_filter *fil=new color_filter(sd.find(SPEC_COLOR_TABLE),fp);  

  int steps=atoi(argv[1]);
  if (steps<2) steps=50;
  trans_image *hh1=new trans_image(h1,"hint1"),*hh2=new trans_image(h2,"hint2");

  time_marker time1;
  super_morph sm(hh1,hh2,steps); 
  int frames=atoi(argv[2]);
  if (frames<2) frames=16;
  smorph_player sp(&sm,pal,i1,i2,frames,-1); 

  time_marker time2;
  dprintf("time = %lf\n",time2.diff_time(&time1));

  set_mode(19,argc,argv);
  pal->load();
  i1->put_image(screen,30,30);
  update_dirty(screen);
  sleep(2);
  while (sp.show(screen,30,30,fil,pal)) 
  { update_dirty(screen);
    screen->bar(30,30,30+sp.w,30+sp.h,0);
  }
  sleep(2);
  close_graphics();
}*/


