#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <intuition/gadgetclass.h>
#include <proto/gadtools.h>
#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <stdlib.h>

#include "cell_gui.h"


#ifdef GERMAN
#define STR(a,b) a
#else
#define STR(a,b) b
#endif


UBYTE *Field,*OldField,*memory=NULL;
UBYTE *Paste=NULL;

int pasteX,pasteY;

extern struct Window *CellWnd;
extern struct Gadget *CellGadgets[];

extern int markstate,marked,mark_left,mark_top,mark_bottom,mark_right;

void notify(struct Window *win,char *s,...);
void quit(char *);
int AllocField(void);
void FreeField(void),FreePaste(void);
void ToggleField(long x,long y,short md);
void DrawBlock(long x,long y,short col);
void NextStep(void);
void InitField(void);
int LeftBorder(void);
void InitGfx(void);
void Resize( void );
void RefreshField(void);
void RefreshScroller(void);
int writeclip(UBYTE *data,int width,int height);
UBYTE *readclip(int *width,int *height,int *error);

ULONG WaitTimeout(ULONG,ULONG,ULONG);

long FWidth,FHeight;

extern int left,top,width,height,XSize,YSize;
extern int xoff,yoff;
extern int new;

int AllocField(void)
{
   FWidth=GetNumber(CellGadgets[GDX_Width])+2;
   FHeight=GetNumber(CellGadgets[GDX_Height])+2;
   memory=AllocVec(FWidth*FHeight*2,MEMF_CLEAR);
   if(memory==NULL) return(1);
   Field=memory;
   OldField=&memory[FWidth*FHeight];
   return(0);
}

void FreeField(void)
{
   SetAPen(CellWnd->RPort,0);
   RectFill(CellWnd->RPort,left,top,left+width-1,top+height-1);
   if(memory) FreeVec(memory);
   memory=0;
}

#define EMPTY 0
#define WIRE 1
#define ELEKTRON 2
#define EBLOCK 3

void ToggleField(long x,long y,short md)
{
   long pos;

   x++;
   y++;

   if(x<1 || x>=FWidth-1 || y<1 || y>=FHeight-1) return;
   pos=x+y*FWidth;

   switch(Field[pos]) {
      case EMPTY: Field[pos]=md?EMPTY:WIRE;
                  break;
      case WIRE:  Field[pos]=md?ELEKTRON:EMPTY;
                  break;
      case ELEKTRON: Field[pos]=md?EBLOCK:EMPTY;
                  break;
      case EBLOCK: Field[pos]=md?WIRE:EMPTY;
                  break;
   }
   DrawBlock(x,y,(short)Field[pos]);
}
void DrawBlock(long x,long y,short col)
{
   long xp,yp;
   int mark=0;

   x--;y--;

   if(marked && mark_left<=x && x<=mark_right && mark_top<=y
                                              && y<=mark_bottom) mark=1;

   if(markstate==-1 && mark_left<=x && x<mark_left+pasteX-2 &&
                       mark_top<=y && y<mark_top+pasteY-2) mark=2;

   x-=xoff;
   y-=yoff;

   if(x>=0 && x<XSize && y>=0 && y<YSize) {
      SetAPen(CellWnd->RPort,(long)col);
      xp=(x<<3)+left;
      yp=(y<<3)+top;
      RectFill(CellWnd->RPort,xp,yp,xp+6L,yp+6L);
      if(mark==1) {
         SetAPen(CellWnd->RPort,(col+1)&3);
         Move( CellWnd->RPort,xp,yp);
         Draw( CellWnd->RPort,xp+6,yp+6);
         Move( CellWnd->RPort,xp+6,yp);
         Draw( CellWnd->RPort,xp,yp+6);
      } else if(mark==2 && Paste) {
         SetAPen(CellWnd->RPort,
                      Paste[(y+yoff-mark_top+1)*pasteX+x+xoff-mark_left+1]);
         RectFill(CellWnd->RPort,xp+1,yp+1,xp+5,yp+5);
      }
   }
}

