/* Mednafen - Multi-system Emulator
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "types.h"
#include "mednafen.h"
#include "driver.h"
#include "endian.h"
#include "general.h"
#include "state.h"
#include "movie.h"
#include "memory.h"
#include "netplay.h"
#include "video.h"

static int SaveStateStatus[10];
static uint32 StateShow;
static uint32 *StateShowPB = NULL; //[160 * 120]; // = NULL; //[160 * 120]; // 320 x 240 -> 160 * 120
static uint32 StateShowPBWidth;
static uint32 StateShowPBHeight;

#define RLSB 		MDFNSTATE_RLSB	//0x80000000


int32 smem_read(StateMem *st, void *buffer, uint32 len)
{
 if((len + st->loc) > st->len)
  return(0);

 memcpy(buffer, st->data + st->loc, len);
 st->loc += len;

 return(len);
}

int32 smem_write(StateMem *st, void *buffer, uint32 len)
{
 if((len + st->loc) > st->malloced)
 {
  uint32 newsize = (st->malloced >= 32768) ? st->malloced : (st->initial_malloc ? st->initial_malloc : 32768);

  while(newsize < (len + st->loc))
   newsize *= 2;
  st->data = (uint8 *)realloc(st->data, newsize);
  st->malloced = newsize;
 }
 memcpy(st->data + st->loc, buffer, len);
 st->loc += len;

 if(st->loc > st->len) st->len = st->loc;

 return(len);
}

int32 smem_putc(StateMem *st, int value)
{
 uint8 tmpval = value;
 if(smem_write(st, &tmpval, 1) != 1)
  return(-1);
 return(1);
}

int32 smem_tell(StateMem *st)
{
 return(st->loc);
}

int32 smem_seek(StateMem *st, uint32 offset, int whence)
{
 switch(whence)
 {
  case SEEK_SET: st->loc = offset; break;
  case SEEK_END: st->loc = st->len - offset; break;
  case SEEK_CUR: st->loc += offset; break;
 }

 if(st->loc > st->len)
 {
  st->loc = st->len;
  return(-1);
 }

 if(st->loc < 0)
 {
  st->loc = 0;
  return(-1);
 }

 return(0);
}

int smem_write32le(StateMem *st, uint32 b)
{
 uint8 s[4];
 s[0]=b;
 s[1]=b>>8;
 s[2]=b>>16;
 s[3]=b>>24;
 return((smem_write(st, s, 4)<4)?0:4);
}

int smem_read32le(StateMem *st, uint32 *b)
{
 uint8 s[4];

 if(smem_read(st, s, 4) < 4)
  return(0);

 *b = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);

 return(4);
}

static int SubWrite(StateMem *st, SFORMAT *sf, int data_only, gzFile fp)
{
 uint32 acc=0;

 while(sf->s || sf->desc)	// Size can sometimes be zero, so also check for the text description.  These two should both be zero only at the end of a struct.
 {
  if(!sf->s || !sf->v)
  {
   sf++;
   continue;
  }
  if(sf->s == (uint32)~0)		/* Link to another struct.	*/
  {
   uint32 tmp;

   if(!(tmp=SubWrite(st,(SFORMAT *)sf->v, data_only, fp)))
    return(0);
   acc+=tmp;
   sf++;
   continue;
  }

  if(!data_only)
   acc+=32 + 4;			/* Description + size */

  int32 bytesize = sf->s&(~(MDFNSTATE_RLSB32 | MDFNSTATE_RLSB16 | RLSB));

  acc += bytesize;
  //printf("%d %d %d\n", bytesize, data_only, fp);
  if(st || fp)			/* Are we writing or calculating the size of this block? */
  {
   char desco[32];
   int slen = strlen(sf->desc);

   memset(desco, 0, 32);

   if(slen > 32) slen = 32;

   memcpy(desco, sf->desc, slen);

   if(!data_only)
   {
    smem_write(st, desco, 32);
    smem_write32le(st, bytesize);
   }

   /* Flip the byte order... */
   if(sf->s & MDFNSTATE_RLSB32)
    Endian_A32_NE_to_LE(sf->v, bytesize / sizeof(uint32));
   else if(sf->s & MDFNSTATE_RLSB16)
    Endian_A16_NE_to_LE(sf->v, bytesize / sizeof(uint16));
   else if(sf->s&RLSB)
    Endian_V_NE_to_LE(sf->v, bytesize);

   if(fp)
   {
    //printf("Wrote: %d\n", bytesize);
    gzwrite(fp, sf->v, bytesize);
   }
   else
   {
    smem_write(st, (uint8 *)sf->v, bytesize);
   }
   /* Now restore the original byte order. */
   if(sf->s & MDFNSTATE_RLSB32)
    Endian_A32_LE_to_NE(sf->v, bytesize / sizeof(uint32));
   else if(sf->s & MDFNSTATE_RLSB16)
    Endian_A16_LE_to_NE(sf->v, bytesize / sizeof(uint16));
   else if(sf->s&RLSB)
    Endian_V_LE_to_NE(sf->v, bytesize);
  }
  sf++; 
 }

 return(acc);
}

