/******************************************************************************
*
* Copyright (C) 2019-2020  Brian Berlin
* 
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************
*
* FILENAME: test_media.c
*
* DESCRIPTION:  This contains tests for the media module.
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.9.0   03/19/20  bberlin      Creation
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "../src/interface/media_if.h"
#include "../src/interface/toml.h"
#include "../src/interface/katconfig.h"
#include "../src/interface/logger.h"
#include "../src/interface/util.h"
#include "../src/interface/sha1.h"
#include "../src/interface/md5.h"
#include "../src/core/debug.h"

static int failure = 0;
extern t_debug debug;

extern KBYTE memory[0x10000];

void output_result ( int result, const char *test, const char *value,
        const char *error );

static int write_test_car ( const char *path, unsigned int cart_type, int payload_size,
        unsigned char seed ) {

    FILE *fp;
    unsigned char header[16] = {0};
    unsigned char *payload;
    int i;

    payload = malloc ( payload_size );
    if ( !payload )
        return -1;

    memcpy ( header, "CART", 4 );
    header[4] = (cart_type >> 24) & 0xff;
    header[5] = (cart_type >> 16) & 0xff;
    header[6] = (cart_type >> 8) & 0xff;
    header[7] = cart_type & 0xff;

    for ( i = 0; i < payload_size; ++i )
        payload[i] = (unsigned char)(seed + (i / 0x2000));

    fp = fopen ( path, "wb" );
    if ( !fp ) {
        free(payload);
        return -1;
    }

    fwrite ( header, 1, sizeof(header), fp );
    fwrite ( payload, 1, payload_size, fp );
    fclose ( fp );
    free ( payload );

    return 0;

}

static int write_test_bytes ( const char *path, unsigned char *buffer, int size ) {

    FILE *fp;

    fp = fopen ( path, "wb" );
    if ( !fp )
        return -1;

    fwrite ( buffer, 1, size, fp );
    fclose ( fp );

    return 0;
}

static void fill_test_hyb ( unsigned char *buffer, int low_vector, int high_vector ) {

    int bank;

    for ( bank = 0; bank < 64; ++bank )
        memset ( buffer + bank * 0x2000, bank, 0x2000 );

    buffer[0x7fffe] = (unsigned char)low_vector;
    buffer[0x7ffff] = (unsigned char)high_vector;
}

static void send_hybrid_sd_command ( unsigned int dest_addr, unsigned int size,
        unsigned int offset, int bad_trailer ) {

    unsigned char cmd[42] = {0};
    unsigned int sum = 0;
    int i;

    cmd[0] = 0x55;
    cmd[1] = 0xaa;
    cmd[2] = 0x80;
    cmd[3] = 0x7f;
    cmd[4] = 0x0e;
    cmd[5] = 0xf1;
    cmd[6] = dest_addr & 0xff;
    cmd[7] = (dest_addr >> 8) & 0xff;
    cmd[8] = (dest_addr >> 16) & 0xff;
    cmd[9] = (dest_addr >> 24) & 0xff;
    cmd[10] = size & 0xff;
    cmd[11] = (size >> 8) & 0xff;
    cmd[12] = (size >> 16) & 0xff;
    cmd[13] = (size >> 24) & 0xff;
    cmd[14] = offset & 0xff;
    cmd[15] = (offset >> 8) & 0xff;
    cmd[16] = (offset >> 16) & 0xff;
    cmd[17] = (offset >> 24) & 0xff;
    memcpy ( &cmd[18], "test.dat", 9 );

    for ( i = 6; i < 38; ++i )
        sum += cmd[i];

    cmd[38] = sum & 0xff;
    cmd[39] = cmd[38] ^ 0xff;
    cmd[40] = bad_trailer ? 0x00 : 0xa5;
    cmd[41] = 0x5a;

    for ( i = 0; i < 42; ++i )
        read_5200_rom_be_hyb ( 0xbe00 | cmd[i] );
}

static void reset_header_test_machine ( e_machine_type machine ) {

    t_config *p_config = config_get_global_cfg();

    p_config->general.machine = machine;
    console_set_machine_type ( machine );
}

static unsigned long save_profile_without_machine ( const char *path,
        e_machine_type profile_dir_machine, e_cart_type mapping ) {

    t_media profile = {0};
    unsigned long crc = 0;
    unsigned long long mask = 0xfffff & ~KCFG_MEDIA_MACHINE;

    if ( media_if_create_from_file ( (char *)path, &profile ) ) {
        output_result ( 0, "Profile Origin Setup", path,
                "Unable to create media metadata" );
        return 0;
    }

    profile.machine_type = profile_dir_machine;
    profile.mapping = mapping;
    profile.media_type = CART_CART;
    crc = profile.crc;

    media_if_delete_crc ( crc );
    if ( config_save_media_settings ( crc, &profile, mask ) )
        output_result ( 0, "Profile Origin Setup", path,
                "Unable to save profile" );

    media_exit ( &profile );

    return crc;
}

static void run_basic_media_state_tests ( void ) {

    const char *disk_path = "basic_media_crc_game.atr";
    const char *basic_path = "basic_media_crc_basic.rom";
    unsigned char disk[0x2000];
    unsigned char basic[0x2000];
    unsigned long disk_crc;
    unsigned long basic_crc;
    char *old_basic;
    int old_basic_enable;
    int status;
    t_config *p_config = config_get_global_cfg();

    memset ( disk, 0x5a, sizeof(disk) );
    memset ( basic, 0xa5, sizeof(basic) );

    write_test_bytes ( disk_path, disk, sizeof(disk) );
    write_test_bytes ( basic_path, basic, sizeof(basic) );
    disk_crc = crc32 ( 0L, disk, sizeof(disk) );
    basic_crc = crc32 ( 0L, basic, sizeof(basic) );

    old_basic = p_config->general.basic ? strdup ( p_config->general.basic ) : NULL;
    old_basic_enable = p_config->general.basic_enable;

    console_init();
    mem_init();
    reset_header_test_machine ( MACHINE_TYPE_XL );

    util_alloc_string ( &p_config->general.basic, basic_path );
    p_config->general.basic_enable = 1;
    config_save_general ( config_get_global_table(),
            KCFG_GENERAL_BASIC | KCFG_GENERAL_BASIC_EN );

    status = media_if_open_rom ( (char *)disk_path, CART_MIN, 0, 1 );
    output_result ( status == 0, "BASIC Media State", "open XL floppy",
            "Expected media to open with BASIC enabled" );
    output_result ( media_if_get_ptr()->crc == disk_crc,
            "BASIC Media State", "global media CRC",
            "Expected BASIC load to leave selected media CRC unchanged" );
    output_result ( console_get_ptr()->main_media->crc == disk_crc,
            "BASIC Media State", "main media CRC",
            "Expected launched media copy to keep selected media CRC" );
    output_result ( media_if_get_ptr()->crc != basic_crc,
            "BASIC Media State", "BASIC CRC not selected",
            "Expected BASIC CRC not to replace selected media CRC" );

    util_alloc_string ( &p_config->general.basic, old_basic ? old_basic : "" );
    p_config->general.basic_enable = old_basic_enable;
    config_save_general ( config_get_global_table(),
            KCFG_GENERAL_BASIC | KCFG_GENERAL_BASIC_EN );

    free ( old_basic );
    remove ( disk_path );
    remove ( basic_path );
    console_exit();

}

static void run_cart_header_tests ( void ) {

    struct {
        unsigned int cart_type;
        int payload_size;
        e_cart_type expected_mapping;
        const char *name;
    } super_carts[] = {
        { 71, 0x10000, CART_32K_BE, "Type 71" },
        { 72, 0x20000, CART_32K_BE, "Type 72" },
        { 73, 0x40000, CART_32K_BE, "Type 73" },
        { 74, 0x80000, CART_32K_BE, "Type 74" }
    };
    char path[128];
    int i;
    t_media profile = {0};
    unsigned long profile_crc;

    mkdir ( "profiles.5200", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH );
    mkdir ( "profiles.8bit", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH );

    console_init();
    mem_init();

    sprintf ( path, "cart_header_type16.car" );
    write_test_car ( path, 16, 0x4000, 0x10 );
    reset_header_test_machine ( MACHINE_TYPE_800 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_PC_16K,
            "CART Header Mapping", "Type 16", "Expected 8-bit 16K mapping" );
    output_result ( console_get_machine_type() == MACHINE_TYPE_800,
            "CART Header Machine", "Type 16",
            "Expected current machine to remain selected" );
    remove ( path );

    for ( i = 0; i < 4; ++i ) {
        sprintf ( path, "cart_header_super_%u.car", super_carts[i].cart_type );
        write_test_car ( path, super_carts[i].cart_type, super_carts[i].payload_size,
                (unsigned char)(0x20 + i) );
        reset_header_test_machine ( MACHINE_TYPE_800 );
        media_if_open_rom ( path, CART_MIN, 0, 1 );
        output_result ( console_get_ptr()->cart->media_info.mapping ==
                        super_carts[i].expected_mapping,
                "CART Header Mapping", super_carts[i].name,
                "Expected 32K BE mapping" );
        output_result ( console_get_machine_type() == MACHINE_TYPE_800,
                "CART Header Machine", super_carts[i].name,
                "Expected current machine to remain selected" );
        remove ( path );
    }

    sprintf ( path, "cart_header_unknown.car" );
    write_test_car ( path, 200, 0x8000, 0x30 );
    reset_header_test_machine ( MACHINE_TYPE_5200 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_32K,
            "CART Header Fallback", "Type 200", "Expected 32K heuristic mapping" );
    remove ( path );

    sprintf ( path, "cart_header_unmapped.car" );
    write_test_car ( path, 43, 0x20000, 0x35 );
    reset_header_test_machine ( MACHINE_TYPE_5200 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_32K_BE,
            "CART Header Fallback", "Type 43", "Expected current-machine heuristic mapping" );
    remove ( path );

    sprintf ( path, "cart_header_right.car" );
    write_test_car ( path, 21, 0x2000, 0x40 );
    reset_header_test_machine ( MACHINE_TYPE_XL );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_XL,
            "CART Header Machine", "Type 21", "Expected current machine to remain selected" );
    remove ( path );

    sprintf ( path, "cart_header_generic_xl.car" );
    write_test_car ( path, 1, 0x2000, 0x50 );
    reset_header_test_machine ( MACHINE_TYPE_XL );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_XL,
            "CART Header Generic Machine", "XL", "Expected XL/XE to remain selected" );
    remove ( path );

    sprintf ( path, "cart_header_generic_800.car" );
    write_test_car ( path, 1, 0x2000, 0x60 );
    reset_header_test_machine ( MACHINE_TYPE_800 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_800,
            "CART Header Generic Machine", "800", "Expected Atari 800 to remain selected" );
    remove ( path );

    sprintf ( path, "cart_header_generic_5200.car" );
    write_test_car ( path, 1, 0x2000, 0x70 );
    reset_header_test_machine ( MACHINE_TYPE_5200 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_5200,
            "CART Header Generic Machine", "5200", "Expected current machine to remain selected" );
    remove ( path );

    /* A profile that forces a machine (machine = "xl") wins over the current
     * machine.  The profile mapping is also applied. */
    sprintf ( path, "cart_header_profile_override.car" );
    write_test_car ( path, 16, 0x4000, 0x80 );
    media_if_create_from_file ( path, &profile );
    profile_crc = profile.crc;
    profile.mapping = CART_PC_16K;
    profile.media_type = CART_CART;
    profile.machine_type = MACHINE_TYPE_XL;
    config_save_media_settings ( profile.crc, &profile, 0xfffff );
    reset_header_test_machine ( MACHINE_TYPE_800 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_PC_16K,
            "CART Header Override", "Profile mapping", "Expected profile mapper to win" );
    output_result ( console_get_machine_type() == MACHINE_TYPE_XL,
            "CART Header Override", "Profile machine",
            "Expected forcing profile machine (xl) to win" );
    media_if_delete_crc ( profile_crc );
    media_exit ( &profile );
    remove ( path );

    /* A profile with the legacy default machine = "800" is "soft": it must NOT
     * override the user's current 8-bit machine (XL stays XL). */
    memset ( &profile, 0, sizeof(profile) );
    sprintf ( path, "cart_header_profile_soft800.car" );
    write_test_car ( path, 16, 0x4000, 0x85 );
    media_if_create_from_file ( path, &profile );
    profile_crc = profile.crc;
    profile.mapping = CART_PC_16K;
    profile.media_type = CART_CART;
    profile.machine_type = MACHINE_TYPE_800;
    config_save_media_settings ( profile.crc, &profile, 0xfffff );
    reset_header_test_machine ( MACHINE_TYPE_XL );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_XL,
            "CART Header Override", "Soft 800 machine",
            "Expected soft machine=800 to keep current XL" );
    media_if_delete_crc ( profile_crc );
    media_exit ( &profile );
    remove ( path );

    sprintf ( path, "cart_header_type159.car" );
    write_test_car ( path, 159, 0xa000, 0x90 );
    {
        FILE *fp = fopen ( path, "r+b" );
        if ( fp ) {
            fseek ( fp, 16 + 0x1ffc, SEEK_SET );
            fputc ( 0x00, fp );
            fclose ( fp );
        }
    }
    reset_header_test_machine ( MACHINE_TYPE_800 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_PC_40K,
            "CART Header Mapping", "Type 159", "Expected current-machine Bounty Bob mapping" );
    output_result ( console_get_machine_type() == MACHINE_TYPE_800,
            "CART Header Machine", "Type 159",
            "Expected current machine to remain selected" );
    remove ( path );

    sprintf ( path, "profile_origin_5200.car" );
    write_test_car ( path, 16, 0x4000, 0xa0 );
    profile_crc = save_profile_without_machine ( path, MACHINE_TYPE_5200,
            CART_16K_ONE_CHIP );
    reset_header_test_machine ( MACHINE_TYPE_XL );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_5200,
            "Profile Origin Machine", "5200 directory",
            "Expected missing profile machine to infer 5200" );
    media_if_delete_crc ( profile_crc );
    remove ( path );

    sprintf ( path, "profile_origin_8bit.car" );
    write_test_car ( path, 1, 0x2000, 0xb0 );
    profile_crc = save_profile_without_machine ( path, MACHINE_TYPE_XL,
            CART_PC_8K );
    reset_header_test_machine ( MACHINE_TYPE_5200 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_XL,
            "Profile Origin Machine", "8-bit directory",
            "Expected missing profile machine to infer XL/XE" );
    media_if_delete_crc ( profile_crc );
    remove ( path );

    sprintf ( path, "profile_origin_none.car" );
    write_test_car ( path, 16, 0x4000, 0xc0 );
    memset ( &profile, 0, sizeof(profile) );
    media_if_create_from_file ( path, &profile );
    profile_crc = profile.crc;
    media_if_delete_crc ( profile_crc );
    media_exit ( &profile );
    reset_header_test_machine ( MACHINE_TYPE_800 );
    media_if_open_rom ( path, CART_MIN, 0, 1 );
    output_result ( console_get_machine_type() == MACHINE_TYPE_800,
            "Profile Origin Machine", "no profile",
            "Expected current machine to remain selected" );
    remove ( path );

    console_exit();

}

