/* FSFS setup/shutdown */

#define FSFS_TYPE 0xA94

#include "kernel.h"
#include "swis.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

static int  declared = FALSE;
void *wsp;

struct discrec
{
   int  log2secsize  : 8;
   int  secspertrack : 8;
   int  heads        : 8;
   int  density      : 8;

   int  idlen        : 8;
   int  log2bpmp     : 8;
   int  skew         : 8;
   int  bootoption   : 8;

   int  lowsector    : 8;
   int  nzones       : 8;
   int  zone_spare   : 16;

   int  root;

   int  disc_size;

   int  disc_id;

   char disc_name[10]; /* I think it can be 12 chars so we'll truncate it... */

   int  disc_type;

   char padding[23];
};

struct nfssector
{
   char marker[4];      // always "AFS0"
   char discname[16];   // can be 16 characters long - truncate to 10?
   char cylinders[2];   // 16 bit integer, LSB first
   char sectors[3];     // 24 bit integer, LSB first
   char physdiscs[1];   // generally unused
   char secspercyl[2];  // 16 bit integer, LSB first
   char secsperbmap[1]; // 8 bit integer
   char drvinc[1];      // generally unused
   char unused[1];      // unused (duh!)
   char rootsin[3];     // SIN of root, 24 bit integer, LSB first
   char initdate[2];    // FS date?
   char firstfree[2];   // 16 bit integer, LSB first
   char isfloppy[1];    // non-zero if floppy disc
};

_kernel_swi_regs regs;

char sectorbuffer[256];

extern int  Image_RO_Base;


void identify_disc(_kernel_swi_regs *);
static void fsfs_finalise(void);
static _kernel_oserror *fsfs_declare(void);
extern void entry_Open(void);
extern void entry_GetBytes(void);
extern void entry_PutBytes(void);
extern void entry_Args(void);
extern void entry_Close(void);
extern void entry_File(void);
extern void entry_Func(void);
extern void entry_Ticker(void);


_kernel_oserror *fsfs_initialise(char *cmd, int pod, void *ws)
{
   _kernel_oserror *err;
   (void) cmd;
   (void) pod;

   atexit(fsfs_finalise);

   fsfs_declare();

   return NULL;
}



static void fsfs_finalise(void)
{
   if (declared == TRUE)
   {
      _kernel_swi_regs r;

      r.r[0] = 36;
      r.r[1] = FSFS_TYPE;
      (void) _kernel_swi(OS_FSControl, &r, &r);
   }

   return;
}



static _kernel_oserror *fsfs_declare(void)
{
   _kernel_oserror *err;
   static int fsInfoBlock[10];

   fsInfoBlock[ 0] = 0;
   fsInfoBlock[ 1] = FSFS_TYPE;
   fsInfoBlock[ 2] = (int) entry_Open         - Image_RO_Base;
   fsInfoBlock[ 3] = (int) entry_GetBytes     - Image_RO_Base;
   fsInfoBlock[ 4] = (int) entry_PutBytes     - Image_RO_Base;
   fsInfoBlock[ 5] = (int) entry_Args         - Image_RO_Base;
   fsInfoBlock[ 6] = (int) entry_Close        - Image_RO_Base;
   fsInfoBlock[ 7] = (int) entry_File         - Image_RO_Base;
   fsInfoBlock[ 8] = (int) &entry_Func        - Image_RO_Base;

   regs.r[0] = 35;
   regs.r[1] = Image_RO_Base;
   regs.r[2] = (int) &fsInfoBlock - Image_RO_Base;
   regs.r[3] = (int) wsp;
   if (err = _kernel_swi(OS_FSControl, &regs, &regs), err)
      return err;

   declared = TRUE;

   return NULL;
}