void NextStep(void)
{
   long size;
   long a,x,y;

   size=FWidth*FHeight;

   for(a=0;a<size;a++) {

      OldField[a]=Field[a];
   }

   for(y=1;y<FHeight-1;y++) {
      for(x=1;x<FWidth-1;x++) {
         short cnt=0;
         long pos;

         pos=x+y*FWidth;

         if(OldField[pos]==WIRE) {

            if(OldField[pos-1]==ELEKTRON) cnt++;
            if(OldField[pos-1-FWidth]==ELEKTRON) cnt++;
            if(OldField[pos-FWidth]==ELEKTRON) cnt++;
            if(OldField[pos+1-FWidth]==ELEKTRON) cnt++;
            if(OldField[pos+1]==ELEKTRON) cnt++;
            if(OldField[pos+1+FWidth]==ELEKTRON) cnt++;
            if(OldField[pos+FWidth]==ELEKTRON) cnt++;
            if(OldField[pos-1+FWidth]==ELEKTRON) cnt++;

            if(cnt>0 && cnt<3) Field[pos]=ELEKTRON;
            if(Field[pos]!=OldField[pos]) DrawBlock(x,y,(short)Field[pos]);
         } else if(OldField[pos]==ELEKTRON) {
            Field[pos]=EBLOCK;
            DrawBlock(x,y,EBLOCK);
         } else if(OldField[pos]==EBLOCK) {
            Field[pos]=WIRE;
            DrawBlock(x,y,WIRE);
         }
      }
   }
}

void RefreshField(void)
{
   int x,y,yy;

   SetAPen(CellWnd->RPort,0);
   RectFill(CellWnd->RPort,left,top,left+width-1,top+height-1);

   for(y=yoff+1;y<=yoff+YSize;y++) {
      yy=y*FWidth;
      for(x=xoff+1;x<=xoff+XSize;x++) {
         DrawBlock(x,y,Field[yy+x]);
      }
   }
#if 0
   for(y=1;y<FHeight-1;y++) {
      yy=y*FWidth;
      for(x=1;x<FWidth-1;x++) {
         DrawBlock(x,y,(short)Field[yy+x]);
      }
   }
#endif
}

#define ID(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d))

void WriteField(char *file)
{
   FILE *out;

   int size;

   struct Head {
      ULONG Form,FSize;
      ULONG Cell,Cfld,CSize;
      USHORT Width,Height;
   } head={ ID('F','O','R','M'),
            0,
            ID('C','E','L','L'),
            ID('C','F','L','D')};

   /* FORMxxxxCELLCFLDxxxxXXYY...Field (pad)*/

   size=(FWidth*FHeight+1)&0xfffffffe;

   head.CSize=size+4;
   head.FSize=size+16;
   head.Width=FWidth;
   head.Height=FHeight;

   if(out=fopen(file,"w")) {
      if(!fwrite(&head,sizeof(head),1,out) ||
              !fwrite(Field,size,1,out)) {
         notify(CellWnd,STR("Fehler beim Schreiben","Write error"));
         return;
      }
      if(fclose(out)==EOF) notify(CellWnd,STR("Fehler beim Schreiben","Write error"));
   } else notify(CellWnd,STR("Kann Datei %s nicht erzeugen","Cannot create file %s"),file);
}
void ReadField(char *file)
{
   FILE *in;
   int size;
   struct Head {
      ULONG Form,FSize;
      ULONG Cell,Cfld,CSize;
      USHORT Width,Height;
   } head;

   FreeField();
   new=1;
   GT_SetGadgetAttrs(CellGadgets[GDX_Width],CellWnd,NULL,GA_Disabled,FALSE,TAG_DONE);
   GT_SetGadgetAttrs(CellGadgets[GDX_Height],CellWnd,NULL,GA_Disabled,FALSE,TAG_DONE);

   if(in=fopen(file,"r")) {
      if(!fread(&head,sizeof(head),1,in)) {
         notify(CellWnd,STR("Lesefehler, bzw. keine Cell-Datei","Read error or no cell-file"));
         fclose(in);
         return;
      }

      if(head.Form!=ID('F','O','R','M') ||
         head.Cell!=ID('C','E','L','L') ||
         head.Cfld!=ID('C','F','L','D')) {
         notify(CellWnd,STR("Keine Cell-Datei","No cell-file"));
         fclose(in);
         return;
      }

      /* Werte in Gadgets eintragen, werden von AllocField() in
         FWidth und FHeight eingetragen !! */

      GT_SetGadgetAttrs(CellGadgets[GDX_Width],CellWnd,NULL,
            GTIN_Number,head.Width-2,TAG_DONE);
      GT_SetGadgetAttrs(CellGadgets[GDX_Height],CellWnd,NULL,
            GTIN_Number,head.Height-2,TAG_DONE);

      RefreshScroller();

      if(AllocField()==0) {
         GT_SetGadgetAttrs(CellGadgets[GDX_Width],CellWnd,NULL,GA_Disabled,TRUE,TAG_DONE);
         GT_SetGadgetAttrs(CellGadgets[GDX_Height],CellWnd,NULL,GA_Disabled,TRUE,TAG_DONE);
         new=0;
      } else {
         notify(CellWnd,STR("Nicht genügend Speicher\nfür Feld %ld*%ld","Not enough memory\nfor field %ld*%ld"),
                        (long)GetNumber(CellGadgets[GDX_Width]),
                        (long)GetNumber(CellGadgets[GDX_Height]));
         fclose(in);
         return;
      }

      size=FWidth*FHeight;

      if(!fread(Field,size,1,in)) {
         notify(CellWnd,STR("Lesefehler","Read error"));
      }
      fclose(in);
      RefreshField();
   } else notify(CellWnd,STR("Kann Datei %s nicht öffnen","Cannot open file %s"),file);
}