static int WriteStateChunk(StateMem *st, const char *sname, SFORMAT *sf, int data_only)
{
 int bsize;

 if(!data_only)
  smem_write(st, (uint8 *)sname, 4);

 bsize=SubWrite(0,sf, data_only, NULL);

 if(!data_only)
  smem_write32le(st, bsize);

 if(!SubWrite(st,sf, data_only, NULL)) return(0);

 if(data_only)
  return(bsize);
 else
  return (bsize + 4 + 4);
}

static SFORMAT *CheckS(SFORMAT *sf, uint32 tsize, const char *desc)
{
 while(sf->s || sf->desc) // Size can sometimes be zero, so also check for the text description.  These two should both be zero only at the end of a struct.
 {
  if(!sf->s || !sf->v)
  {
   sf++;
   continue;
  }
  if(sf->s==(uint32)~0)		/* Link to another SFORMAT structure. */
  {
   SFORMAT *tmp;
   if((tmp= CheckS((SFORMAT *)sf->v, tsize, desc) ))
    return(tmp);
   sf++;
   continue;
  }
  char check_str[32];
  memset(check_str, 0, sizeof(check_str));

  strncpy(check_str, sf->desc, 32);

  if(!memcmp(desc, check_str, 32))
  {
   uint32 bytesize = sf->s&(~(MDFNSTATE_RLSB32 | MDFNSTATE_RLSB16 | RLSB));

   if(tsize != bytesize)
    return(0);
   return(sf);
  }
  sf++;
 }
 return(0);
}

// Fast raw chunk reader
static void DOReadChunk(StateMem *st, SFORMAT *sf)
{
 while(sf->s || sf->desc)       // Size can sometimes be zero, so also check for the text description.  
				// These two should both be zero only at the end of a struct.
 {
  if(!sf->s || !sf->v)
  {
   sf++;
   continue;
  }

  if(sf->s == (uint32) ~0) // Link to another SFORMAT struct
  {
   DOReadChunk(st, (SFORMAT *)sf->v);
   sf++;
   continue;
  }

  int32 bytesize = sf->s&(~(MDFNSTATE_RLSB32 | MDFNSTATE_RLSB16 | RLSB));

  smem_read(st, (uint8 *)sf->v, bytesize);
  sf++;
 }
}

bool MDFNSS_WriteMovieJoyChunk(StateMem *st, gzFile fp, SFORMAT *sf)
{
 return(SubWrite(st, sf, 1, fp));
}

bool MDFNSS_ReadMovieJoyChunk(StateMem *st, gzFile fp, SFORMAT *sf)
{
 while(sf->s || sf->desc)       // Size can sometimes be zero, so also check for the text description.
                                // These two should both be zero only at the end of a struct.
 {
  if(!sf->s || !sf->v)
  {
   sf++;
   continue;
  }

  if(sf->s == (uint32) ~0) // Link to another SFORMAT struct
  {
   MDFNSS_ReadMovieJoyChunk(st, fp, (SFORMAT *)sf->v);
   sf++;
   continue;
  }

  int32 bytesize = sf->s&(~(MDFNSTATE_RLSB32 | MDFNSTATE_RLSB16 | RLSB));

  if(st)
   smem_read(st, (uint8 *)sf->v, bytesize);
  else
   gzread(fp, (uint8 *)sf->v, bytesize);
  if(sf->s & MDFNSTATE_RLSB32)
   Endian_A32_LE_to_NE(sf->v, bytesize / sizeof(uint32));
  else if(sf->s & MDFNSTATE_RLSB16)
   Endian_A16_LE_to_NE(sf->v, bytesize / sizeof(uint16));
  else if(sf->s&RLSB)
   Endian_V_LE_to_NE(sf->v, bytesize);

  sf++;
 }
 return(1);
}

