/******************************************************************************
*
* Copyright (C) 2026  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_gtia.c
*
* DESCRIPTION:  This contains tests for the GTIA module.
*
* AUTHORS:  bberlin
*
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include "../src/core/console.h"
#include "../src/core/debug.h"
#include "../src/core/gtia.h"

extern t_gtia gtia;
t_antic antic;

static int failure = 0;
static unsigned int color_array[256];
static unsigned char gtia_screen[448];
static t_media test_media;
static t_cart test_cart;
static t_atari test_atari;

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 );
    }

}

static void check_int ( const char *test, unsigned int value, unsigned int answer ) {

    char value_string[80];
    char error[80];

    sprintf ( value_string, "0x%02x", value );
    sprintf ( error, "Expected 0x%02x", answer );
    output_result ( value == answer, test, value_string, error );

}

static void check_ptr ( const char *test, unsigned int *value, unsigned int *answer ) {

    char value_string[80];
    char error[80];

    sprintf ( value_string, "%p", (void *)value );
    sprintf ( error, "Expected %p", (void *)answer );
    output_result ( value == answer, test, value_string, error );

}

static void setup_gtia ( void ) {

    int i;

    memset ( &gtia, 0, sizeof(t_gtia) );
    memset ( &antic, 0, sizeof(t_antic) );

    for ( i = 0; i < 256; ++i )
        color_array[i] = i;

    gtia.color_array = color_array;

}

static void setup_transfer_nothing ( void ) {

    int i;

    for ( i = 0; i < 2; ++i ) {
        gtia.transfer_p0_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_p1_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_p2_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_p3_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_m0_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_m1_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_m2_ptr[i] = gtia_transfer_nothing;
        gtia.transfer_m3_ptr[i] = gtia_transfer_nothing;
    }

}

static void setup_mode10_scanline_prior_timing ( unsigned int fetch_start,
        unsigned int visible_start, int hscroll, int prior_before_init ) {

    setup_gtia ();
    memset ( gtia_screen, 0x55, sizeof(gtia_screen) );

    gtia_set_screen ( gtia_screen, color_array, sizeof(gtia_screen), 1, 1 );
    gtia_init_prior_array ();

    gtia_write_register ( COLPM0, 0x00 );
    gtia_write_register ( COLPM1, 0x3a );
    gtia_write_register ( COLPM2, 0x84 );
    gtia_write_register ( COLPM3, 0x34 );
    gtia_write_register ( COLPF0, 0xf8 );
    gtia_write_register ( COLPF1, 0xc6 );
    gtia_write_register ( COLPF2, 0xc2 );
    gtia_write_register ( COLPF3, 0x0e );
    gtia_write_register ( COLBK, 0x98 );
    if ( prior_before_init )
        gtia_write_register ( PRIOR, 0x94 );

    antic.dl_scanline = 8;
    antic.hscroll_pixel_start = fetch_start;
    antic.normal_pixel_start = visible_start;
    gtia.pf_x_start_ptr = hscroll ? &antic.hscroll_pixel_start :
                                    &antic.normal_pixel_start;
    setup_transfer_nothing ();

    gtia_init_scanline ();
    if ( !prior_before_init )
        gtia_write_register ( PRIOR, 0x94 );

}

static void setup_mode10_scanline ( unsigned int fetch_start,
        unsigned int visible_start, int hscroll ) {

    setup_mode10_scanline_prior_timing ( fetch_start, visible_start, hscroll, 1 );

}

static void draw_mode10_chunk ( unsigned int source ) {

    memset ( gtia.pf_byte, 0, 5 * sizeof(KBYTE) );
    gtia.pf_byte[PF2_BYTE] = source;
    gtia_set_byte_mode_2_g2 ();

}

static void setup_g1g3_scanline ( unsigned int prior, unsigned int colbk,
        unsigned int fetch_start, unsigned int visible_start, int hscroll ) {

    setup_gtia ();
    memset ( gtia_screen, 0x55, sizeof(gtia_screen) );

    gtia_set_screen ( gtia_screen, color_array, sizeof(gtia_screen), 1, 1 );
    gtia_init_prior_array ();

    gtia_write_register ( COLBK, colbk );
    gtia_write_register ( PRIOR, prior );

    antic.dl_scanline = 8;
    antic.hscroll_pixel_start = fetch_start;
    antic.normal_pixel_start = visible_start;
    gtia.pf_x_start_ptr = hscroll ? &antic.hscroll_pixel_start :
                                    &antic.normal_pixel_start;
    setup_transfer_nothing ();

    gtia_init_scanline ();

}

static void draw_g1g3_chunk ( unsigned int source ) {

    memset ( gtia.pf_byte, 0, 5 * sizeof(KBYTE) );
    gtia.pf_byte[PF2_BYTE] = source;
    gtia_set_byte_mode_2_g1g3 ();

}

static void setup_blank_byte ( unsigned int prior, unsigned int colbk ) {

    setup_gtia ();
    memset ( gtia_screen, 0x55, sizeof(gtia_screen) );

    gtia_set_screen ( gtia_screen, color_array, sizeof(gtia_screen), 1, 1 );
    gtia_init_prior_array ();

    gtia_write_register ( COLPM0, 0x00 );
    gtia_write_register ( COLPM1, 0x3a );
    gtia_write_register ( COLBK, colbk );
    gtia_write_register ( PRIOR, prior );

    gtia.d_surface.current = gtia.d_surface.start;
    gtia.pf_x_byte = 4;

}

static void run_reset_without_screen_tests ( void ) {

    memset ( &gtia, 0, sizeof(t_gtia) );
    memset ( &antic, 0, sizeof(t_antic) );

    gtia_reset ( NTSC );

    output_result ( gtia.color_array != NULL,
            "GTIA reset without screen", "color_array set",
            "color_array is NULL" );

    gtia_write_register ( COLPF1, 0x07 );
    gtia_write_register ( COLPM0, 0x13 );
    gtia_write_register ( COLPM1, 0x35 );
    gtia_write_register ( COLBK, 0x98 );
    gtia_write_register ( PRIOR, 0x20 );

    check_int ( "GTIA fallback PF1 color", gtia.col_pf1, 0x06 );
    check_int ( "GTIA fallback PM0 color", gtia.col_pm0, 0x12 );
    check_int ( "GTIA fallback mode2 PM0 set color",
            gtia.col_pm0_mode2_set, 0x16 );
    check_int ( "GTIA fallback background color", gtia.col_bk, 0x98 );
    check_int ( "GTIA fallback mode 9/11 color",
            gtia.gtia_mode_col[0][0x0f], 0x9f );
    check_int ( "GTIA fallback overlap color", gtia.col_ov01, 0x36 );

}

static void run_mode2_player_color_tests ( void ) {

    setup_gtia ();

    gtia_write_register ( COLPM0, 0x00 );
    gtia_write_register ( COLPF1, 0x06 );
    gtia_write_register ( COLPF2, 0x8e );

    check_int ( "GTIA Mode2 PM0 clear is player color",
            gtia.col_pm0_mode2_clear, 0x00 );
    check_int ( "GTIA Mode2 PM0 set uses PF1 luminance",
            gtia.col_pm0_mode2_set, 0x06 );

    gtia_write_register ( COLPM0, 0x12 );
    gtia_write_register ( COLPM1, 0x34 );
    gtia_write_register ( COLPM2, 0x56 );
    gtia_write_register ( COLPM3, 0x78 );

    check_int ( "GTIA Mode2 PM0 clear before PF2 change",
            gtia.col_pm0_mode2_clear, 0x12 );
    check_int ( "GTIA Mode2 PM1 clear before PF2 change",
            gtia.col_pm1_mode2_clear, 0x34 );
    check_int ( "GTIA Mode2 PM2 clear before PF2 change",
            gtia.col_pm2_mode2_clear, 0x56 );
    check_int ( "GTIA Mode2 PM3 clear before PF2 change",
            gtia.col_pm3_mode2_clear, 0x78 );

    gtia_write_register ( COLPF2, 0xa6 );

    check_int ( "GTIA Mode2 PM0 clear ignores PF2 change",
            gtia.col_pm0_mode2_clear, 0x12 );
    check_int ( "GTIA Mode2 PM1 clear ignores PF2 change",
            gtia.col_pm1_mode2_clear, 0x34 );
    check_int ( "GTIA Mode2 PM2 clear ignores PF2 change",
            gtia.col_pm2_mode2_clear, 0x56 );
    check_int ( "GTIA Mode2 PM3 clear ignores PF2 change",
            gtia.col_pm3_mode2_clear, 0x78 );

}

static void run_mode2_fifth_player_color_tests ( void ) {

    setup_gtia ();

    gtia_write_register ( COLPF1, 0x06 );
    gtia_write_register ( COLPF2, 0x8e );
    gtia_write_register ( COLPF3, 0x40 );

    check_int ( "GTIA Mode2 PF3 clear is fifth-player color",
            gtia.col_pf3_mode2_clear, 0x40 );
    check_int ( "GTIA Mode2 PF3 set uses PF1 luminance",
            gtia.col_pf3_mode2_set, 0x46 );

    gtia_write_register ( COLPF1, 0x0c );
    check_int ( "GTIA Mode2 PF3 set tracks PF1 change",
            gtia.col_pf3_mode2_set, 0x4c );

    gtia_write_register ( COLPF2, 0xa6 );
    check_int ( "GTIA Mode2 PF3 clear ignores PF2 change",
            gtia.col_pf3_mode2_clear, 0x40 );

}

static void run_mode2_priority_pointer_tests ( void ) {

    unsigned int prior1;

    setup_gtia ();

    gtia_init_prior_array ();
    prior1 = 1 << PRIOR_SHIFT;

    check_ptr ( "GTIA Mode2 PM0 PF2 lookup uses clear color",
            gtia.prior_ptr_array[MODE2_MASK | prior1 | PM0_MASK | PF2_MASK],
            &gtia.col_pm0_mode2_clear );
    check_ptr ( "GTIA Mode2 PM0 PF1 lookup uses set color",
            gtia.prior_ptr_array[MODE2_MASK | prior1 | PM0_MASK | PF1_MASK],
            &gtia.col_pm0_mode2_set );
    check_ptr ( "GTIA Mode2 PF3 PF2 lookup uses clear color",
            gtia.prior_ptr_array[MODE2_MASK | prior1 | PF3_MASK | PF2_MASK],
            &gtia.col_pf3_mode2_clear );
    check_ptr ( "GTIA Mode2 PF3 PF1 lookup uses set color",
            gtia.prior_ptr_array[MODE2_MASK | prior1 | PF3_MASK | PF1_MASK],
            &gtia.col_pf3_mode2_set );

}

static void run_mode10_hscroll_visible_seed_tests ( void ) {

    setup_mode10_scanline ( 88, 96, 1 );
    draw_mode10_chunk ( 0x44444444 );

    check_int ( "GTIA Mode10 HSCROL left border starts black",
            gtia_screen[24], 0x00 );
    check_int ( "GTIA Mode10 HSCROL left border reaches edge",
            gtia_screen[31], 0x00 );
    check_int ( "GTIA Mode10 HSCROL visible-edge seed first pixel",
            gtia_screen[32], 0x00 );
    check_int ( "GTIA Mode10 HSCROL visible-edge seed second pixel",
            gtia_screen[33], 0x00 );
    check_int ( "GTIA Mode10 HSCROL visible-edge clears PF mask",
            gtia_screen[34], 0xf8 );

    setup_mode10_scanline_prior_timing ( 88, 96, 1, 0 );
    draw_mode10_chunk ( 0x44444444 );

    check_int ( "GTIA Mode10 HSCROL left border survives late PRIOR",
            gtia_screen[31], 0x00 );
    check_int ( "GTIA Mode10 HSCROL visible seed survives late PRIOR",
            gtia_screen[32], 0x00 );
    check_int ( "GTIA Mode10 late PRIOR PF resumes",
            gtia_screen[34], 0xf8 );

    setup_mode10_scanline ( 88, 120, 1 );
    draw_mode10_chunk ( 0x44444444 );
    draw_mode10_chunk ( 0x44444444 );

    check_int ( "GTIA Mode10 HSCROL full block border carries",
            gtia_screen[55], 0x00 );
    check_int ( "GTIA Mode10 HSCROL visible seed carries to next block",
            gtia_screen[56], 0x00 );
    check_int ( "GTIA Mode10 HSCROL next-block seed second pixel",
            gtia_screen[57], 0x00 );
    check_int ( "GTIA Mode10 HSCROL next-block PF resumes",
            gtia_screen[58], 0xf8 );

    setup_mode10_scanline ( 96, 96, 0 );
    draw_mode10_chunk ( 0x44444444 );

    check_int ( "GTIA Mode10 unscrolled seed unchanged",
            gtia_screen[32], 0x00 );
    check_int ( "GTIA Mode10 unscrolled PF resumes",
            gtia_screen[34], 0xf8 );

}

static void run_g1g3_hscroll_left_border_tests ( void ) {

    setup_g1g3_scanline ( 0x44, 0x20, 88, 96, 1 );
    draw_g1g3_chunk ( 0xffffffff );

    check_int ( "GTIA Mode9 HSCROL left border uses code 0",
            gtia_screen[24], 0x20 );
    check_int ( "GTIA Mode9 HSCROL left border reaches edge",
            gtia_screen[31], 0x20 );
    check_int ( "GTIA Mode9 HSCROL active data resumes at edge",
            gtia_screen[32], 0x2f );

    setup_g1g3_scanline ( 0xc4, 0x26, 88, 96, 1 );
    draw_g1g3_chunk ( 0xffffffff );

    check_int ( "GTIA Mode11 HSCROL left border uses code 0",
            gtia_screen[24], 0x20 );
    check_int ( "GTIA Mode11 HSCROL left border reaches edge",
            gtia_screen[31], 0x20 );
    check_int ( "GTIA Mode11 HSCROL active data resumes at edge",
            gtia_screen[32], 0xf6 );

    setup_g1g3_scanline ( 0x44, 0x20, 88, 120, 1 );
    draw_g1g3_chunk ( 0xffffffff );
    draw_g1g3_chunk ( 0xffffffff );

    check_int ( "GTIA Mode9 HSCROL full block border carries",
            gtia_screen[55], 0x20 );
    check_int ( "GTIA Mode9 HSCROL resumes after full block",
            gtia_screen[56], 0x2f );

    setup_g1g3_scanline ( 0x44, 0x20, 96, 96, 0 );
    draw_g1g3_chunk ( 0xffffffff );

    check_int ( "GTIA Mode9 unscrolled data starts normally",
            gtia_screen[32], 0x2f );

}

static void run_mode10_blank_border_tests ( void ) {

    setup_blank_byte ( 0x04, 0x98 );
    gtia_set_byte_blank ();

    check_int ( "GTIA blank byte outside mode 10 uses COLBK",
            gtia_screen[0], 0x98 );
    check_int ( "GTIA blank byte outside mode 10 remains COLBK",
            gtia_screen[31], 0x98 );

    setup_blank_byte ( 0x94, 0x98 );
    gtia_set_byte_blank ();

    check_int ( "GTIA Mode10 blank byte uses COLPM0",
            gtia_screen[0], 0x00 );
    check_int ( "GTIA Mode10 blank byte remains COLPM0",
            gtia_screen[31], 0x00 );

    setup_blank_byte ( 0x94, 0x98 );
    gtia.graph_p1_line[4] = 0x80000000;
    gtia_set_byte_blank ();

    check_int ( "GTIA Mode10 blank PM priority starts at PM0",
            gtia_screen[0], 0x00 );
    check_int ( "GTIA Mode10 blank PM0 base continues",
            gtia_screen[1], 0x00 );
    check_int ( "GTIA Mode10 implicit PM0 does not collide",
            gtia.collision_p1pl[0], 0x00 );

    setup_mode10_scanline ( 88, 96, 1 );
    check_int ( "GTIA Mode10 scanline left border uses COLPM0",
            gtia_screen[0], 0x00 );

}

static void run_g1g3_blank_border_tests ( void ) {

    setup_blank_byte ( 0x44, 0x2a );
    gtia_set_byte_blank ();

    check_int ( "GTIA Mode9 blank byte uses code 0",
            gtia_screen[0], 0x2a );
    check_int ( "GTIA Mode9 blank byte remains code 0",
            gtia_screen[31], 0x2a );

    setup_blank_byte ( 0xc4, 0x26 );
    gtia_set_byte_blank ();

    check_int ( "GTIA Mode11 blank byte uses forced-luma code 0",
            gtia_screen[0], 0x20 );
    check_int ( "GTIA Mode11 blank byte remains code 0",
            gtia_screen[31], 0x20 );

    setup_blank_byte ( 0xc4, 0x26 );
    gtia.graph_p1_line[4] = 0x80000000;
    gtia_set_byte_blank ();

    check_int ( "GTIA Mode11 blank PM priority uses code 0 base",
            gtia_screen[1], 0x20 );
    check_int ( "GTIA Mode11 blank PM does not fake PF collision",
            gtia.collision_p1pf[0], 0x00 );

    setup_g1g3_scanline ( 0xc4, 0x26, 88, 96, 1 );
    check_int ( "GTIA Mode11 scanline left border uses code 0",
            gtia_screen[0], 0x20 );

}

int main ( int argc, char *argv[] ) {

    (void)argc;
    (void)argv;

    run_reset_without_screen_tests ();
    run_mode2_player_color_tests ();
    run_mode2_fifth_player_color_tests ();
    run_mode2_priority_pointer_tests ();
    run_mode10_hscroll_visible_seed_tests ();
    run_g1g3_hscroll_left_border_tests ();
    run_mode10_blank_border_tests ();
    run_g1g3_blank_border_tests ();

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

    return failure;

}

t_atari * console_get_ptr ( void ) {

    test_media.media_type = CART_32K;
    test_cart.loaded = 1;
    test_atari.main_media = &test_media;
    test_atari.cart = &test_cart;
    test_atari.machine_type = MACHINE_TYPE_5200;
    return &test_atari;

}

e_machine_type console_get_machine_type ( void ) {

    return MACHINE_TYPE_5200;

}

void debug_search_for_breakpoint ( e_debug_breakpoint break_type, int value ) {

    (void)break_type;
    (void)value;

}