static void run_super_cart_bank_tests ( void ) {

    struct {
        int size;
        const char *name;
    } carts[] = {
        { 0x10000, "64K" },
        { 0x20000, "128K" },
        { 0x40000, "256K" },
        { 0x80000, "512K" }
    };
    unsigned char *buffer;
    int i, bank, bank_count;

    console_init();
    mem_init();
    console_set_machine_type ( MACHINE_TYPE_5200 );

    for ( i = 0; i < 4; ++i ) {
        buffer = malloc ( carts[i].size );
        bank_count = carts[i].size / 0x8000;
        for ( bank = 0; bank < bank_count; ++bank )
            memset ( buffer + bank * 0x8000, bank, 0x8000 );

        console_get_ptr()->cart->media_info.image_size = carts[i].size;
        cart_open ( "synthetic.car", buffer, carts[i].size, 0, CART_32K_BE );

        output_result ( memory[0x4000] == (KBYTE)(bank_count - 1),
                "Super Cart Boot Bank", carts[i].name, "Expected top bank at boot" );

        for ( bank = 0; bank < bank_count; ++bank ) {
            read_5200_rom_be32k ( 0xbfc0 | (bank & 0x0c) );
            read_5200_rom_be32k ( 0xbfd0 | ((bank & 0x03) << 2) );
            if ( memory[0x4000] != (KBYTE)bank )
                break;
        }
        output_result ( bank == bank_count,
                "Super Cart Bank Select", carts[i].name,
                "Expected BFCx/BFDx bank selection to reach every bank" );

        read_5200_rom_be32k ( 0xbfe0 );
        output_result ( memory[0x4000] == (KBYTE)(bank_count - 1),
                "Super Cart Top Bank", carts[i].name,
                "Expected BFEx to select top bank" );

        free ( buffer );
    }

    console_exit();

}