static int ReadStateChunk(StateMem *st, SFORMAT *sf, int size, int data_only)
{
 SFORMAT *tmp;
 int temp;

 if(data_only)
 {
  DOReadChunk(st, sf);
 }
 else
 {
  temp = smem_tell(st);
  while(smem_tell(st) < temp + size)
  {
   uint32 tsize;
   char toa[32];

   if(smem_read(st, toa, 32) <= 0)
    return 0;

   int x, nr;

   for(x = nr = 0; x < 32; x++)
   {
    if(toa[nr] == '"') // Strip out quotes to fix loading save states created in 0.2.0 through 0.3.1
    {
     if(nr < 32 && toa[nr] != 0)
      nr++;
    }
    toa[x] = (nr >= 32) ? 0 : toa[nr];
    nr++;
   }
   smem_read32le(st, &tsize);
   if((tmp=CheckS(sf,tsize,toa)))
   {
    int32 bytesize = tmp->s&(~(MDFNSTATE_RLSB32 | MDFNSTATE_RLSB16 | RLSB));

    smem_read(st, (uint8 *)tmp->v, bytesize);

    if(tmp->s & MDFNSTATE_RLSB32)
     Endian_A32_LE_to_NE(tmp->v, bytesize / sizeof(uint32));
    else if(tmp->s & MDFNSTATE_RLSB16)
     Endian_A16_LE_to_NE(tmp->v, bytesize / sizeof(uint16));
    else if(tmp->s&RLSB)
     Endian_V_LE_to_NE(tmp->v, bytesize);
   }
   else
    if(smem_seek(st,tsize,SEEK_CUR) < 0)
     return(0);
  } // while(...)
 }
 return 1;
}

int CurrentState=0;
extern int geniestage;

static void MakeStatePreview(uint8 *dest, uint32 *fb, MDFN_Rect *LineWidths, int xdiv, int ydiv)
{
 int sqx, sqy;
 int bitcow = 8;

 if(xdiv == 2) bitcow++;
 if(ydiv == 2) bitcow++;

 if(!fb || !LineWidths) return;
 for(sqy=MDFNGameInfo->DisplayRect.y / ydiv;sqy<(MDFNGameInfo->DisplayRect.y + MDFNGameInfo->DisplayRect.h) / ydiv;sqy++)
 {
  for(sqx=0;sqx<MDFNGameInfo->width / xdiv;sqx++)
  {
   int x, y;
   uint32 r,g,b;

   r=g=b=0;
   for(y=0;y<ydiv;y++)
    for(x=0;x<xdiv;x++)
    {
     uint32 real_x;
     uint32 pixel; 
     int nr, ng, nb;

     if(LineWidths[0].w != ~0)
      real_x = 256 * LineWidths[sqy * ydiv + y].x + 256 * (LineWidths[sqy * ydiv + y].w * sqx * xdiv + x) / MDFNGameInfo->width;
     else
      real_x = 256 * MDFNGameInfo->DisplayRect.x + 256 * (MDFNGameInfo->DisplayRect.w * sqx * xdiv + x) / MDFNGameInfo->width;

     pixel = fb[(real_x >> 8) + ((sqy * ydiv) + y) * MDFNGameInfo->pitch / sizeof(uint32)];
     DECOMP_COLOR(pixel, nr, ng, nb);

     r += nr * (256 - (real_x & 0xFF));
     g += ng * (256 - (real_x & 0xFF));
     b += nb * (256 - (real_x & 0xFF));

     pixel = fb[((real_x >> 8) + 1) + ((sqy * ydiv) + y) * MDFNGameInfo->pitch / sizeof(uint32)];
     DECOMP_COLOR(pixel, nr, ng, nb);

     r += nr * (real_x & 0xFF);
     g += ng * (real_x & 0xFF);
     b += nb * (real_x & 0xFF);     
    }
   *dest++ = r >> bitcow;
   *dest++ = g >> bitcow;
   *dest++ = b >> bitcow;
  }
 }
}

