/*
 * sprstore.c
 * Part of the !Memphis distribution
 * (c) bdb/nas, 1991-3
 */

/* #define DEBUG */
/* includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "sprite.h"
#include "util.h"
#include "core.h"
#include "spr.h"
#include "Lfile.h"

#include "store.h"

/* debug support */
#ifdef DEBUG
#define DEBUGF printf
#else
#define DEBUGF 1?(void)0:(void)printf
#endif

/* vars */
#define ROOTFILETYPE 0x3F1              /* Acorn-allocated */

#define STOREHANDBUFSIZE 10

/* storeid will be NULL for sprite area, or else a ptr to this */

struct storeid
{ char basename[256];
  LHANDLE storehandle[STOREHANDBUFSIZE];   /* OS file handle */
  int storeinode[STOREHANDBUFSIZE];    /* inode number or -1 */
  int nextone;
  storeid next;
};

static storeid thestoreids;

static DEFERR( mb_Lostsprite,           0,  "sprite lost" );
static DEFERR( mb_Lostfile,             0,  "host file lost" );

/*
 * store interface part defines 
 */
typedef struct file_header
{       sprite_header   h;
        char    data[4];
} file_header;

/*
 * Root Lfile header
 */
typedef struct root_header
{
  char id[4];   /* "MemF" */
  char ctype;   /* compression type, 1 = squash */
  char res1;    /* reserved, must be 0 */
  char res2;
  char res3;
} root_header;

#define FHSIZE ( sizeof( file_header )-4 )
#define ASERROR ==_kernel_ERROR?_kernel_last_oserror():NULL


static file_header *findstoresprite( int inode ) /* Find sprite for an inode*/
{ SPRITENAME sname;
  sprintf( sname.name, "mfs%08x", inode );
  return (file_header *)findsprite( sname );
}