static void run_hybrid_cart_tests ( void ) {

    unsigned char *hyb;
    unsigned char *dat;
    KBYTE *bank;
    int i;
    KBYTE before;
    KBYTE bank0e_before;
    KBYTE bank0f_before;
    t_debug dbg;

    hyb = malloc ( 0x80000 );
    dat = malloc ( 0x6000 );
    if ( !hyb || !dat ) {
        output_result ( 0, "Hybrid Cart Setup", "malloc", "Unable to allocate test buffers" );
        free ( hyb );
        free ( dat );
        return;
    }

    console_init();
    mem_init();
    console_set_machine_type ( MACHINE_TYPE_XL );

    fill_test_hyb ( hyb, 0x34, 0x12 );
    for ( i = 0; i < 0x6000; ++i )
        dat[i] = (unsigned char)((i * 3 + 0x40) & 0xff);
    write_test_bytes ( "hybrid_pair.hyb", hyb, 0x80000 );
    write_test_bytes ( "hybrid_pair.dat", dat, 0x6000 );

    media_if_open_rom ( "hybrid_pair.hyb", CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_512K_HYBRID,
            "Hybrid Cart Mapping", ".hyb filename", "Expected filename-selected hybrid mapper" );
    output_result ( console_get_machine_type() == MACHINE_TYPE_5200,
            "Hybrid Cart Machine", ".hyb filename", "Expected 5200 machine type" );

    read_5200_rom_512k_hyb ( 0xbf86 );
    output_result ( read_5200_hyb_banked ( 0x4000 ) == 0x0c &&
                    read_5200_hyb_banked ( 0x5fff ) == 0x0c &&
                    read_5200_hyb_banked ( 0x6000 ) == 0x0d &&
                    read_5200_hyb_banked ( 0x7fff ) == 0x0d,
            "Hybrid 16K Bank Select", "$BF80-$BF87", "Expected 16K read bank window" );
    before = read_5200_hyb_banked ( 0x4000 );
    write_5200_sram_hyb ( 0x4000, before ^ 0xff );
    output_result ( read_5200_hyb_banked ( 0x4000 ) == before,
            "Hybrid 16K Bank Select", "read-only", "Expected 16K read window to ignore SRAM writes" );
    read_5200_rom_512k_hyb ( 0xbfc5 );
    output_result ( read_5200_hyb_banked ( 0x4000 ) == 5 &&
                    read_5200_hyb_banked ( 0x6000 ) == 5,
            "Hybrid 8K Bank Select", "$BFC0-$BFFD", "Expected 8K SRAM mode after BFCx switch" );

    debug_init();
    debug.breakpoint_flag = 1;
    debug.breakpoint[0] = BK_MEM;
    debug.breakpoint_place[0] = 0x4000;
    output_result ( debug_is_page_in_debug ( 0x40 ) && !debug_is_page_in_debug ( 0x41 ),
            "Debug Page Lookup", "BK_MEM page", "Expected memory breakpoint page lookup to use address high byte" );
    debug.breakpoint_flag = 0;
    debug.breakpoint[0] = BK_NONE;

    memset ( &dbg, 0, sizeof(dbg) );
    dbg.breakpoint[0] = BK_MEM;
    dbg.breakpoint_place[0] = 0xbe00;
    dbg.breakpoint[1] = BK_MEM;
    dbg.breakpoint_place[1] = 0xbf00;
    dbg.breakpoint[2] = BK_MEM;
    dbg.breakpoint_place[2] = 0x4000;
    dbg.breakpoint[3] = BK_MEM;
    dbg.breakpoint_place[3] = 0x6000;
    mem_assign_ops_debug ( console_get_ptr()->cpu, &dbg );
    output_result ( console_get_ptr()->cpu->read_mem[0xbe] == read_5200_rom_be_hyb_debug,
            "Hybrid Debug Ops", "$BE read", "Expected SD command reads to use debug handler" );
    output_result ( console_get_ptr()->cpu->read_mem[0xbf] == read_5200_rom_512k_hyb_debug &&
                    console_get_ptr()->cpu->write_mem[0xbf] == write_5200_rom_512k_hyb_debug,
            "Hybrid Debug Ops", "$BF read/write", "Expected status and bank switch handlers to use debug variants" );
    output_result ( console_get_ptr()->cpu->read_mem[0x40] == read_5200_hyb_banked_debug &&
                    console_get_ptr()->cpu->write_mem[0x40] == write_5200_sram_hyb_debug,
            "Hybrid Debug Ops", "$4000 window", "Expected SRAM window handlers to use debug variants" );
    output_result ( console_get_ptr()->cpu->read_mem[0x60] == read_5200_hyb_banked_debug,
            "Hybrid Debug Ops", "$6000 window", "Expected read-only bank window to use debug handler" );
    mem_assign_ops ( console_get_ptr()->cpu );
    memset ( &dbg, 0, sizeof(dbg) );
    dbg.breakpoint[0] = BK_WATCH;
    dbg.breakpoint_place[0] = 0x4000;
    mem_assign_ops_debug ( console_get_ptr()->cpu, &dbg );
    output_result ( console_get_ptr()->cpu->write_mem[0x40] == write_5200_sram_hyb_watch,
            "Hybrid Debug Ops", "$4000 watch", "Expected SRAM write window to support watchpoints" );
    mem_assign_ops ( console_get_ptr()->cpu );

    read_5200_rom_512k_hyb ( 0xbfce );
    bank0e_before = read_5200_hyb_banked ( 0x6000 );
    read_5200_rom_512k_hyb ( 0xbfcf );
    bank0f_before = read_5200_hyb_banked ( 0x6000 );
    read_5200_rom_512k_hyb ( 0xbfc5 );
    send_hybrid_sd_command ( 0x60000, 0x3000, 0x20, 0 );
    console_get_ptr()->cpu->pc = 0x0300;
    output_result ( read_5200_rom_512k_hyb ( 0xbffe ) == 0xff &&
                    read_5200_rom_512k_hyb ( 0xbfff ) == 0xff &&
                    read_5200_rom_512k_hyb ( 0xbfff ) == 0x00,
            "Hybrid SD Status", "$BFFE/$BFFF", "Expected FF, FF, 00 status sequence" );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == 5,
            "Hybrid SD Bank Select", "DMA", "Expected DMA not to change selected bank" );
    read_5200_rom_512k_hyb ( 0xbfce );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == bank0e_before,
            "Hybrid SD Header", "byte4", "Expected byte 4 not to select destination bank $0e" );
    read_5200_rom_512k_hyb ( 0xbfcf );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == bank0f_before,
            "Hybrid SD Header", "byte4", "Expected loader bank $0f to remain unchanged" );
    read_5200_rom_512k_hyb ( 0xbff0 );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == dat[0x20] &&
                    read_5200_hyb_banked ( 0x6001 ) == dat[0x21],
            "Hybrid SD DMA", ".dat to SRAM", "Expected data file bytes in destination bank" );
    read_5200_rom_512k_hyb ( 0xbff1 );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == dat[0x2020],
            "Hybrid SD DMA", "bank span", "Expected destination byte address to span SRAM banks" );
    console_get_ptr()->cpu->pc = 0xf000;
    output_result ( read_5200_rom_512k_hyb ( 0xbffe ) == 0x34 &&
                    read_5200_rom_512k_hyb ( 0xbfff ) == 0x12,
            "Hybrid BIOS Vector", "$BFFE/$BFFF", "Expected BIOS reads to return ROM vector bytes" );

    fill_test_hyb ( hyb, 0x78, 0x56 );
    write_test_bytes ( "hybrid_direct.hyb", hyb, 0x80000 );
    write_test_bytes ( "hybrid_direct.dat", dat, 0x6000 );
    media_if_open_rom ( "hybrid_direct.dat", CART_MIN, 0, 1 );
    output_result ( console_get_ptr()->cart->media_info.mapping == CART_512K_HYBRID &&
                    memory[0xbffe] == 0x78 && memory[0xbfff] == 0x56,
            "Hybrid Cart Mapping", ".dat filename", "Expected sibling .hyb to be opened" );

    read_5200_rom_512k_hyb ( 0xbfc3 );
    before = read_5200_hyb_banked ( 0x6000 );
    send_hybrid_sd_command ( 3 * 0x2000, 0x1000, 0x00, 1 );
    read_5200_rom_512k_hyb ( 0xbfc3 );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == before,
            "Hybrid SD Command", "bad trailer", "Expected malformed command to leave SRAM unchanged" );

    fill_test_hyb ( hyb, 0xab, 0xcd );
    write_test_bytes ( "hybrid_missing.hyb", hyb, 0x80000 );
    media_if_open_rom ( "hybrid_missing.hyb", CART_MIN, 0, 1 );
    read_5200_rom_512k_hyb ( 0xbfc1 );
    before = read_5200_hyb_banked ( 0x6000 );
    send_hybrid_sd_command ( 1 * 0x2000, 0x1000, 0x00, 0 );
    read_5200_rom_512k_hyb ( 0xbfc1 );
    output_result ( read_5200_hyb_banked ( 0x6000 ) == before,
            "Hybrid SD Missing Data", ".dat absent", "Expected stale SD data to be cleared" );

    bank = mem_get_bank_image ();
    output_result ( bank != NULL && mem_get_bank_image_size() == 0x80000,
            "Hybrid Cart SRAM", "bank image", "Expected 512K SRAM image" );

    console_exit();

    remove ( "hybrid_pair.hyb" );
    remove ( "hybrid_pair.dat" );
    remove ( "hybrid_direct.hyb" );
    remove ( "hybrid_direct.dat" );
    remove ( "hybrid_missing.hyb" );

    free ( hyb );
    free ( dat );
}

