//////////////////////////////////////////////////////////////////////////
// Visprint -- visual fingerprint generator
//
// visprint is a program to produce visual fingerprints. This code was
// written by Ian Goldberg, based on an idea by Hal Finney, in a post to
// the coderpunks mailing list. The most excellent color enhancements
// were added by Raph Levien.  David Johnston Ported the program to 
// Windows 95 console mode and added a bunch of nice features.
//
// Ian Golderg:    http://http.cs.berkeley.edu/~iang/
// Raph Leviev:    http://www.cs.berkeley.edu/~raph/
// Hal Finney:     http://www.rain.org/~hal/
// David Johnston: http://www.cybercities.com/ston/
//
// Modified August 12th, 1998
//////////////////////////////////////////////////////////////////////////


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>

#define MAX_RES            1000
#define DEFAULT_RES        300
#define MAX_NEQNS          4     // number of equations
#define DEFAULT_NEQNS      4
#define DEFAULT_INTENSITY  10
#define DEFAULT_BACKGROUND 0  
#define DEFAULT_COLOUR     1  

typedef unsigned char byte;

byte pic[MAX_RES][MAX_RES][3];

double eqns[MAX_NEQNS][6];
byte hashd[MAX_NEQNS][6];

int res, neqns, intensity, background;

//////////////////////////////////////////////////////////////////////////

void inithashd(void)
{
    byte buf[8];
    int i;
    int j;

    for(i=0;i<neqns;++i) 
    {

      fread(buf, 8, 1, stdin);
      for(j=0;j<8;++j) 
      {
        byte c = buf[j];
        if (c >= '0' && c <= '9')
          buf[j] = c-'0';
        else if (c >= 'a' && c <= 'f')
          buf[j] = c-('a'-10);
        else if (c >= 'A' && c <= 'F')
          buf[j] = c-('A'-10);
        else buf[j] = 0;
      }
      hashd[i][0] = ( buf[0] << 2 ) | ( buf[1] >> 2 );
      hashd[i][1] = ( (buf[1]&3) << 4 ) | buf[2];
      hashd[i][2] = ( buf[3] << 2 ) | ( buf[4] >> 2 );
      hashd[i][3] = ( (buf[4]&3) << 4 ) | buf[5];
      hashd[i][4] = buf[6];
      hashd[i][5] = buf[7];
    }

}

//////////////////////////////////////////////////////////////////////////

void initeqn(int n, double s, double t, double a, double b, double u,
    double v)
{
    eqns[n][0] = s*(1+a*b);
    eqns[n][1] = s*b;
    eqns[n][2] = t*a;
    eqns[n][3] = t;
    eqns[n][4] = u;
    eqns[n][5] = v;
}

//////////////////////////////////////////////////////////////////////////

void initeqns(void)
{
    int i;
    double s,t,a,b,u,v;

    for(i=0;i<neqns;++i) {
	s = (hashd[i][0])*0.3/64.0 + 0.3;
	t = (hashd[i][1])*0.3/64.0 + 0.3;
	a = (hashd[i][2])/32.0 - 1.0;
	b = (hashd[i][3])/32.0 - 1.0;
	u = (hashd[i][4])/32.0 - 0.25;
	v = (hashd[i][5])/32.0 - 0.25;
	initeqn(i,s,t,a,b,u,v);
    }
}

//////////////////////////////////////////////////////////////////////////

int gfcn = 0;
int colour = 0;

//////////////////////////////////////////////////////////////////////////