/* This function is called by the game driver(NES, GB, GBA) to save a state. */
int MDFNSS_StateAction(StateMem *st, int load, int data_only, std::vector <SSDescriptor> &sections)
{
 std::vector<SSDescriptor>::iterator section;

 if(load)
 {
  char sname[4];

  for(section = sections.begin(); section != sections.end(); section++)
  {
   if(data_only)
   {
    ReadStateChunk(st, section->sf, ~0, 1);
   }
   else
   {
    int found = 0;
    uint32 tmp_size;
    uint32 total = 0;
    while(smem_read(st, (uint8 *)sname, 4) == 4)
    {
     if(!smem_read32le(st, &tmp_size)) return(0);
     total += tmp_size + 8;
     // Yay, we found the section
     if(!memcmp(sname, section->name, 4))
     {
      if(!ReadStateChunk(st, section->sf, tmp_size, 0))
       return(0);
      found = 1;
      break;
     } 
     else
     {
	//puts("SEEK");
      if(smem_seek(st, tmp_size, SEEK_CUR) < 0)
       return(0);
     }
    }
    if(!found) // Not found.  We are sad!
    {
    }
    if(smem_seek(st, -total, SEEK_CUR) < 0)
     return(0);
   }
  }
 }
 else
  for(section = sections.begin(); section != sections.end(); section++)
  {
   if(!WriteStateChunk(st, section->name, section->sf, data_only))
    return(0);
  }
 return(1);
}

int MDFNSS_StateAction(StateMem *st, int load, int data_only, SFORMAT *sf, const char *name)
{
 std::vector <SSDescriptor> love;

 love.push_back(SSDescriptor(sf, name));
 return(MDFNSS_StateAction(st, load, data_only, love));
}

int MDFNSS_SaveSM(StateMem *st, int wantpreview, int data_only, uint32 *fb, MDFN_Rect *LineWidths)
{
        static uint8 header[32]="MEDNAFENSVESTATE";
	int neowidth, neoheight;
	bool OriginalImage = 0;

	neowidth = MDFNGameInfo->width;
	neoheight = MDFNGameInfo->DisplayRect.h;

	if(neowidth < 256 && neoheight < 224) // Fudge, FIXME
	{
	 OriginalImage = 1;
	}
	else
	{
	 neowidth /= 2;
	 neoheight /= 2;
	}

	if(!data_only)
	{
         memset(header+16,0,16);
	 MDFN_en32lsb(header + 16, MEDNAFEN_VERSION_NUMERIC);
	 MDFN_en32lsb(header + 24, neowidth);
	 MDFN_en32lsb(header + 28, neoheight);
	 smem_write(st, header, 32);
	}

	if(wantpreview)
	{
         uint8 *previewbuffer = (uint8 *)malloc(3 * neowidth * neoheight);

         MakeStatePreview(previewbuffer, fb, LineWidths, OriginalImage ? 1 : 2, OriginalImage ? 1 : 2);
         smem_write(st, previewbuffer, 3 * neowidth * neoheight);

	 free(previewbuffer);
	}

	MDFNGameInfo->StateAction(st, 0, data_only);

	if(!data_only)
	{
	 uint32 sizy = smem_tell(st);
	 smem_seek(st, 16 + 4, SEEK_SET);
	 smem_write32le(st, sizy);
	}
	return(1);
}

int MDFNSS_Save(const char *fname, const char *suffix, uint32 *fb, MDFN_Rect *LineWidths)
{
	gzFile st = NULL;

	if(geniestage==1)
	{
	 MDFN_DispMessage((UTF8 *)_("Cannot use states in GG Screen."));
	 return(0);
        }

	if(fname)
	 st=gzopen(fname, "wb6");
	else
	{
         st=gzopen(MDFN_MakeFName(MDFNMKF_STATE,CurrentState,suffix).c_str(),"wb6");
	}

	if(st == NULL)
	{
	 if(!fname && !suffix)
          MDFN_DispMessage((UTF8 *)_("State %d save error."),CurrentState);
	 return(0);
	}

	MDFNSS_SaveFP(st, fb, LineWidths);
	SaveStateStatus[CurrentState] = 1;
	gzclose(st);

	if(!fname && !suffix)
	 MDFN_DispMessage((UTF8 *)_("State %d saved."),CurrentState);
	return(1);
}