void output_result ( int result, const char *test, const char *value, 
        const char *error ) {

    if ( result ) {
        printf ( "\e[32m[PASS]\e[m %s Result=%s\n", test, value );
    }
    else {
        failure++;
        printf ( "\e[31m[FAIL]\e[m %s %s Result=%s\n", test, error, value );
    }

}

int check_file ( char *profile, char *set_file ) {

    char error[257];
    int line_no;
    t_toml_node *node;
    char *result_value = 0;
    int result, status;

    node = toml_parse_file ( profile );
    if ( toml_get_error(&line_no) ) {
        sprintf ( error, "TOML parse error at line %d", line_no );
        output_result ( 0, "Profile file update", profile, error );
        return -1;
    }

    result_value = toml_get_key_value ( node, "file" );
    if ( result_value ) {
        result = strcmp ( result_value, set_file );
        if ( result )
            sprintf ( error, "File name not \"%s\"", set_file );
        output_result ( result ? 0 : 1, "Profile file update", profile, error );
        status = result;
    }
    else {
        output_result ( 0, "File key not found in profile", profile, error );
        status = -1;
    }

    toml_free_node_tree ( node );

    return status;
}

/******************************************************************************
**  Function   :  main
**                                                
**  Objective  :  This is the main used to test the media module
**                                               
**  Parameters :  NONE                          
**                                             
**  return     :  0 if success                
**                -1 if failure               
******************************************************************************/ 
int main ( int argc, char *argv[]  ) {

    int i,j;
    FILE *fp;
    t_media tmp_media = {0};
    unsigned char *buffer;
    char result_string[256] = "";
    char result_file[70];
    char conf_file[70];
    int num_loops = 0;
    char *testv[1] = {"kat5200"};
    t_config *p_config = config_get_global_cfg();
    t_media_scan scan_params;
    int result;
    unsigned long crc;
    char sha1_result[40] = {'\0'};
    char md5_result[32] = {'\0'};
    unsigned char tmp_ary[21];

    /*
     * Assign input values
     */
    logger_init();
    config_init(1, testv);

    if ( argc > 1 && !strcmp(argv[1], "headers") ) {
        run_cart_header_tests();
        run_basic_media_state_tests();
        run_super_cart_bank_tests();
        config_exit();
        logger_exit();
        return failure;
    }

    if ( argc > 1 && !strcmp(argv[1], "hybrid") ) {
        run_hybrid_cart_tests();
        config_exit();
        logger_exit();
        return failure;
    }

    /* if we input machine type, set it */
    if ( argc > 1 ) {
        if ( strstr(argv[1], "5200") )
            p_config->general.machine = MACHINE_TYPE_5200;
        if ( strstr(argv[1], "800") )
            p_config->general.machine = MACHINE_TYPE_800;
        if ( strstr(argv[1], "xl") )
            p_config->general.machine = MACHINE_TYPE_XL;
    }

    /* Scan and update file tests */

    /* 
     * Atari 800 
     */
    scan_params.type = MACHINE_TYPE_800;
    scan_params.directory = malloc ( strlen("roms") + 1 );
    strcpy ( scan_params.directory, "roms" );
    scan_params.flags = MEDIA_SCAN_MEDIA | MEDIA_SCAN_BIOS | MEDIA_SCAN_SUBS;
    /* Run the scan synchronously (as the GUI does in menu_setup.c).  The old
     * async pc_start_thread + pc_get_thread_data wait spun forever because
     * media_if_scan_directory no longer clears the thread data flag. */
    media_if_scan_directory ( &scan_params );

    sprintf ( result_file, "roms%ctestrom7.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.8bit%c879b3fa4.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( result_file, "roms%croms2.zip/testrom8.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.8bit%ce4ebb71e.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( result_file, "roms%croms2.zip/troms/inrom/testrom9.zip/testrom9.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.8bit%c75ec52bb.conf", util_get_dir_seperator_character() );
    result = check_file ( conf_file, result_file );

    /* Will check this one for correct crc, sha1, md5, and rom buffer */
    if ( !result ) {
        config_load_media_settings ( 0x75ec52bb, &tmp_media );
        media_if_open_file ( tmp_media.file, &buffer);

        crc = crc32(0L,buffer,tmp_media.image_size);
        sprintf ( result_string, "%08lx : Expected 0x75ec52bb", crc );
        if ( crc == tmp_media.crc )
            output_result ( 1, "CRC - TestRom9", result_string, "" );
        else
            output_result ( 0, "CRC - TestRom9", result_string, "" );

        if ( buffer )
            sha1_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 20; ++i ) {
            sprintf ( sha1_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 40; ++i )
            tmp_media.sha1[i] = tolower(tmp_media.sha1[i]);
        sprintf ( result_string, "%.40s : Expected %s", sha1_result, tmp_media.sha1 );
        if ( strncmp(sha1_result, tmp_media.sha1, 40) ) 
            output_result ( 0, "SHA1 - TestRom9", result_string, "" );
        else
            output_result ( 1, "SHA1 - TestRom9", result_string, "" );

        if ( buffer )
            md5_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 16; ++i ) {
            sprintf ( md5_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 32; ++i )
            tmp_media.md5[i] = tolower(tmp_media.md5[i]);
        sprintf ( result_string, "%.32s : Expected %s", md5_result, tmp_media.md5 );
        if ( strncmp(md5_result, tmp_media.md5, 32) ) 
            output_result ( 0, "MD5 - TestRom9", result_string, "" );
        else
            output_result ( 1, "MD5 - TestRom9", result_string, "" );

        free(buffer);

        /* Let's force creation of Collection File */
        config_save_media_settings ( 0x75ec52bb, &tmp_media, KCFG_MEDIA_COLLECTS );
    }

    sprintf ( result_file, "roms%ctestrom5.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.8bit%c8fd1a1bd.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( result_file, "roms%ctestrom6.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.8bit%c055a2fea.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( conf_file, "profiles.8bit%cTOSEC-Games.csv", util_get_dir_seperator_character() );
    result = util_check_for_line_in_file ( conf_file, "\"testrom9\", 0x75ec52bb" );
    if ( result )
        output_result ( 1, "Collection - TOSEC-Games - TestRom9", "testrom9 in file", "" );
    else
        output_result ( 0, "Collection - TOSEC-Games - TestRom9", "testrom9 in file", conf_file );



    /* 
     * Atari 5200 
     */
    scan_params.type = MACHINE_TYPE_5200;
    media_if_scan_directory ( &scan_params );

    sprintf ( result_file, "roms%croms.zip/testrom3.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.5200%c361800ce.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( result_file, "roms%ctestrom1.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.5200%c44a7f9f1.conf", util_get_dir_seperator_character() );
    result = check_file ( conf_file, result_file );

    /* Will check this one for correct crc, sha1, md5, and rom buffer */
    if ( !result ) {
        config_load_media_settings ( 0x44a7f9f1, &tmp_media );
        media_if_open_file ( tmp_media.file, &buffer);

        crc = crc32(0L,buffer,tmp_media.image_size);
        sprintf ( result_string, "%08lx : Expected 0x44a7f9f1", crc );
        if ( crc == tmp_media.crc )
            output_result ( 1, "CRC - TestRom1", result_string, "" );
        else
            output_result ( 0, "CRC - TestRom1", result_string, "" );

        sha1_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 20; ++i ) {
            sprintf ( sha1_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 40; ++i )
            tmp_media.sha1[i] = tolower(tmp_media.sha1[i]);
        sprintf ( result_string, "%.40s : Expected %s", sha1_result, tmp_media.sha1 );
        if ( strncmp(sha1_result, tmp_media.sha1, 40) ) 
            output_result ( 0, "SHA1 - TestRom1", result_string, "" );
        else
            output_result ( 1, "SHA1 - TestRom1", result_string, "" );

        md5_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 16; ++i ) {
            sprintf ( md5_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 32; ++i )
            tmp_media.md5[i] = tolower(tmp_media.md5[i]);
        sprintf ( result_string, "%.32s : Expected %s", md5_result, tmp_media.md5 );
        if ( strncmp(md5_result, tmp_media.md5, 32) ) 
            output_result ( 0, "MD5 - TestRom1", result_string, "" );
        else
            output_result ( 1, "MD5 - TestRom1", result_string, "" );

        free(buffer);

        /* Let's force creation of Collection File */
        config_save_media_settings ( 0x44a7f9f1, &tmp_media, KCFG_MEDIA_COLLECTS );
    }

    sprintf ( result_file, "roms%ctestrom2.zip/testrom2.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.5200%c6eb8daaf.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    sprintf ( result_file, "roms%croms.zip/testrom4.bin", util_get_dir_seperator_character() );
    sprintf ( conf_file, "profiles.5200%c7b893f99.conf", util_get_dir_seperator_character() );
    check_file ( conf_file, result_file );

    /* 
     * Create new entry test from testrom10.bin 
     */
    sprintf ( result_file, "roms%ctestrom10.bin", util_get_dir_seperator_character() );
    media_if_create_cart_from_file ( result_file, &tmp_media );
    config_save_media_settings ( tmp_media.crc, &tmp_media, 0xfffff );

    /* Load another one to force a reload from file for this one */
    config_load_media_settings ( 0x6eb8daaf, &tmp_media );
    config_save_media_settings ( 0x6eb8daaf, &tmp_media, KCFG_MEDIA_COLLECTS );

    result = config_load_media_settings ( 0x11f03d1d, &tmp_media );
    if ( result )
        output_result ( 0, "Unable to load 11f03d1d.conf profile", result_string, "TestRom10" );
    else {
        media_if_open_file ( tmp_media.file, &buffer);

        crc = crc32(0L,buffer,tmp_media.image_size);
        sprintf ( result_string, "%08lx : Expected 0x11f03d1d", crc );
        if ( crc == tmp_media.crc )
            output_result ( 1, "CRC - TestRom10", result_string, "" );
        else
            output_result ( 0, "CRC - TestRom10", result_string, "" );

        sha1_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 20; ++i ) {
            sprintf ( sha1_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 40; ++i )
            tmp_media.sha1[i] = tolower(tmp_media.sha1[i]);
        sprintf ( result_string, "%.40s : Expected %s", sha1_result, tmp_media.sha1 );
        if ( strncmp(sha1_result, tmp_media.sha1, 40) ) 
            output_result ( 0, "SHA1 - TestRom10", result_string, "" );
        else
            output_result ( 1, "SHA1 - TestRom10", result_string, "" );

        md5_csum( buffer, tmp_media.image_size, tmp_ary );
        for ( i = 0; i < 16; ++i ) {
            sprintf ( md5_result+(i*2), "%02x", tmp_ary[i] );
        }
        for ( i = 0; i < 32; ++i )
            tmp_media.md5[i] = tolower(tmp_media.md5[i]);
        sprintf ( result_string, "%.32s : Expected %s", md5_result, tmp_media.md5 );
        if ( strncmp(md5_result, tmp_media.md5, 32) ) 
            output_result ( 0, "MD5 - TestRom10", result_string, "" );
        else
            output_result ( 1, "MD5 - TestRom10", result_string, "" );

        free(buffer);
    }

    sprintf ( conf_file, "profiles.5200%cConversions-5200.csv", util_get_dir_seperator_character() );
    result = util_check_for_line_in_file ( conf_file, "\"testrom1\", 0x44a7f9f1" );
    if ( result )
        output_result ( 1, "Collection - TOSEC-Games - TestRom1", "testrom1 in file", "" );
    else
        output_result ( 0, "Collection - TOSEC-Games - TestRom1", "testrom1 in file", conf_file );

    run_cart_header_tests();
    run_basic_media_state_tests();
    run_super_cart_bank_tests();

    if ( !failure )
        output_result ( 1, "File Scan", "All Tests Passed", "" );

    free ( scan_params.directory );

    config_exit();
    logger_exit();

    return failure;

} /* end main */
