#include "common.h"

extern myfile* filelist;

// R0=chandle, R1=name, R2=value
void setint(regs &r)
{
myfile* myf=filelist->find(r.r[0]);
if(myf == NULL) { seterror("Unknown handle in MyChoices SWI"); return; }

if(nameok(r.r[1])==0) { return; }

std::string name=(char*)(r.r[1]);

if(myf->openw() == 0) { seterror("Unable to open file in MyChoices SWI"); return; } // Open file for writing

myf->writeint4(name,r.r[2]);
}

// R0=chandle, R1=name, R2=memory, R3=length
void setmem(regs &r)
{
myfile* myf=filelist->find(r.r[0]);
if(myf == NULL) { seterror("Unknown handle in MyChoices SWI"); return; }

if(nameok(r.r[1])==0) { return; }
if(r.r[2]==0) { seterror("Memory pojnter cannot be null in MyChoices SWI"); return; }

std::string name=(char*)(r.r[1]);

if(myf->openw() == 0) { seterror("Unable to open file in MyChoices SWI"); return; } // Open file for writing

myf->writemem(name,r.r[2],r.r[3]);
}

// R0=chandle, R1=name, R2=value
void setstring(regs &r)
{
myfile* myf=filelist->find(r.r[0]);
if(myf == NULL) { seterror("Unknown handle in MyChoices SWI"); return; }

if(nameok(r.r[1])==0) { return; }
if(r.r[2]==0) { seterror("Missing string in MyChoices SWI"); return; }

std::string name=(char*)(r.r[1]);
std::string value=(char*)(r.r[2]);

if(myf->openw() == 0) { seterror("Unable to open file in MyChoices SWI"); return; } // Open file for writing

myf->writes(name,value);
}

// R0=chandle, R1=name, R2=value(PTR)
void setfloat(regs &r,FloatFormat ff)
{
myfile* myf=filelist->find(r.r[0]);
if(myf == NULL) { seterror("Unknown handle in MyChoices SWI"); return; }

if(nameok(r.r[1])==0) { return; }
if(r.r[2]==0) { seterror("Missing pointer in MyChoices SWI"); return; }

std::string name=(char*)(r.r[1]);
void* mem=(void*)(r.r[2]);

if(myf->openw() == 0) { seterror("Unable to open file in MyChoices SWI"); return; } // Open file for writing

myf->writef(name,mem,ff);
}

void del(regs &r)
{
myfile* myf=filelist->find(r.r[0]);
if(myf == NULL) { seterror("Unknown handle in MyChoices SWI"); return; }

if(nameok(r.r[1])==0) { return; }

std::string name=(char*)(r.r[1]);

if(myf->openw() == 0) { seterror("Unable to open file in MyChoices SWI"); return; } // Open file for writing

myf->del(name);
}