int MDFNSS_LoadSM(StateMem *st, int haspreview, int data_only)
{
        uint8 header[32];
	uint32 stateversion;

	if(data_only)
	{
	 stateversion = MEDNAFEN_VERSION_NUMERIC;
	}
	else
	{
         smem_read(st, header, 32);
         if(memcmp(header,"MEDNAFENSVESTATE",16))
          return(0);

	 stateversion = MDFN_de32lsb(header + 16);
	}

	if(!stateversion) stateversion = 1;

	if(haspreview)
        {
         uint32 width = MDFN_de32lsb(header + 24);
         uint32 height = MDFN_de32lsb(header + 28);
	 uint32 psize;

         if(!width) width = MDFNGameInfo->width / 2;
	 if(!height) height = MDFNGameInfo->height / 2;

	 if(MDFNGameInfo->system == GISYS_PCE)
	 {
	  if(stateversion < 0x0303)
	   height = 240;
	  else if(stateversion < 0x0305)
	   height = 224;
	 }
	 psize = width * height * 3;
	 smem_seek(st, psize, SEEK_CUR);	// Skip preview
 	}
	return(MDFNGameInfo->StateAction(st, stateversion, data_only));
}

int MDFNSS_LoadFP(gzFile fp)
{
 uint8 header[32];
 StateMem st;
 
 memset(&st, 0, sizeof(StateMem));

 if(gzread(fp, header, 32) != 32)
 {
  return(0);
 }
 st.len = MDFN_de32lsb(header + 16 + 4);

 if(st.len < 32)
  return(0);

 if(!(st.data = (uint8 *)malloc(st.len)))
  return(0);

 memcpy(st.data, header, 32);
 if(gzread(fp, st.data + 32, st.len - 32) != ((int32)st.len - 32))
 {
  free(st.data);
  return(0);
 }
 if(!MDFNSS_LoadSM(&st, 1, 0))
 {
  free(st.data);
  return(0);
 }
 free(st.data);
 return(1);
}


int MDFNSS_SaveFP(gzFile fp, uint32 *fb, MDFN_Rect *LineWidths)
{
 StateMem st;

 memset(&st, 0, sizeof(StateMem));

 if(!MDFNSS_SaveSM(&st, 1, 0, fb, LineWidths))
 {
  if(st.data)
   free(st.data);
  return(0);
 }

 if(gzwrite(fp, st.data, st.len) != (int32)st.len)
 {
  if(st.data)
   free(st.data);
  return(0);
 }

 if(st.data)
  free(st.data);

 return(1);
}

int MDFNSS_Load(const char *fname, const char *suffix)
{
	gzFile st;

        if(geniestage==1)
        {
         MDFN_DispMessage((UTF8 *)_("Cannot use states in GG Screen."));
         return(0);
        }

        if(fname)
         st=gzopen(fname, "rb");
        else
        {
         st=gzopen(MDFN_MakeFName(MDFNMKF_STATE,CurrentState,suffix).c_str(),"rb");
	}

	if(st == NULL)
	{
	 if(!fname && !suffix)
	 {
          MDFN_DispMessage((UTF8 *)_("State %d load error."),CurrentState);
          SaveStateStatus[CurrentState]=0;
	 }
	 return(0);
	}

	if(MDFNSS_LoadFP(st))
	{
	 if(!fname && !suffix)
	 {
          SaveStateStatus[CurrentState]=1;
          MDFN_DispMessage((UTF8 *)_("State %d loaded."),CurrentState);
          SaveStateStatus[CurrentState]=1;
	 }
	 gzclose(st);
         return(1);
        }   
        else
        {
         SaveStateStatus[CurrentState]=1;
         MDFN_DispMessage((UTF8 *)_("State %d read error!"),CurrentState);
	 gzclose(st);
         return(0);
        }
}

void MDFNSS_CheckStates(void)
{
        gzFile st=NULL;
        int ssel;

        for(ssel=0;ssel<10;ssel++)
        {
         st=gzopen(MDFN_MakeFName(MDFNMKF_STATE,ssel,0).c_str(),"rb");
         if(st)
         {
          SaveStateStatus[ssel]=1;
          gzclose(st);
         }
         else
          SaveStateStatus[ssel]=0;
        }

	CurrentState=0;
	StateShow=0;
}

