/* x-check.c */

#include "kernel.h"
#include <stdio.h>
#include <ctype.h>
#include "swis.h"
#include <stdarg.h>
#include <setjmp.h>

#include "chunks.h"

#define FREE 0x45455246

static jmp_buf jb;

static void ASSERT(int t)
{
   static _kernel_oserror assertFailed = { 0, "Assert failed" };

   if (!t)
      e(&assertFailed);
}

static void e(_kernel_oserror *err)
{
   if (err)
      longjmp(jb, (int) err);
}

static int power2(int x)
{
   while (x && !(x & 1)) x >>= 1;
   return x == 1;
}

static void test(int c, const char *fmt, ...)
{
   va_list ap;
   va_start(ap, fmt);

   if (!c)
   {
      char buffer[256];
      vsprintf(buffer, fmt, ap);
      printf("x-check: %s\n", buffer);
   }

   va_end(ap);
}

static int okOffset(xFiles_info *pInfo, unsigned offset)
{
   return (offset & (pInfo->fileHeader.allocationSize - 1)) == 0;
}

static void testChunk(xFiles_info *pInfo, unsigned cnkNum, xFiles_chunk *pChunk)
{
   unsigned maxChunk;

   test(pInfo->fileHeader.chunkTable.size % sizeof(xFiles_chunk) == 0,
        "Bad size for chunk table %d",
        pInfo->fileHeader.chunkTable.size);

   maxChunk = pInfo->fileHeader.chunkTable.size / sizeof(xFiles_chunk);

   test(cnkNum < maxChunk, "Bad chunk number %d", cnkNum);

   if (pChunk->usage == FREE)
   {
      test(pChunk->offset < maxChunk,
           "Chunk %d (free) bad offset %08x", cnkNum, pChunk->offset);

      test(pChunk->size == FREE,
           "Chunk %d (free) size is %08x (should be %08x)",
           cnkNum, pChunk->size, FREE);

      test(pChunk->allocSize == FREE,
           "Chunk %d (free) allocSize is %08x (should be %08x)",
           cnkNum, pChunk->allocSize, FREE);
   }
   else
   {
      test(okOffset(pChunk->offset),
           "Chunk %d (used) bad offset %08x", cnkNum, pChunk->offset);
      test(pChunk->size <= pChunk->allocSize)
           "Chunk %d (used) bad size %08x (allocSize %08x)",
           cnkNum, pChunk->size, pChunk->allocSize);
      test(okOffset(pChunk->allocSize),
           "Chunk %d (used) bad allocSize %08x", cnkNum, pChunk->allocSize);
   }
}

static int chcmp(const void *a, const void *b)
{
   const xFiles_chunk *ca = a;
   const xFiles_chunk *cb = b;

   return (int) ca->offset - (int) cb->offset;
}

static void checkChunkTable(xFiles_info *pInfo)
{
   xFiles_chunk *ct;
   unsigned maxChunk, freeChunk;
   unsigned i, j;

   maxChunk = pInfo->fileHeader.chunkTable.size / sizeof(xFiles_chunk);

   ct = malloc(maxChunk * sizeof(xFiles_chunk));
   ASSERT(ct != NULL);

   e(xFiles_readChunk(pInfo, ct, 0, maxChunk * sizeof(xFiles_chunk), 0));

   /* First follow the free chain */

   freeChunk = pInfo->fileHeader.freeChunk;

   while (freeChunk != 0)
   {
      testChunk(pInfo, freeChunk, &ct[freeChunk]);
      ct[freeChunk].usage = 0;
      freeChunk = ct[freeChunk].offset;
   }

   /* Scan for any untouched free chunks and also discard any free chunks ready
    * for checking the used ones.
    */   

   for (i = 0, j = 0; i < maxChunk; i++)
   {
      if (ct[i].usage == FREE)
      {
         printf("x-check: free chunk %d is unlinked\n", c);
      }

      if (ct[i].size != free)
         ct[j++] = ct[i];
   }

   maxChunk = j;

   /* Now sort the array on offset so we can look for overlaps */

   qsort(ct, maxChunk, sizeof(xFiles_chunk), chcmp);

   free(ct);
}

static _kernel_oserror *check(const char *fileName, int fix)
{
   xFiles_info info;
   _kernel_swi_regs regs;
   _kernel_oserror *err;
   unsigned fileSize;

   regs.r[0] = 0x4F;
   regs.r[1] = (int) fileName;
   if (err = _kernel_swi(OS_Find, &regs, &regs), err)
      return err;

   info.FileHandle = regs.r[0];

   if (err = xFiles_openImage(&info), err)
      goto fail;

   if (err = xFiles_getLength(&info, &fileSize), err)
      goto fail;

   if (err = (_kernel_oserror *) setjmp(jb), err)
      goto fail;

   /* Validate the header */

   test(info.fileHeader.hdrSize == sizeof(xFiles_header),
        "Illegal hdrSize %d",
        info.fileHeader.hdrSize);

   test(info.fileHeader.structureVersion == xFiles_STRUCTUREVERSION,
        "Illegal structureVersion %d",
        info.fileHeader.structureVersion);

   test(info.fileHeader.directoryVersion == xFiles_DIRECTORYVERSION,
        "Illegal directoryVersion %d",
        info.fileHeader.directoryVersion);

   testChunk(&info, 0, &info.fileHeader.chunkTable);

   test(power2(info.fileHeader.allocationUnit),
        "Illegal allocationUnit %d",
        info.fileHeader.allocationUnit);

   testChunkTable(&info);
   test(info.fileHeader.waste < fileSize,
        "Illegal waste %d (fileSize is %d)",
        info.fileHeader.waste, fileSize);

   testStructure(&info);     

   if (err = xFiles_FlushFileInfo(&info), err)
      goto fail;

   regs.r[0] = 0;
   regs.r[1] = info.fileHandle;
   return _kernel_swi(OS_Find, &regs, &regs);

fail:
   regs.r[0] = 0;
   regs.r[1] = info.fileHandle;
   (void) _kernel_swi(OS_Find, &regs, &regs);
   return err;
}

int main(int argc, char *argv[])
{
   int argn;
   int fix = 0;

   for (argn = 1; argn < argc; argn++)
   {
      if (*argv[argn] == '-')
      {
         switch (tolower(argv[argn][1]))
         {
            case 'f':
               fix = 1;
               break;
            default:
               fprintf(stderr, "Ignoring unknown option \"%s\"\n", argv[argn]);
               break;
         }
      }
   }

   for (argn = 1; argn < argc; argn++)
   {
      if (*argv[argn] != '-')
      {
         check(argv[argn], fix);
      }
   }

   return 0;
}