void fsfs_service(int servicenum, _kernel_swi_regs *r, void *ws)
{

   (void) ws;

   switch (servicenum)
   {
      case 0x00040: /* Service_FSRedeclare */
         (void) fsfs_declare();
         break;

     case 0x00069: /* Service_IdentifyDisc */
                   /* -> R2 = pointer to buffer for format name  [seems to be zero?]
                         R3 = Buffer length                      [seems to be zero?]
                         R5 = Pointer to disc record
                         R6 = Sector cache handle
                         R8 = Pointer to FileCore private word

                      <- R1 = 0 (claim)
                         R2 = Filetype to be given to disc
                         R5 = Pointer to modified disc record
                         R6 = New sector cache handle
                         R8 = Preserved
                   */

                   identify_disc(r);
                   break;

     case 0x0007D: /* Service_DiscDismounted */
                   /* R2 = pointer to "filingsystem::disc" */
                   break;
   }
}


void identify_disc(_kernel_swi_regs *r)
{
   struct discrec *dr = NULL;
   struct nfssector *ns = NULL;
   int    mybyte;

   dr = (struct discrec *)r->r[5];

   // The first step:
   //   Check disc record's sector size, sectors/track, density, and lowest sector ID.

   if ( dr->log2secsize != 8 )
      return; // Log2 Sec Size should be 8 ( 2^8 = 256 )

   if ( dr->secspertrack != 16 )
      return; // Should be 16 sectors per track

   if ( dr->density != 2 )
      return; // Density should be 2

   if ( dr->heads != 2 )
      return; // Two heads only please!

   if ( dr->lowsector != 0 )
      return; // Is zero on my test discs

   // What we shall do NOW is try to read the NFS sector to verify the disc.
   // BUG: We should use FSOp 9 to use the cached version. This works so we'll leave it for now.
   regs.r[0] = 0;
   regs.r[1] = 65 | ( ((int)dr >> 2) << 8); // aiyaiyai!
   regs.r[2] = 0x1E00; /* track 0, head 1, sector 14 */
   regs.r[3] = (int)sectorbuffer;
   regs.r[4] = 256;
   regs.r[8] = r->r[8];
   _kernel_swi(0x60540, &regs, &regs); /* XFileCore_DiscOp */

   // Confirm ID bytes
   if ((sectorbuffer[0] != 'A') |
       (sectorbuffer[1] != 'F') |
       (sectorbuffer[2] != 'S') |
       (sectorbuffer[3] != '0') )
      return; // First four bytes must be "AFS0".

   // Alright then, it is a format we recognise!
   ns = (struct nfssector *)sectorbuffer;

   // Pointer to root (not really required!)
   dr->root = ( (ns->rootsin[0] << 8) | (ns->rootsin[1] << 16) | (ns->rootsin[2] << 24) );

   // Disc size
   dr->disc_size = ( (ns->sectors[0] | (ns->sectors[1] << 8) | (ns->sectors[2] << 16) ) * 256);

   // Hacky way to copy the disc name (first 10 chars ONLY)
   for (int recurse = 0; recurse < 10; recurse++)
   {
      mybyte = ns->discname[recurse];
      if (mybyte < 33) mybyte = 0;
      dr->disc_name[recurse] = mybyte;
   }

   // Hacky disc cycle ID calculation; should be fairly disc-unique
   mybyte = 0;
   for (int recurse = 0; recurse < 10; recurse++)
      mybyte += (dr->disc_name[recurse] * recurse);
   dr->disc_id = mybyte;

   // Patch in disc type marker
   dr->disc_type = FSFS_TYPE;

   // We are supposed to update the 'sequence', 'doublestep' and 'heads'.
   // I have NO idea. I'll patch in something here if it balls' up without! :-)

   // Okay, CLAIM THIS AS OURS!

   // Set the format name.
   // PROBLEM: Seems that buffer pointer/size are both zero?!? FileCore error?
   // strcpy((char *)r->r[2], "FileStore disc");

   r->r[1] = 0; // claim!
   r->r[2] = FSFS_TYPE;
   r->r[5] = (int)dr;
   // leave R6 as-was

   return;
}
