/*
 * This module manages the bitmap for allocating catalogue entries.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <descrip.h>

#define BITS_PER_LONG 32
#include "cds_volume.h"
#include "cds_bitmap.h"

/*
 * Count number of bits set in a buffer.
 */
static int pop_count ( unsigned char *buffer, int bytes )
{
    static char bits_in_byte[256];
    static int pop_count_init = 0;
    int i, j, count;
    if ( !pop_count_init ) {
	for ( i = 0; i < sizeof(bits_in_byte); i++ ) {
	    count = 0;
	    for ( j = i; j > 0; j = j/2 ) if ( j&1 == 1 ) count++;
	    bits_in_byte[i] = count;
	}
    }
    /*
     * Not most efficient means, but calls should be infrequent.
     */
    for ( i = count = 0; i < bytes; i++ ) count += bits_in_byte[buffer[i]];
    return count;
}

/****************************************************************************/
/* Read page from bitmap file into table.
 */
int verify_catalog_bmtable ( struct cds_volume *vol, int page_num )
{
    int status, offset, i;
    char dname[272];
    static $DESCRIPTOR(dname_dx,"");
    extern int LIB$CREATE_DIR();

    if ( vol->bmtable[page_num] ) return 1;	/* page loaded */
    /*
     * Allocate memory.
     */
    vol->bmtable[page_num] = malloc(sizeof(*vol->bmtable[page_num]));
    if ( !vol->bmtable[page_num] ) return 0;	/* alloc failed */
    /*
     * Read page.
     */
    offset = page_num * sizeof(vol->bmtable[page_num]->map);
    fseek ( vol->bitmap, offset, SEEK_SET );
    if ( 0 >= fread ( vol->bmtable[page_num]->map,
	sizeof(vol->bmtable[page_num]->map), 1, vol->bitmap ) ) {
	/*
	 * Error, clear out of bitmap table.
	 */
	free ( vol->bmtable[page_num] );
	vol->bmtable[page_num] = (void *) 0;
	return 0;
    }
    /*
     * Do population count on loaded page.
     */
    vol->bmtable[page_num]->count = pop_count ( 
	(unsigned char *) vol->bmtable[page_num]->map,
	sizeof(vol->bmtable[page_num]->map) );
    vol->bmtable[page_num]->last_offset = 0;
    vol->bmtable[page_num]->dirty = 0;
    /*
     * ensure directory with volnamexx exists and save its directory ID.
     */
    strcpy ( dname, vol->dir );
    i = strlen(vol->dir);
    if ( i > 0 ) dname[i-1] = '.';
    else {
	strcpy ( dname, "sys$disk:[." );
	i = 11;
    }
    sprintf(&dname[i], "%s%02x]", vol->name );

    pthread_lock_global_np();
    dname_dx.dsc$w_length = strlen(dname);
    dname_dx.dsc$a_pointer = dname;

    status = LIB$CREATE_DIR ( &dname_dx, 0, 0, 0, 0, 0 );
    pthread_unlock_global_np();
    if ( status&1 ) {
    } else {
	printf("error status of create_directory: %d\n", status );
    }
    return status;
}
/***************************************************************************/
/* Find free entry in bitmap and mark allocated, returning its number
 * to caller.
 */
int cds_allocate ( struct cds_volume *vol, int *number )
{
    int i, j, n, offset;
    /*
     * Scan bitmap for first free bit.
     */
    for ( i = 0; i < 256; i++ ) {
	/*
	 * Outer loop scans each page.
	 */
	if ( 1&verify_catalog_bmtable ( vol, i ) ) {
	    /*
	     * scan page i, scan from previous offset.
	     */
	    if (vol->bmtable[i]->count >= 
		8*sizeof(vol->bmtable[i]->map)) continue;
	    offset = vol->bmtable[i]->last_offset;
	    for ( j = 0; j < 480; j++ ) {
		if ( vol->bmtable[i]->map[offset] != 0xffffffff ) break;
		offset = (offset < 480) ? offset+1 : 0;
	    }
	    if ( j < 480 ) {
		/*
		 * Found longword with at least 1 free bit find that bit and
		 * compute bit number;
		 */
		long mask;
		mask = vol->bmtable[i]->map[offset];
		vol->bmtable[i]->last_offset = offset;
		n = (i*15360) + (offset*32);

		for ( j = 1; j&mask; j = (j<<1) ) n++;
		vol->bmtable[i]->count++;
		vol->bmtable[i]->map[offset] |= j;
		vol->bmtable[i]->dirty = 1;
		*number = n;
		return 1;
	    }
	} else {
	    /*
	     * Unexpected error, couldn't load table entry.
	     */
	}
    }
    return 0;
}


int cds_deallocate ( struct cds_volume *vol, int number )
{
    int page_num, offset, status;
    long mask;

    page_num = number / 15360;
    number = number - (page_num*15360);
    offset = number / 32;
    number = number - (offset*32);
    status = 0;
    if ( 1&verify_catalog_bmtable ( vol, page_num ) ) {
	mask = 1<<number;
	if ( (mask&vol->bmtable[page_num]->map[offset]) == 0 ) {
	    /*
	     * Bit was already clear.
	     */
	} else {
	    /*
	     * Clear bit and mark page dirty.
	     */
	    vol->bmtable[page_num]->last_offset = offset;
	    vol->bmtable[page_num]->dirty = 1;
	    vol->bmtable[page_num]->map[offset] ^= mask;
	    status = 1;
	}
    } else {
	/* Couldn't read table entry. */
    }

    return 0;
}
/****************************************************************************/
int cds_flush_bitmap ( struct cds_volume *vol, int delete_flag )
{
    int i, offset;
    /*
     * Flush bitmap to file.
     */
    for ( i = 0; i < 256; i++ ) {
	if ( !vol->bmtable[i] ) continue;		/* entry never loaded */
	if ( vol->bmtable[i]->dirty ) {
	    /*
	     * Update data in file.
	     */
	    offset = i * sizeof(vol->bmtable[i]->map);
	    fseek ( vol->bitmap, offset, SEEK_SET );
	    if ( 0 >= fwrite ( vol->bmtable[i]->map, 
		sizeof(vol->bmtable[i]->map), 1, vol->bitmap ) ) {
	        perror ( "bitmap flush" );
	    }
	    vol->bmtable[i]->dirty = 0;
	}
	if ( delete_flag ) {
	    free ( vol->bmtable[i] );
	    vol->bmtable[i] = (void *) 0;
	}
    }
    return 1;
}
/****************************************************************************/
/* Translate number for filename of corresponding data file.
 */
int cds_number_to_fname ( struct cds_volume *vol, int number, char *fname)
{
    int page_num, ndx, version, i;
    page_num = number / 15360;
    number = number - (page_num*15360);
    ndx = number / 60;
    version = number - (ndx*60) + 1;

    strcpy ( fname, vol->dir );
    i = strlen ( fname );
    if ( i == 0 ) {
	strcpy ( fname, "sys$disk:[]" ); i = 11;
    }
    fname[i-1] = '.';
    sprintf ( &fname[i], "%s%02x]%02x;%d", vol->name, page_num, ndx, version );

    return 1;
}