void MDFNI_SelectState(int w)
{
 gzFile fp;
 if(w == -1) { StateShow = 0; return; }
 MDFNI_SelectMovie(-1);

 CurrentState=w;
 StateShow = MDFND_GetTime() + 2000;

 fp = gzopen(MDFN_MakeFName(MDFNMKF_STATE,CurrentState,NULL).c_str(),"rb");
 if(fp)
 {
  uint8 header[32];
  int x,y;

  gzread(fp, header, 32);
  uint32 width = MDFN_de32lsb(header + 24);
  uint32 height = MDFN_de32lsb(header + 28);

  if(!width) width = MDFNGameInfo->width / 2;
  if(!height) height = MDFNGameInfo->height / 2;

  if(width > 512) width = 512;
  if(height > 512) height = 512;

  {
   uint8 previewbuffer[3 * width * height];
   uint8 *rptr = previewbuffer;

   gzread(fp, previewbuffer, 3 * width * height);

   if(StateShowPB)
   {
    free(StateShowPB);
    StateShowPB = NULL;
   }
   StateShowPB = (uint32 *)malloc(4 * width * height);
   StateShowPBWidth = width;
   StateShowPBHeight = height;

   for(y=0; y<height; y++)
    for(x=0;x<width; x++)
    {
     StateShowPB[x + y * width] = MK_COLORA(rptr[0],rptr[1],rptr[2], 0xFF);
     rptr+=3;
    }

   gzclose(fp);
  }
 }
 else
 {
  if(StateShowPB)
  {
   free(StateShowPB);
   StateShowPB = NULL;
  }
  StateShowPBWidth = MDFNGameInfo->width / 2;
  StateShowPBHeight = MDFNGameInfo->DisplayRect.h / 2;
 }
 MDFN_ResetMessages();
}  

void MDFNI_SaveState(const char *fname, const char *suffix, uint32 *fb, MDFN_Rect *LineWidths)
{
 StateShow=0;
 MDFNSS_Save(fname, suffix, fb, LineWidths);
}

void MDFNI_LoadState(const char *fname, const char *suffix)
{
 StateShow=0;

 /* For network play and movies, be load the state locally, and then save the state to a temporary buffer,
    and send or record that.  This ensures that if an older state is loaded that is missing some
    information expected in newer save states, desynchronization won't occur(at least not
    from this ;)).
 */
 if(MDFNSS_Load(fname, suffix))
 {
  #ifdef NETWORK
  if(MDFNnetplay)
   MDFNNET_SendState();
  #endif
  if(MDFNMOV_IsRecording())
   MDFNMOV_RecordState();
 }
}

bool MDFN_DrawSaveStates(uint32 *XBuf, const MDFN_Rect *OSDRect, uint32 pitch32)
{
 if(StateShow < MDFND_GetTime()) return(0);

 MDFN_DrawStateMovieRow((uint32 *)XBuf, OSDRect, pitch32, StateShowPB,StateShowPBWidth, StateShowPBHeight, SaveStateStatus,CurrentState, (UTF8 *)_("-select state-"));
 return(1);
}


#include "minilzo.h"

typedef struct
{
	uint8 *compressed_data;
	uint32 compressed_len;
	uint32 uncompressed_len;

	StateMem MovieLove;
} StateMemLZO;

#define SRW_NUM		600

static int EvilEnabled = 0;
static StateMemLZO *bcs;
static signed int bcspos;
static StateMem sm_compress_buffer;

void MDFN_StateEvilBegin(void)
{
 int x;

 if(!EvilEnabled)
  return;

 bcs = (StateMemLZO *)calloc(SRW_NUM, sizeof(StateMemLZO));
 bcspos = 0;

 memset(&sm_compress_buffer, 0, sizeof(StateMem));

 for(x=0;x<SRW_NUM;x++)
 {
  bcs[x].compressed_data = NULL;
  bcs[x].compressed_len = 0;
 }
}

bool MDFN_StateEvilIsRunning(void)
{
 return(EvilEnabled);
}

void MDFN_StateEvilEnd(void)
{
 int x;

 if(!EvilEnabled)
  return;

 if(bcs)
 {
  if(MDFNMOV_IsRecording())
   MDFN_StateEvilFlushMovieLove();

  for(x = 0;x < SRW_NUM; x++)
  {
   if(sm_compress_buffer.data)
    free(sm_compress_buffer.data);
   sm_compress_buffer.data = NULL;

   if(bcs[x].compressed_data)
    free(bcs[x].compressed_data);
   bcs[x].compressed_data = NULL;
   bcs[x].compressed_len = 0;
  }
  free(bcs);
 }
}

