// (c) Jean MICHEL June 1993 -- any modifications must be signaled to the
// author

// cell.cpp: classes to compute life generations. This is completely
// independant from windows, and can be used Con any system

#include "cell.h"
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "efns.h"
#define MAXCELLS 16000
#define INFINI 0xfffdfffal
static cell_type temp[MAXCELLS];

static cell_type *copy(cell_type *pw,int pop)
{ cell_type *v=new cell_type[pop+1];
  for(int i=0;i<pop;i++)v[i]=pw[i];
  v[i]=INFINI;
  return v;
}

cellpop::cellpop(cellpop&w){pop=w.pop;v=copy(w.v,pop);}

void cellpop::clear_pop(){delete[] v;v=copy(NULL,pop=0);}

cellpop::cellpop(){v=NULL;pop=0;}

void cellpop::operator=(const cellpop& w)
{if(v)delete[] v;v=copy(w.v,pop=w.pop);}

cellpop::~cellpop(){if(v)delete[] v;}

cellpop::cellpop(int lg)
{ pop=lg;
  v=new cell_type[pop+1];
  for(int i=0;i<pop;i++)v[i]=cell_type(i,0);
  v[i]=INFINI;
}

BOOL cellpop::operator==(cellpop& w)
{ if(pop!=w.pop)return FALSE;
  for(int i=0;i<pop;i++)if(v[i]!=w.v[i])return FALSE;
  return TRUE;
}

BOOL cellpop::in(cell_type c)
{ for(int i=0;i<pop;i++)if(v[i]==c)return TRUE;
  return FALSE;
}

void cellpop::nextgen(cellpop& w)/* v=nextgen from w */
{ INTCELL *ind[9]/* indexes in w for neighb*/;
  static INTCELL neighb[9]; // neighbours
  for(int i=0;i<9;i++){neighb[i]=0;ind[i]=(INTCELL *)w.v;}
  static long d[9]={-BASE-1,-BASE,1-BASE,-1,0,1,BASE-1,BASE,BASE+1};
  /* relative position of neighbours */
  INTCELL x,lim=INFINI+d[0];
  int iv=0;
  for(INTCELL z=0/* previous cell */;z<lim;z=x)
  { INTCELL *n=neighb;int sum=0;
    x=INFINI; // will become current cell
    for (i=0;i<9;i++,n++)
    { if(*n==z)*n=d[i]+*ind[i]++;
      if(*n<x){x=*n;sum=1;}
      else if(*n==x)sum++;
    }
    if(sum==3 ||(sum==4 && neighb[4]==x))
    { if(iv>=MAXCELLS)
      { efns(WARNING_,"%d: too many cells",MAXCELLS);
	delete[]v;v=NULL;
	return;
      }
      temp[iv++]=cell_type(x);
    }
  }
  delete[]v;v=copy(temp,pop=iv);
}

static int compar(const void *a,const void *b)
{ unsigned long x=*(unsigned long *)a,y=*(unsigned long *)b;
  return x<y?-1:(x>y?1:0);}

void cellpop::transform(char orientation)
{ int dirx=orientation&0x1?-1:1,diry=orientation&0x2?-1:1;
  BOOL rot=orientation&0x4;
  for(int i=0;i<pop;i++)
  v[i]=cell_type(dirx*(rot?v[i].celly():v[i].cellx()),
		 diry*(rot?v[i].cellx():v[i].celly()));
  qsort(v,pop,sizeof(cell_type),compar);
}

void cellpop::add(cellpop&w,cellpop &shape,cell_type offset,int mode)
{ int iv=0,iw=0;
  for(int is=0;is<shape.pop;is++)
  { cell_type c=shape.v[is]+offset;
    while(w.v[iw]<c)temp[iv++]=w.v[iw++];
    if(w.v[iw]>c){if(mode!=LAY_OFF)temp[iv++]=c;}
    else if(mode==LAY_ON)temp[iv++]=w.v[iw++];
    else iw++;
  }
  while(iw<w.pop)temp[iv++]=w.v[iw++];
  v=copy(temp,pop=iv);
}

cell_type cellpop::lastx(void)
{ cell_type res;int x=-cell_type(INFINI).cellx();
  for(int i=0;i<pop;i++)
    if(v[i].cellx()>=x){x=v[i].cellx();res=v[i];}
  return res;
}

cell_type cellpop::firstx(void)
{ cell_type res;int x=cell_type(INFINI).cellx();
  for(int i=0;i<pop;i++)
    if(v[i].cellx()<x){x=v[i].cellx();res=v[i];}
  return res;
}

cell_type cellpop::firsty(void)
{ cell_type res;int y=cell_type(INFINI).cellx();
  for(int i=0;i<pop;i++)
    if(v[i].celly()<y){y=v[i].celly();res=v[i];}
  return res;
}

cell_type cellpop::lasty(void)
{ cell_type res;int y=-cell_type(INFINI).cellx();
  for(int i=0;i<pop;i++)
    if(v[i].celly()>=y){y=v[i].celly();res=v[i];}
  return res;
}

void cellpop::erase_rect(cell_type start,cell_type end)
{ int y=0;
  for(int x=0;x<pop;x++)
     if(v[x].cellx()<start.cellx() || v[x].cellx()>=end.cellx() ||
	v[x].celly()<start.celly() || v[x].celly()>=end.celly()) v[y++]=v[x];
  v[y]=INFINI;
  pop=y;
}