void find_window(double *xtrans, double *ytrans, double *scalefactor)
{
  int i, j, k;

  double x = 0, y = 0;
  double nx, ny;

  double xdiff, ydiff;
  double maxx=0, minx=0, maxy=0, miny=0;

  int fcn;

  for(i = 0;i < (50 * res); i++)  // iterate a few times first
  {
    fcn = rand()%neqns; // choose 

    nx = eqns[fcn][0]*x + eqns[fcn][1]*y + eqns[fcn][4];
    ny = eqns[fcn][2]*x + eqns[fcn][3]*y + eqns[fcn][5];
    x=nx;
    y=ny;
  }

  for(; i < (60 *res); i++)  // now continue where we just left off for a small amount of time
  {
    fcn = rand()%neqns; // choose 

    nx = eqns[fcn][0]*x + eqns[fcn][1]*y + eqns[fcn][4];
    ny = eqns[fcn][2]*x + eqns[fcn][3]*y + eqns[fcn][5];

    if (i>res*50)
    {
      if (nx < minx)
        minx = nx;
      if (nx > maxx)
        maxx = nx;
      if (ny < miny)
        miny = ny;
      if (ny > maxy)
        maxy = ny;
    }
    x=nx;
    y=ny;
  }

  xdiff = (maxx - minx) * 1.05;
  ydiff = (maxy - miny) * 1.05;

  if (xdiff > ydiff)
    *scalefactor = 1 / xdiff;
  else *scalefactor = 1 / ydiff;

  *xtrans = ((maxx + minx) / 2);
  *ytrans = ((maxy + miny) / 2);
}


//////////////////////////////////////////////////////////////////////////

void nextpoint(double x, double y, double *nx, double *ny)
{
    int fcn = rand()%neqns; // choose 

    if (colour && (colour>1 || ((rand() & 1) == 0))) gfcn = fcn + 1;
    *nx = eqns[fcn][0]*x + eqns[fcn][1]*y + eqns[fcn][4];
    *ny = eqns[fcn][2]*x + eqns[fcn][3]*y + eqns[fcn][5];
}

//////////////////////////////////////////////////////////////////////////

void ifs(void)
{
    double nx,ny;
    double x = 1;//rand()%res;
    double y = 2;//rand()%res;
    int i,j,k;
    int res2, nxres2, nyres2;
    int max_intensity;
    double xtrans, ytrans;
    double scalefactor;

    find_window(&xtrans, &ytrans, &scalefactor);
    
    max_intensity = 255 - background;

    for(i=0;i<res;++i) for(j=0;j<res;++j) for(k=0;k<3;k++)
      pic[i][j][k]=background;

    for(i = 0;i < (50 * res); i++)  // iterate a few times first
    {
      nextpoint(x,y,&nx,&ny);
	    x=nx;
      y=ny;
    }

    for(;i<intensity*res*res; i++)
    {
      nextpoint(x,y,&nx,&ny);
      nxres2 = ((nx - xtrans) * scalefactor + 0.5) * res;
      nyres2 = ((ny - ytrans) * scalefactor + 0.5) * res;
//      fprintf(stderr, "nxres2: %d  nyres2: %d  scalefactor: %f\n", nxres2, nyres2, scalefactor);
      if (i>res*50 && nxres2>=0 && nxres2<res && nyres2>=0 && nyres2<res)
      {
	      if (colour)
        {
          if(background == 255)
          {
            if ((gfcn & 1) && pic[nxres2][nyres2][0] != max_intensity)
              pic[nxres2][nyres2][0]--;
            if ((gfcn & 2) && pic[nxres2][nyres2][1] != max_intensity)
              pic[nxres2][nyres2][1]--;
            if ((gfcn & 4) && pic[nxres2][nyres2][2] != max_intensity)
              pic[nxres2][nyres2][2]--;
          }
          else
          {
            if ((gfcn & 1) && pic[nxres2][nyres2][0] != max_intensity)
              pic[nxres2][nyres2][0]++;
            if ((gfcn & 2) && pic[nxres2][nyres2][1] != max_intensity)
              pic[nxres2][nyres2][1]++;
            if ((gfcn & 4) && pic[nxres2][nyres2][2] != max_intensity)
              pic[nxres2][nyres2][2]++;
          }
        }
        else
        {
          pic[nxres2][nyres2][0] = max_intensity;
        }
      }
	    x=nx;
      y=ny;
    }
}

//////////////////////////////////////////////////////////////////////////