void MDFN_StateEvilFlushMovieLove(void)
{
 int bahpos = (bcspos + 1) % SRW_NUM;
 for(int x = 0; x < SRW_NUM; x++)
 {
  if(bcs[bahpos].MovieLove.data)
  {
   if(bcs[x].compressed_data)
    MDFNMOV_ForceRecord(&bcs[bahpos].MovieLove);
   free(bcs[bahpos].MovieLove.data);
   bcs[bahpos].MovieLove.data = NULL;
  }
  bahpos = (bahpos + 1) % SRW_NUM;
 }
}

int MDFN_StateEvil(int rewind)
{
 if(!EvilEnabled)
  return(0);

 if(rewind)
 {
  int dootpos = bcspos - 1;
  if(dootpos < 0) dootpos += SRW_NUM;

  if(bcs[dootpos].compressed_data)
  {
   if(bcs[bcspos].MovieLove.data)
   {
    free(bcs[bcspos].MovieLove.data);
    bcs[bcspos].MovieLove.data = NULL;
   }
   free(bcs[bcspos].compressed_data);
   bcs[bcspos].compressed_data = NULL;
   bcs[bcspos].compressed_len = 0;
  }
  bcspos--;
  if(bcspos < 0) bcspos += SRW_NUM;
  if(!bcs[bcspos].compressed_len)
   bcspos = (bcspos + 1) % SRW_NUM;

  if(bcs[bcspos].compressed_len)
  {
   StateMem sm;
   uint8 *tmp_buf;
   lzo_uint dst_len = bcs[bcspos].uncompressed_len;

   tmp_buf = (uint8 *)malloc(bcs[bcspos].uncompressed_len);

   lzo1x_decompress(bcs[bcspos].compressed_data, bcs[bcspos].compressed_len, tmp_buf, &dst_len, NULL);

   sm.data = tmp_buf;
   sm.loc = 0;
   sm.initial_malloc = 0;
   sm.malloced = sm.len = bcs[bcspos].uncompressed_len;

   MDFNSS_LoadSM(&sm, 0, 1);
   free(tmp_buf);

   free(MDFNMOV_GrabRewindJoy().data);
   return(1);
  }
 }
 else
 {
  uint8 workmem[LZO1X_1_MEM_COMPRESS];
  bcspos = (bcspos + 1) % SRW_NUM;

  if(MDFNMOV_IsRecording())
  {
   if(bcs[bcspos].compressed_data && bcs[bcspos].MovieLove.data)
   {
    //printf("Force: %d\n", bcspos);
    MDFNMOV_ForceRecord(&bcs[bcspos].MovieLove);
    free(bcs[bcspos].MovieLove.data);
    bcs[bcspos].MovieLove.data = NULL;
   }
  }
  if(bcs[bcspos].compressed_data)
  {
   free(bcs[bcspos].compressed_data);
   bcs[bcspos].compressed_data = NULL;
  }
  if(bcs[bcspos].MovieLove.data)
  {
   free(bcs[bcspos].MovieLove.data);
   bcs[bcspos].MovieLove.data = NULL;
  }
  sm_compress_buffer.loc = 0;
  sm_compress_buffer.len = 0;
  MDFNSS_SaveSM(&sm_compress_buffer, 0, 1);
  uint8 * tmp_buf = (uint8 *)malloc((size_t)(1.10 * sm_compress_buffer.len));
  lzo_uint dst_len = (lzo_uint)(1.10 * sm_compress_buffer.len);
  lzo1x_1_compress(sm_compress_buffer.data, sm_compress_buffer.len, tmp_buf, &dst_len, workmem);

  bcs[bcspos].compressed_data = (uint8 *)realloc(tmp_buf, dst_len);
  bcs[bcspos].compressed_len = dst_len;
  bcs[bcspos].uncompressed_len = sm_compress_buffer.len;

  if(MDFNMOV_IsRecording())
   bcs[bcspos].MovieLove = MDFNMOV_GrabRewindJoy();
 }
 return(0);
}

void MDFNI_EnableStateRewind(int enable)
{
 if(MDFNGameInfo)
  MDFN_StateEvilEnd();

 EvilEnabled = enable;

 if(MDFNGameInfo)
  MDFN_StateEvilBegin();
}