// Finds a block suitable for 'name'. If name exists return that block if large enough, if not reset the block to type NON and find another suitable one. Return end of file if nothing suitable found.
// Returns the blocksize so can be used again or zero if was not found
int  myfile::setblock(std::string name,int size)
{
// Find if name matches first, priority
fseek(fhandle,0,SEEK_SET);
int counter=0;
long int best=-1;             // Store the smallest empty block that is equal or bigger to size
int      bestsize=0x7FFFFFFF; // The best size block
long int lastpos=0;          // Last block position
int      lasttype=-1;        // Last block type (-1 means unknown)
int      lastsize=-1;        // Last size
while(feof(fhandle)==0)
 {
 long int pos=ftell(fhandle);
 int blocksize=fread4(fhandle);
 if(blocksize == 0) { break; } // break if zero, must be end of file
 long int typepos=ftell(fhandle);
 int blocktype=fread2(fhandle);
 int blocknamesize=fread4(fhandle);
 int blockdatasize=fread4(fhandle);
 if(blocktype != NON)
  {
  std::string blockname=freads(fhandle,blocknamesize);
  if(blockname == name)
   {
                 if(blocksize<size) // Matched but not enough space
                  {
                  long int resetpos=ftell(fhandle);
                          // Wipe all data in block
                          fseek(fhandle,typepos,SEEK_SET);
                          int len=blocksize-4;
                          while(len>0)
                           {
                           fputc(0,fhandle);
                           len--;
                           }
                  fseek(fhandle,resetpos,SEEK_SET);
                  }
                 else
                  {
                  fseek(fhandle,pos,SEEK_SET); return blocksize; // enough space, name matches, use this
                  }
   }
  else
   {
   // Type is something filled, and the name doesn't match, check if there is free space at the end of the block that could be made into a new block
   int usedsize=HEADER_FIXED+blocknamesize+blockdatasize; // How much of the space is used
   if(this->open==OPEN_RW && blocksize>=usedsize+MAKE_FREE_SPACE)
    {
    // Write into middle of existing block first, then shrink the existing block
    fseek(fhandle,pos+usedsize,SEEK_SET); // Move to end of used size (start of new block)
    fwrite4(fhandle,blocksize-usedsize);  // Set this block to the rest of the space
    fwrite2(fhandle,NON);                 // Make it a NON block, it contains nothing
    fwrite4(fhandle,0);
    fwrite4(fhandle,0);
    fseek(fhandle,pos,SEEK_SET);          // Move to start of block (start of existing block)
    fwrite4(fhandle,usedsize);            // Shrink the block size
    }
   }
  }


 else // blocktype==NON, calculate if this block is ok, and the best option so far
  {
  if(blocksize>=size)
   {
   if(blocksize<bestsize)
    {
    best=pos;
    bestsize=blocksize;
    }
   }
  else
   {
   // Ok, this is a NON blocktype, check if the last one was a NON as well, merge blocks if they were
   if(this->open==OPEN_RW && lasttype==NON)
    {
    fseek(fhandle,lastpos,SEEK_SET);     // Go back to last block
    fwrite4(fhandle,blocksize+lastsize); // Write a new header with the size last size + this size
    fwrite2(fhandle,NON);                // Make it a NON block, it contains nothing
    fwrite4(fhandle,0);
    fwrite4(fhandle,0);
    }
   }
  }
 fseek(fhandle,pos+blocksize,SEEK_SET);
 lastpos = pos;
 lasttype= blocktype;
 lastsize= blocksize;
 counter++;
 if(counter>=0x7FFFFFFF) { break; } // emergency
 }


// Was there a best block that could be used?
if(best >= 0)
 {
 fseek(fhandle,best,SEEK_SET);
 return bestsize;
 }
// Move to end of file if didn't find a match and return zero
fseek(fhandle,0,SEEK_END);
return 0;
}


void myfile::writeint4(std::string name,int data)
{
if(open == OPEN_C) { openw(); }
if(open == OPEN_C) { seterror("Error opening file in MyChoices SWI"); return; }
fseek(fhandle,0,SEEK_SET);
//int headersize=fread4(fhandle);
int size=HEADER_FIXED + name.length() + 4;
// find a free block of the required size, pointer should be set or at end of file if doesn't exist
int blocksize = this->setblock(name,size);
if(blocksize != 0) size=blocksize;
// Write to the block
fwrite4(fhandle,size);
fwrite2(fhandle,INT);
fwrite4(fhandle,name.length());
fwrite4(fhandle,4);
fwrites(fhandle,name);
fwrite4(fhandle,data);
}

