/*
 * The program performs a test of updating the block allocation bitmap
 * on the given image.
 *
 * It does the following:
 * 1. makes a copy of the original images
 * 2. mounts and enforces block allocation bitmap update
 * 3. compares if the updated bitmap is the same as the original
 *    (every bit different counts as an error)
 * Obviously, the test can be performed only on images with already correct
 * block allocation bitmap (which should not be changed by the enforced update).
 *
 * More details on bitmap issues:
 * https://github.com/adflib/ADFlib/issues/63
 */


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

#include "adflib.h"
#include "log.h"

#define TEST_VERBOSITY 1


ADF_RETCODE copy_file( const char * const dst_fname,
                       const char * const src_fname );

unsigned compare_bitmaps( struct AdfVolume * const volOrig,
                          struct AdfVolume * const volUpdate );

char * num32_to_bit_str( uint32_t num,
                         char     str[33] );


int main( const int          argc,
          const char * const argv[] )
{
    log_init( stderr, TEST_VERBOSITY );

    if ( argc < 2 ) {
        log_error( "Usage: %s <ADF_IMAGE>\n", argv[0] );
        return 1;
    }

    /* set filenames */
    const char * const adfOrig         = argv[1];
    char               adfUpdate[1024] = "\0";
    strcat( adfUpdate, adfOrig );
    strcat( adfUpdate, "-bitmap_updated.adf" );

    bool error_status = false;

    /* make a copy for updating */
    if ( copy_file( adfUpdate, adfOrig ) != ADF_RC_OK ) {
        error_status = true;
        goto delete_adf_copy;
    }

    /* init adflib */
    adfLibInit();
    adfEnvSetProperty( ADF_PR_IGNORE_CHECKSUM_ERRORS, true );
//	adfEnvSetFct(0,0,MyVer,0);

    /* mount the original image */
    struct AdfDevice * devOrig = adfDevOpen( adfOrig, ADF_ACCESS_MODE_READONLY );
    if ( ! devOrig ) {
        log_error( "Cannot open file/device '%s' - aborting...\n", adfOrig );
        error_status = true;
        goto clean_up;
    }

    ADF_RETCODE rc = adfDevMount( devOrig );
    if ( rc != ADF_RC_OK ) {
        log_error( "can't mount device %s\n", adfOrig );
        error_status = true;
        goto close_dev_orig;
    }

    struct AdfVolume * const volOrig = adfVolMount( devOrig, 0,
                                                    ADF_ACCESS_MODE_READONLY );
    if ( volOrig == NULL ) {
        log_error( "can't mount volume %d\n", 0 );
        error_status = true;
        goto umount_dev_orig;
    }

    
    /* mount the image copy (for updating) */
    struct AdfDevice * const devUpdate = adfDevOpen( adfUpdate,
                                                     ADF_ACCESS_MODE_READWRITE );
    if ( ! devUpdate ) {
        log_error( "Cannot open file/device '%s' - aborting...\n", adfUpdate );
        error_status = true;
        goto umount_vol_orig;
    }

    rc = adfDevMount ( devUpdate );
    if ( rc != ADF_RC_OK ) {
        log_error( "can't mount device %s\n", adfUpdate );
        error_status = true;
        goto close_dev_updated;
    }

    struct AdfVolume * volUpdate = adfVolMount( devUpdate, 0,
                                                ADF_ACCESS_MODE_READONLY );
    if ( volUpdate == NULL ) {
        log_error( "can't mount volume %d\n", 0 );
        error_status = true;
        goto umount_dev_updated;
    }


    /* update the block allocation bitmap */
    rc = adfVolRemount( volUpdate, ADF_ACCESS_MODE_READWRITE );
    if ( rc != ADF_RC_OK ) {
        log_error( "error remounting read-write, volume %d\n", 0 );
        error_status = true;
        goto umount_vol_updated;
    }

    struct AdfRootBlock root;
    rc = adfReadRootBlock( volUpdate, (uint32_t) volUpdate->rootBlock, &root );
    if ( rc != ADF_RC_OK ) {
        log_error( "Invalid RootBlock, volume %s, sector %u - aborting...",
                   volUpdate->volName, volUpdate->rootBlock );
        error_status = true;
        goto umount_vol_updated;
    }

    rc = adfReconstructBitmap( volUpdate, &root );
    if ( rc != ADF_RC_OK ) {
        log_error( "Error rebuilding the bitmap (%d), volume %s",
                   rc, volUpdate->volName );
        error_status = true;
        goto umount_vol_updated;
    }

    /*
    rc = adfUpdateBitmap ( vol );
    if ( rc != ADF_RC_OK ) {
        adfEnv.eFct ( "Error writing updated bitmap (%d), volume %s",
                      rc, volUpdate->volName );
        return rc;
    }

    adfVolUnMount ( volUpdate );

    volUpdate = adfVolMount ( devUpdate, 0,
                              ADF_ACCESS_MODE_READWRITE );
    if ( volUpdate == NULL ) {
        log_error ( stderr, "can't mount volume %d\n", 0 );
        error_status = true;
        goto umount_dev_updated;
    }
    */

    /* compare the original and reconstructed */
    unsigned nerrors = compare_bitmaps( volOrig, volUpdate );
    if ( nerrors != 0 )
        log_error( "Bitmap update of %s: %u errors\n", adfOrig, nerrors );
    
    error_status = ( nerrors != 0 );
    
    //showVolInfo( vol );
    //putchar('\n');

    /* cleanup */

umount_vol_updated:
    adfVolUnMount( volUpdate );
umount_dev_updated:
    adfDevUnMount( devUpdate );
close_dev_updated:
    adfDevClose( devUpdate );

umount_vol_orig:
    adfVolUnMount( volOrig );
umount_dev_orig:
    adfDevUnMount( devOrig );
close_dev_orig:
    adfDevClose( devOrig );

clean_up:
    adfLibCleanUp();
    
delete_adf_copy:
    log_info( "Removing %s\n", adfUpdate );
    remove( adfUpdate );
    
    return ( error_status ? 1 : 0 );
}