void WritePaste(char *file)
{
   FILE *out;

   int size;

   struct Head {
      ULONG Form,FSize;
      ULONG Cell,Cfld,CSize;
      USHORT Width,Height;
   } head={ ID('F','O','R','M'),
            0,
            ID('C','E','L','L'),
            ID('C','F','L','D')};

   /* FORMxxxxCELLCFLDxxxxXXYY...Field (pad)*/

   size=(pasteX*pasteY+1)&0xfffffffe;

   head.CSize=size+4;
   head.FSize=size+16;
   head.Width=pasteX;
   head.Height=pasteY;

   if(out=fopen(file,"w")) {
      if(!fwrite(&head,sizeof(head),1,out) ||
              !fwrite(Paste,size,1,out)) {
         notify(CellWnd,STR("Fehler beim Schreiben","Write error"));
         return;
      }
      if(fclose(out)==EOF) notify(CellWnd,STR("Fehler beim Schreiben","Write error"));
   } else notify(CellWnd,STR("Kann Datei %s nicht erzeugen","Cannot create file %s"),file);
}
void ReadPaste(char *file)
{
   FILE *in;
   int size;
   struct Head {
      ULONG Form,FSize;
      ULONG Cell,Cfld,CSize;
      USHORT Width,Height;
   } head;

   FreePaste();

   if(in=fopen(file,"r")) {
      if(!fread(&head,sizeof(head),1,in)) {
         notify(CellWnd,STR("Lesefehler, bzw. keine Cell-Datei","Read error or no cell-file"));
         fclose(in);
         return;
      }

      if(head.Form!=ID('F','O','R','M') ||
         head.Cell!=ID('C','E','L','L') ||
         head.Cfld!=ID('C','F','L','D')) {
         notify(CellWnd,STR("Keine Cell-Datei","No cell-file"));
         fclose(in);
         return;
      }

      pasteX=head.Width;
      pasteY=head.Height;

      if(!(Paste=AllocVec(pasteX*pasteY,0))) {
         notify(CellWnd,STR("Nicht genügend Speicher","Not enough memory"));
         fclose(in);
         return;
      }

      size=pasteX*pasteY;

      if(!fread(Paste,size,1,in)) {
         notify(CellWnd,STR("Lesefehler","Read error"));
         FreePaste();
      }
      fclose(in);
   } else notify(CellWnd,STR("Kann Datei %s nicht öffnen","Cannot open file %s"),file);
}

