/* map.c for !DiscEx */


#include "main.h"
#include "alloc.h"
#include "dinfo.h"
#include "map.h"


static char *map;             /* addresses a copy of the FileCore disc map */


/* used by next_one_bit(..) and get_bits(..) */
static int mask1[] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
static int mask2[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };



/*
 * v is a bit array, and offset indexes an entry in this array.
 *
 * The result is the index of the first 'one' bit found whose index is
 *  greater than or equal to offset.
 */

static int next_one_bit (char *v, int offset)
{
  int byte = offset >> 3;
  int bit = offset & 0x7;

  if ((v[byte] & mask1[bit]) == 0)
  {
    do
    {
      byte++;
    } while (v[byte] == 0);
    bit = 0;
  }

  while ((v[byte] & mask2[bit]) == 0)
    bit++;

  return ((byte << 3) + bit);
}


/*
 * v is a bit array, and offset indexes an entry in this array.
 *
 * The result is the integer value of the bit-string whose least significant
 *  bit is v[offset], and whose most significant bit is v[offset+len-1].
 */

static int get_bits (char *v, int offset, int len)
{
  int byte1 = offset >> 3;
  int bit1 = offset & 0x7;
  int byte2 = (offset + len) >> 3;
  int bit2 = (offset + len) & 0x7;
  int x, y;
  int bits;

 /* pick up first part */
  y = v[byte1] & mask1[bit1];

 /* does this contain all there is? */
  if (byte1 == byte2)
  {
    y &= ~mask1[bit2];
    y >>= bit1;
    return y;
  }

  y >>= bit1;
  bits = 8 - bit1;

 /* now add any complete bytes to y */
  do
  {
    byte1++;
    if (byte1 == byte2) break;
    y |= v[byte1] << bits;
    bits += 8;
  } while (TRUE);

 /* and any final part */
  x = v[byte2] & ~mask1[bit2];
  y |= x << bits;

  return y;
}


/*
 * Scan a map block, calling a function to process each fragment found.
 *
 * The map block is in buffer 'buf', and is for zone number 'zone'.
 *
 * f(int zone, int fragid, int pos, int len, void *handle) is called for
 *  each fragment in turn, where:
 *
 *    zone    - the zone number
 *    fragid  - the fragment identifier (or -1 if it is a free fragment)
 *    pos     - the position of the start of the fragment, as an offset in
 *               allocation units from the start of the zone
 *    len     - the length of the fragment in allocation units
 */

error * map_scan_block
(
    DiscInfoPtr di,
    char *buf,
    int zone,
    error * (*f) (DiscInfoPtr di,
                  int zone, int fragid, int pos, int len, void *handle),
    void *handle
)
{
    int mapstart, mapend, mapentry, free;

    /* set mapstart to offset of first bit of allocation bytes */
    mapstart = 4 * 8;    /* allow for map block header */
    if (zone == 0)        /* and for disc record if it's the first zone */
        mapstart += 60 * 8;

    mapentry = mapstart;

    /* set mapend to one beyond offset of final bit of allocation bytes */
    mapend = (di->secsize + 4) * 8 - di->zonespare;
    if (zone == di->dr.nzones - 1 && zone != 0)
        /* the last zone is a special case for a hard disc */
        mapend = di->lastzonemapend;

    /* set free to offset of first free map entry */
    free = get_bits (buf, 8, di->dr.idlen) + 8;

    /* process each map entry in turn */
    while (mapentry < mapend)
    {
        int frag_id = get_bits (buf, mapentry, di->dr.idlen);
        int next_entry = next_one_bit (buf, mapentry + di->dr.idlen) + 1;
        int size = next_entry - mapentry;
        BOOL is_free = (mapentry == free);

        ER ( f (di,
                zone,
                is_free ? -1 : frag_id,
                mapentry - mapstart,
                size,
                handle) );

        if (is_free)
            free += frag_id;

        mapentry = next_entry;
    }

    if (mapentry != mapend)
        return error_lookup ("BadMapBlock", zone);

    return NULL;
}


/*
 * Read the FileCore map for the specified drive into memory.
 */

error * map_read (int drive, DiscInfoPtr di)
{
    DiscRecordPtr dr = &di->dr;

    /* allocate space for the map */
    ER ( check_alloc ((void **) &map, dr->nzones << dr->log2secsize) );

    /* read it in */
    ER ( _swix (di->bigdisc ? di->FS_BigDiscOp : di->FS_DiscOp, I1|I2|I3|I4,
                         1,            /* read sectors */
                         (drive << 29) +
                             (di->bigdisc ? di->mapaddr :
                                            di->mapaddr << dr->log2secsize),
                         (int) map,
                         dr->nzones << dr->log2secsize) );

    return NULL;
}


/*
 * Free space occupied by the current map in memory.
 */

void map_free (void)
{
    free (map);

    return;
}


/*
 * Set 'buf' to the address of the start of the map for the given zone; this
 *  function should only be called after map_read(..) has been called and
 *  before map_free(..).
 */

error * map_find_zone
(
    DiscInfoPtr di,
    int zone,
    char **buf
)
{
    *buf = map + zone * di->secsize;

    return NULL;
}