static char *storename(storeid s,int inode ) /* Make host file name for an inode*/
{ static char buf[256],*b1;
  strcpy(buf,s->basename);
  b1 = strrchr(buf,';');
  if (b1)
  { *b1 = ':';
    if (b1>buf && b1[-1]==';')
      b1[-1]=':';
  }
  b1=buf+strlen(buf);
  while (inode>0)
  { char b[4];
    sprintf(b,",.%c","0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[inode%36]);
    strins(b1,b);
    inode/=36;
  }
  return buf;
}

static int storefindh( storeid s,int inode ) /* Open host file for an inode*/
{ char *p;
  int n;
  for (n=0;n<STOREHANDBUFSIZE;n++)
    if (s->storeinode[n]==inode)
      return n;
  n = s->nextone;
  if (++s->nextone>=STOREHANDBUFSIZE)
    s->nextone=0;
  if (s->storeinode[n]!=-1 && s->storehandle[n])
    Lclose(s->storehandle[n]);
  p=storename(s,inode);
  if (Lopen(p,&s->storehandle[n]))
    s->storehandle[n]=NULL;
  s->storeinode[n]=inode;
DEBUGF("storefindhand(%d),%s=%d\n",inode,p,n);
  return n;
}

static LHANDLE storefindhand( storeid s,int inode ) /* Open host file for an inode*/
{
  return s->storehandle[storefindh(s,inode)];
}

_kernel_oserror *Store_Init(void) /* Initialise store subsystem*/
{
  DEBUGF("Store_Init\n");
  thestoreids = NULL;
  return Linit();
}

void Store_Flush(void)
{ storeid s,t;
  for (s=thestoreids;s;s=t)
  { int i;
    for (i=0;i<STOREHANDBUFSIZE;i++)
    { if (s->storeinode[i]!=-1 && s->storehandle[i])
        Lclose(s->storehandle[i]);
      s->storeinode[i]=-1;
    }
    t = s->next;
  }
}

_kernel_oserror *Store_Finish(void) /* Terminate store subsystem*/
{ storeid s,t;
  Store_Flush();
  for (s=thestoreids;s;s=t)
  { t = s->next;
    free(s);
  }
  thestoreids = NULL;
  return Lfinish();
}

storeid Store_Find( char *special_field, char *volume ) /* Find a storeid*/
{ storeid s;
  int i;
  volume = volume;
  DEBUGF("Store_Find %s %s\n",special_field?special_field:"NULL",volume?volume:"NULL");
  if (!special_field)
    return NULL;
  for (s=thestoreids;s;s=s->next)
    if (!strcmp(s->basename,special_field))
      return s;
  s = calloc(1,sizeof(struct storeid));
  strcpy(s->basename,special_field);
  for (i=0;i<STOREHANDBUFSIZE;i++)
    s->storeinode[i]=-1;
  s->next = thestoreids;
  s->nextone = 0;
  thestoreids = s;
  return s;
}

_kernel_oserror *Store_Read(storeid s, int inode, int offset, int length, void *ptr) 
{
  DEBUGF("Store_Read(%s,%d,%d,%d,%p)\n",s?s->basename:"Mem",inode,offset,length,ptr);
  if (s)
  { LHANDLE p=storefindhand(s,inode);
    if (!p)
      return ERR(mb_Lostfile);
    return Lread(p,offset,length,ptr);
  }
  else
  { file_header *h=findstoresprite(inode);
    if (!h)
      return ERR(mb_Lostsprite);
    memcpy( ptr, &h->data[offset], length );
    return NULL;
  }
}

_kernel_oserror *Store_Write(storeid s,int inode, int offset, int length, void *ptr )
{
  DEBUGF("Store_Write(%s,%d,%d,%d,%p)\n",s?s->basename:"Mem",inode,offset,length,ptr);
  if (s)
  { LHANDLE p=storefindhand(s,inode);
    if (!p)
      return ERR(mb_Lostfile);
    return Lwrite(p,offset,length,ptr);
  }
  else
  { file_header *h=findstoresprite(inode);
    if (!h)
      return ERR(mb_Lostsprite);
    memcpy( &h->data[offset], ptr, length );
    return NULL;
  }
}

_kernel_oserror *Store_SetLength( storeid s,int inode, int length )
{ DEBUGF("Store_SetLength(%s,%d,%d)\n",s?s->basename:"Mem",inode,length);
  if (s)
  { int n=storefindh(s,inode);
    _kernel_osfile_block b;
    if (length==0)
    { if (s->storehandle[n])
      { Lclose(s->storehandle[n]);
        s->storehandle[n]=0;
        return _kernel_osfile(6,storename(s,inode),&b) ASERROR;
      }
      else
        return NULL;
    }
    else
    { int h=storefindh(s,inode);
      if (s->storehandle[h])
        return Lsetlength(s->storehandle[h],length);
      else
      { _kernel_oserror *err;
        char *p,*n;
        s->storeinode[h]=-1;
        err = Lcreate(n=storename(s,inode),length);
        if (err)
        { for (p=n;*p;p++)
            if (*p=='.')
            { *p=0;
              _kernel_osfile(8,n,&b);
              *p='.';
            }
          err = Lcreate(n=storename(s,inode),length);
        }
        /* Create root inode */
        if (!inode)
        { root_header root;
          b.load=ROOTFILETYPE;
          _kernel_osfile(18,n,&b);
          strcpy(root.id, "MemF");
          root.ctype = 1; root.res1 = 0; root.res2 = 0; root.res3 = 0;
        }
        DEBUGF("Create %s len %d %s\n",n,length,err?"ERR":"OK");
        return err;
      }
    }
  }
  else
  {
    file_header *h=findstoresprite( inode );
    if (length==0)
    { if (h)
        deletesprite(&h->h);
      return NULL;
    }
    else
    { length = ((length-1)|127)+1;
      if (h)
        return setspritesize( &h->h, length+FHSIZE-SHSIZE );
      else
      { sprite_header *h;
        SPRITENAME sname;
        sprintf( sname.name, "mfs%08x", inode );
        return createsprite( sname, length+FHSIZE-SHSIZE, &h );
      }
    }
  }
}

_kernel_oserror *Store_FreeSpace( storeid s, struct freespace *b )
{ _kernel_swi_regs r;
  _kernel_oserror *err=NULL;
  if (!s)
    spritefreespace(b);
  else
  { r.r[0]=49;
    r.r[1]=(int)storename(s,0);
    err=_kernel_swi(OS_FSControl,&r,&r);
    b->free=r.r[0];
    b->biggest = r.r[1];
    b->size = r.r[2];
  }
  return err;
}

char * Store_Name( storeid s ) 
{ char *p;
  if (!s)
    return "Sprites";
  p=strrchr(s->basename,'.');
  if (p)
    return p+1;
  else
    return s->basename;
}

char * Store_SpecialField( storeid s ) 
{
  if (!s)
    return NULL;
  return s->basename;
}