void FreePaste(void)
{
   if(Paste) FreeVec(Paste);
   Paste=NULL;
}
int GetPaste(int cut)
{
   int x,y,yy,yy2;

   pasteX=mark_right-mark_left+3;
   pasteY=mark_bottom-mark_top+3;

   FreePaste();
   Paste=AllocVec(pasteX*pasteY,MEMF_CLEAR);

   if(!Paste) return(1);

   for(y=1;y<pasteY-1;y++) {
      yy=y*pasteX;
      yy2=(y+mark_top)*FWidth;
      for(x=1;x<pasteX-1;x++) {
         Paste[yy+x]=Field[yy2+x+mark_left];
         if(cut) Field[yy2+x+mark_left]=EMPTY;
      }
   }
   RefreshField();

   return(0);
}
int PushPaste(void)
{
   return(writeclip(Paste,pasteX,pasteY));
}

void DoPaste(void)
{
   int x,y,yy,yy2;

   if(!Paste) return;

   for(y=1;y<pasteY-1 && y+mark_top<FHeight-1;y++) {
      yy=y*pasteX;
      yy2=(y+mark_top)*FWidth;
      for(x=1;x<pasteX-1 && x+mark_left<FWidth-1;x++) {
         Field[yy2+x+mark_left]=Paste[yy+x];
      }
   }
   return;
}
int PopPaste(void)
{
   int err;
   FreePaste();
   Paste=readclip(&pasteX,&pasteY,&err);
   return(err);
}

void Rotate90(void)
{
   UBYTE *buf;
   int x,y;

   if(Paste) {
      if(buf=AllocVec(pasteX*pasteY,0)) {

         for(y=0;y<pasteY;y++) {
            for(x=0;x<pasteX;x++) {
               buf[x*pasteY+pasteY-y-1]=Paste[y*pasteX+x];
            }
         }
         x=pasteX;
         pasteX=pasteY;
         pasteY=x;
         FreeVec(Paste);
         Paste=buf;
      } else notify(CellWnd,STR("Kein Speicher","Not enough memory"));
   }
}
void Rotate180(void)
{
   UBYTE *buf;
   int x,y;

   if(Paste) {
      if(buf=AllocVec(pasteX*pasteY,0)) {

         for(y=0;y<pasteY;y++) {
            for(x=0;x<pasteX;x++) {
               buf[(pasteY-y-1)*pasteX+pasteX-x-1]=Paste[y*pasteX+x];
            }
         }
         FreeVec(Paste);
         Paste=buf;
      } else notify(CellWnd,STR("Kein Speicher","Not enough memory"));
   }
}
void Rotate270(void)
{
   UBYTE *buf;
   int x,y;

   if(Paste) {
      if(buf=AllocVec(pasteX*pasteY,0)) {

         for(y=0;y<pasteY;y++) {
            for(x=0;x<pasteX;x++) {
               buf[(pasteX-x-1)*pasteY+y]=Paste[y*pasteX+x];
            }
         }
         x=pasteX;
         pasteX=pasteY;
         pasteY=x;
         FreeVec(Paste);
         Paste=buf;
      } else notify(CellWnd,STR("Kein Speicher","Not enough memory"));
   }
}

#define SWAP_UBYTE(x,y) {UBYTE __swap;__swap=x;x=y;y=__swap;}

void MirrorX(void)
{
   int x,y;

   if(Paste) {

      for(y=0;y<pasteY;y++) {
         for(x=0;x<(pasteX>>1);x++) {
            SWAP_UBYTE(Paste[y*pasteX+x],Paste[y*pasteX+pasteX-x-1]);
         }
      }
   }
}
void MirrorY(void)
{
   int x,y;

   if(Paste) {

      for(y=0;y<(pasteY>>1);y++) {
         for(x=0;x<pasteX;x++) {
            SWAP_UBYTE(Paste[y*pasteX+x],Paste[(pasteY-y-1)*pasteX+x]);
         }
      }
   }
}