void writepnm(void)
{
    int i,j;

    _setmode(_fileno(stdout), _O_BINARY);

    if (colour)
    {
        printf("P6\n%d %d\n255\n",res,res);
        for(i = 0; i < res; i++)
          fwrite(pic[i], 3, res, stdout);
    }
    else
    {
        printf("P6\n%d %d\n255\n",res,res);
        for(j=0;j<res;++j)
        {
          for (i=0;i<res;++i)
          {
            fwrite (pic[i][j], 1, 1, stdout);
            fwrite (pic[i][j], 1, 1, stdout);
            fwrite (pic[i][j], 1, 1, stdout);
          }
        }
    } 
}

//////////////////////////////////////////////////////////////
// void parse_cmdline(int argc,char *argv[])
//
// Added by David Johnston on Aug 12th 1998
//
// Parses the command line for switches and other arguments
//////////////////////////////////////////////////////////////

void parse_cmdline(int argc,char *argv[])
{
  int x;

  colour     = DEFAULT_COLOUR;
  neqns      = DEFAULT_NEQNS;
  res        = DEFAULT_RES;
  intensity  = DEFAULT_INTENSITY;
  background = DEFAULT_BACKGROUND;

  for(x=1;x<argc;x++)
    {
//      fprintf(stderr,"looking at %s\n",argv[x]);
      
      if(!strcmp("-c",argv[x]))
        colour = 2;
      if(!strcmp("-g",argv[x]))
        colour = 0;
      else if(!strcmp("-r",argv[x]))
      {
	      x++;
        if(sscanf(argv[x],"%d",&res)==0)
        {
          fprintf(stderr, "invalid resolution on command line");
	        exit(1);
        }
      }
      else if(argv[x][0]=='-' && argv[x][1]=='r')
      {
        if (sscanf(argv[x]+2,"%d",&res)==0)
        {
          fprintf(stderr, "invalid resolution on command line");
	        exit(1);
        }
      }
      else if(!strcmp("-n",argv[x]))
      {
	      x++;
        if(sscanf(argv[x],"%d",&neqns)==0)
        {
          fprintf(stderr, "invalid number of equations on command line");
	        exit(1);
        }
      }
      else if(argv[x][0]=='-' && argv[x][1]=='n')
      {
        if (sscanf(argv[x]+2,"%d",&neqns)==0)
        {
          fprintf(stderr, "invalid number of equations on command line");
	        exit(1);
        }
      }
      else if(!strcmp("-i",argv[x]))
      {
	      x++;
        if(sscanf(argv[x],"%d",&intensity)==0)
        {
          fprintf(stderr, "invalid intensity on command line");
	        exit(1);
        }
      }
      else if(argv[x][0]=='-' && argv[x][1]=='i')
      {
        if (sscanf(argv[x]+2,"%d",&intensity)==0)
        {
          fprintf(stderr, "invalid intensity on command line");
	        exit(1);
        }
      }
      else if(!strcmp("-b",argv[x]))
      {
	      x++;
        if(sscanf(argv[x],"%d",&background)==0)
        {
          fprintf(stderr, "invalid background on command line");
	        exit(1);
        }
      }
      else if(argv[x][0]=='-' && argv[x][1]=='b')
      {
        if (sscanf(argv[x]+2,"%d",&background)==0)
        {
          fprintf(stderr, "invalid background on command line");
	        exit(1);
        }
      }
      else
      {
        fprintf(stderr, "Unknown command line switch.");
        exit(1);
      }        
  } 

  if(res > MAX_RES)
  {
    fprintf(stderr, "% is too high a resolution.  Assuming %d.", res, MAX_RES);
    res = MAX_RES;
  }

  if(neqns > MAX_NEQNS)
  {
    fprintf(stderr, "% is too many equations.  Assuming %d.", neqns, DEFAULT_NEQNS);
    neqns = DEFAULT_NEQNS;
  }
}


//////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
    parse_cmdline(argc, argv);

    srand(time(NULL));
    inithashd();
    initeqns();
    ifs();
    writepnm();
    return 0;
}