void cellpop::save_rect(char *FileName,cell_type start,cell_type end)
{ FILE *fp=fopen(FileName,"w");
  int x=start.x,y=start.y;
  if(fp==NULL){ efns(WARNING_|FILE_,FileName);return;}
  for(int i=0;i<pop;i++)
    if(v[i].cellx()>=start.cellx() && v[i].cellx()<end.cellx() &&
       v[i].celly()>=start.celly() && v[i].celly()<end.celly())
    { for(;y<v[i].y;y++){fprintf(fp,"\n");x=start.x;}
      for(;x<v[i].x;x++)fprintf(fp," ");
      fprintf(fp,"o");x++;
    }
  fprintf(fp,"\n");
  fclose(fp);
}

void cellpop::save(char *FileName)
{ save_rect(FileName,cell_type(firstx().cellx(),firsty().celly()),
		     cell_type(1+lastx().cellx(),1+lasty().celly()));
}

cellpop::cellpop(char *string)
{ pop=0;v=0;
  int x=0,y=0;
  for(char *s=string;*s;s++)
  switch(*s)
  { case '\n':y++;x=0;break;
    case '\r':break;
    case ' ':case '.':x++;break;
    case '$':x+=10;break;
    case '\t': x+=8-x%8;break;
    case 'o':case 'O':
      if(pop==MAXCELLS){efns(WARNING_,"%d: too many cells",MAXCELLS);return;}
      temp[pop++]=cell_type(x,y);
      x++;break;
    default: efns(WARNING_,"invalid data in string <%.15s>",
                                                       s-5<string?string:s-5);
             return;
  }
  v=copy(temp,pop);
}

cellpop readfile(char *FileName)
{ cellpop res;
  FILE *f=fopen(FileName,"rb");
  if(f==NULL)
  { efns(WARNING_|FILE_,FileName);
    return res;
  }
  long flen=filelength(fileno(f));
  if(flen>65534u)
  { efns(WARNING_,"file `%s' too big: %ld bytes",FileName,flen) ;
    fclose(f);return res;
  }
  char *s=new char[(unsigned)flen];
  if(flen!=fread(s,1,(unsigned)flen,f))
  { efns(WARNING_|FILE_,FileName);
    delete[]s;fclose(f);return res;
  }
  fclose(f);
  s[flen]=0; res=cellpop(s);
  delete[]s;
  return res;
}


struct named_shape
{   char *name;
    cellpop shape;
    named_shape *next,*prev;
};
static named_shape *shapeptr=NULL;

static int shapeidx=-1;

char *getshapename(int i)
{ if(!shapeptr)return NULL;
  while(i>shapeidx && shapeptr->next)
  { shapeidx++;shapeptr=shapeptr->next;}
  if(i>shapeidx)return NULL;
  while(i<shapeidx && shapeptr->prev)
  { shapeidx--;shapeptr=shapeptr->prev;}
  if(i<shapeidx)return NULL;
  return shapeptr->name;
}

cellpop getshape(int i)
{ char *s=getshapename(i);
  return shapeptr->shape;
}

#define MAXNAME 30
void readshapelib(char *FileName)
{ FILE *f=fopen(FileName,"rb");
  int line=1,c;
  char name[MAXNAME+1];
  named_shape *oldptr=NULL;
  if(f==NULL)
  { efns(WARNING_|FILE_,FileName);
    return;
  }
  if(shapeptr)
  { while(shapeptr->next)
    { shapeidx++;shapeptr=shapeptr->next;}
    oldptr=shapeptr;
  }
  while((c=getc(f))!=EOF)switch(c)
  { case '|': while((c=getc(f))!=EOF && c!='\n');line++;break;
    case '[': for(char *n=name;(c=getc(f))!=']';n++)
	     { if(n>=name+MAXNAME)
	       { efns(WARNING_,"error in %s line %d: name too long (>%d chars)",
		    FileName,line,MAXNAME);
		 goto fin;
	       }
	       if(c==EOF)
	       { efns(WARNING_,"error in %s line %d: end of file in name",
		    FileName,line);
		 goto fin;
	       }
	       if(c=='\n')
	       { efns(WARNING_,"error in %s line %d: line break in name",
		    FileName,line);
		 goto fin;
	       }
	       *n=c;
	     }
	     while((c=getc(f))==' ' || c=='\t' || c=='\r');
	     if(c!='\n')
	     { efns(WARNING_,"error in %s: garbage follows name line %d",
		  FileName,line);
	       goto fin;
	     }
	     else line++;
	     *n=0;
	     cellpop w;
             int x=0,y=0;
             while(strchr("\n\r .$\toO",c=getc(f)))switch(c)
	     { case '\n':y++;x=0;line++;break;
	       case '\r':break;
	       case ' ':case '.':x++;break;
	       case '$':x+=10;break;
	       case '\t': x+=8-x%8;break;
	       case 'o':case 'O':
	       if(w.pop==MAXCELLS)
	       { efns(WARNING_,"error in %s line %d: too many cells(%d)",
	                     FileName,line,MAXCELLS);
		 goto fin;
               }
	       temp[w.pop++]=cell_type(x,y);
	       x++;break;
	       default: efns(WARNING_,"error in %s line %d: invalid data",
							     FileName, line);
			goto fin;
	     }
	     ungetc(c,f);
             w.v=copy(temp,w.pop);
	     named_shape *temp=new named_shape;
             if(shapeptr)shapeptr->next=temp;
	     temp->prev=shapeptr;
             shapeptr=temp;
             shapeidx++;
             shapeptr->next=NULL;
             shapeptr->name=strdup(name);
             shapeptr->shape=w;
  }
  fin: fclose(f);
  if(oldptr)
  { oldptr->next->prev=NULL;
    for(;oldptr;oldptr=oldptr->prev)shapeidx--;
  }
  return;
}