#define BUFSIZE 1024

ADF_RETCODE copy_file( const char * const dst_fname,
                       const char * const src_fname )
{
    log_info( "Copying %s to %s... ", src_fname, dst_fname );
    FILE * const src = fopen( src_fname, "rb" );
    if ( src == NULL )
        return ADF_RC_ERROR;

    FILE * const dst = fopen( dst_fname, "wb" );
    if ( src == NULL ) {
        fclose( src );
        return ADF_RC_ERROR;
    }

    ADF_RETCODE status = ADF_RC_OK;
    char buffer[BUFSIZE];
    size_t bytes_read;
    while ( ( bytes_read = fread( buffer, 1, BUFSIZE, src ) ) > 0 ) {
        size_t bytes_written = fwrite( buffer, 1, bytes_read, dst );
        if ( bytes_written != bytes_read ) {
            log_error( "error writing copy to %s\n", dst_fname );
            status = ADF_RC_ERROR;
            break;
        }
    }
    fclose( src );
    fclose( dst );
    log_info( "Done!\n" );
    return status;
}


// returns number of errors
unsigned compare_bitmaps ( struct AdfVolume * const volOrig,
                           struct AdfVolume * const volUpdate )
{
    struct AdfRootBlock   rbOrig, rbUpdate;
    struct AdfBitmapBlock bmOrig, bmUpdate;

    if ( adfReadRootBlock( volOrig, (uint32_t) volOrig->rootBlock,
                           &rbOrig ) != ADF_RC_OK )
    {
        log_error( "invalid RootBlock on orig. volume, sector %u\n",
                   volOrig->rootBlock );

        return 1;
    }

    if ( adfReadRootBlock( volUpdate, (uint32_t) volUpdate->rootBlock,
                           &rbUpdate ) != ADF_RC_OK )
    {
        log_error( "invalid RootBlock on orig. volume, sector %u\n",
                   volOrig->rootBlock );

        return 1;
    } 
    
    /* Check root bm pages  */
    unsigned nerrors = 0;
    for ( unsigned i = 0 ; i < ADF_BM_PAGES_ROOT_SIZE ; i++ ) {
        ADF_SECTNUM bmPageOrig   = rbOrig.bmPages[i],
                    bmPageUpdate = rbUpdate.bmPages[i];

        if ( bmPageOrig == 0 && bmPageUpdate == 0 )
            continue;
             
        if ( ( bmPageOrig == 0 && bmPageUpdate != 0 ) ||
             ( bmPageOrig != 0 && bmPageUpdate == 0 ) )
        {
            log_error( "bmPages[%u]: orig (%u) != ... update (%u)"
                       " - and one of them is 0 -> error -> skipping!\n",
                       i, bmPageOrig, bmPageUpdate );
            nerrors++;
            continue;
        }
        
        if ( bmPageOrig != bmPageUpdate ) {
            // in case of relocation, this could be OK - but return a warning
            log_warning( "bmPages[%u] differ: orig (%u) != update (%u)\n",
                         i, bmPageOrig, bmPageUpdate );
        }

        ADF_RETCODE rc = adfReadBitmapBlock( volOrig, bmPageOrig, &bmOrig );
        if ( rc != ADF_RC_OK ) {
            log_error( "error reading bitmap block on vol. orig, block %u\n",
                       bmPageOrig );
            nerrors++;
            continue;
        }

        rc = adfReadBitmapBlock( volUpdate, bmPageUpdate, &bmUpdate );
        if ( rc != ADF_RC_OK ) {
            log_error( "error reading bitmap block on vol. update, block %u\n",
                       bmPageOrig );
            nerrors++;
            continue;
        }

        char bitStrOrig[33];
        char bitStrUpdate[33];
        for ( unsigned i = 0 ; i < ADF_BM_MAP_SIZE ; i++ ) {
            if ( bmOrig.map[i] != bmUpdate.map[i] ) {
                uint32_t
                    valOrig   = bmOrig.map[i],
                    valUpdate = bmUpdate.map[i];
                log_error( "bm differ at %u:\n"
                           "  orig   0x%08x  %s\n"
                           "  update 0x%08x  %s\n",
                           i,
                           valOrig,   num32_to_bit_str( valOrig, bitStrOrig ),
                           valUpdate, num32_to_bit_str( valUpdate, bitStrUpdate ) );
                nerrors++;
            }
        }
    }

    /* add checking bmExt blocks! */

    return nerrors;
}


char * num32_to_bit_str( uint32_t num,
                         char     str[33] )
{
    for (unsigned i = 0 ; i <= 31 ; i++ ) {
        uint32_t mask = 1 << i;
        uint32_t bitValue = ( num & mask ) >> i;
        str[i] = bitValue ?
            '.' :  // block free
            'o';   // block allocated
    }
    str[32] = '\0';
    return str;
}