void myfile::writef(std::string name,void* mem,FloatFormat ff)
{
if(open == OPEN_C) { openw(); }
if(open == OPEN_C) { seterror("Error opening file in MyChoices SWI"); return; }
fseek(fhandle,0,SEEK_SET);
//int headersize=fread4(fhandle);
int size=HEADER_FIXED + name.length() + 20;
// find a free block of the required size, pointer should be set or at end of file if doesn't exist
int blocksize = this->setblock(name,size);
if(blocksize != 0) size=blocksize;
// Convert to standard format
int sign,exponent,special;
uint64_t mantissa;
memory_to_float_variables(mem,ff,sign,exponent,mantissa,special);
// Write to the block
fwrite4(fhandle,size);
fwrite2(fhandle,FLT);
fwrite4(fhandle,name.length());
fwrite4(fhandle,20);
fwrites(fhandle,name);
fwrite4(fhandle,sign);
fwrite4(fhandle,exponent);
uint32_t hi=(uint32_t)(mantissa>>32);
uint32_t lo=(uint32_t)(mantissa & 0xFFFFFFFF);
fwrite4(fhandle,(int)hi);
fwrite4(fhandle,(int)lo);
fwrite4(fhandle,special);
}

void myfile::writes(std::string name,std::string data)
{
if(open == OPEN_C) { openw(); }
if(open == OPEN_C) { seterror("Error opening file in MyChoices SWI"); return; }
fseek(fhandle,0,SEEK_SET);
//int headersize=fread4(fhandle);
int size=HEADER_FIXED + name.length() + data.length();
if(data.length()>64) size+=32; // Add some extra space for strings, these tend to change size and this can be more efficient at the cost of space used/speed. Only do for strings over a certain size
// find a free block of the required size, pointer should be set or at end of file if doesn't exist
int blocksize = this->setblock(name,size);
if(blocksize != 0) size=blocksize;
// Write to the block
fwrite4(fhandle,size);
fwrite2(fhandle,STR);
fwrite4(fhandle,name.length());
fwrite4(fhandle,data.length());
fwrites(fhandle,name);
fwrites(fhandle,data);
}

void myfile::writemem(std::string name,regint start,regint length)
{
if(length<0) length=0;
if(open == OPEN_C) { openw(); }
if(open == OPEN_C) { seterror("Error opening file in MyChoices SWI"); return; }
fseek(fhandle,0,SEEK_SET);
//int headersize=fread4(fhandle);
int size=HEADER_FIXED + name.length() + length;
if(length<256) size+=32; // Add some extra space for small blocks, can change size without warning
// find a free block of the required size, pointer should be set or at end of file if doesn't exist
int blocksize = this->setblock(name,size);
if(blocksize != 0) size=blocksize;
// Write to the block
fwrite4(fhandle,size);
fwrite2(fhandle,MEM);
fwrite4(fhandle,name.length());
fwrite4(fhandle,length);
fwrites(fhandle,name);
fwritem(fhandle,fileno(fhandle),start,length);
}

void myfile::del(std::string name)
{
// Find if name matches first, priority
fseek(fhandle,0,SEEK_SET);
int counter=0;
while(feof(fhandle)==0)
 {
 long int pos=ftell(fhandle);
 int blocksize=fread4(fhandle);
 if(blocksize == 0) { break; } // break if zero, must be end of file
 long int typepos=ftell(fhandle);
 int blocktype=fread2(fhandle);
 int blocknamesize=fread4(fhandle);
 int blockdatasize=fread4(fhandle);
 IGNORE(blockdatasize);
 if(blocktype != NON)
  {
  std::string blockname=freads(fhandle,blocknamesize);
  if(blockname == name)
   {
   //long int resetpos=ftell(fhandle);
   fseek(fhandle,typepos,SEEK_SET);
   //fwrite2(fhandle,NON);
   //fseek(fhandle,resetpos,SEEK_SET);
   int len=blocksize-4;
   while(len>0)
    {
    fputc(0,fhandle);
    len--;
    }
   return;
   }
  }
 fseek(fhandle,pos+blocksize,SEEK_SET);
 counter++;
 if(counter>=0x7FFFFFFF) { break; } // emergency
 }

fseek(fhandle,0,SEEK_END);
}
