#include "gb.h"

void GB_cheat_search_reset(GB_gameboy_t *gb)
{
    if (gb->cheat_search_data) {
        free(gb->cheat_search_data);
        gb->cheat_search_data = NULL;
    }
    if (gb->cheat_search_bitmap) {
        free(gb->cheat_search_bitmap);
        gb->cheat_search_bitmap = NULL;
    }
    gb->cheat_search_count = 0;
}

bool GB_cheat_search_filter(GB_gameboy_t *gb, const char *expression, GB_cheat_search_data_type_t data_type)
{
    GB_ASSERT_NOT_RUNNING(gb)
    
    // Make sure the expression is valid first
    if (GB_debugger_evaluate_cheat_filter(gb, expression, NULL, 0, 0)) {
        return false;
    }
    gb->cheat_search_data_type = data_type;

    if (gb->cheat_search_count == 0) {
        GB_cheat_search_reset(gb);
        gb->cheat_search_count = gb->ram_size + gb->mbc_ram_size + sizeof(gb->hram);
        gb->cheat_search_data = malloc(gb->cheat_search_count);
        gb->cheat_search_bitmap = malloc((gb->cheat_search_count + 7) / 8);
        memset(gb->cheat_search_data, 0, gb->cheat_search_count);
        memset(gb->cheat_search_bitmap, 0, (gb->cheat_search_count + 7) / 8);
    }
    
    uint8_t mask = 1;
    uint8_t *old_data = gb->cheat_search_data;
    uint8_t *bitmap = gb->cheat_search_bitmap;
    uint8_t *new_data = gb->ram;
    
    for (unsigned i = gb->ram_size + gb->mbc_ram_size + sizeof(gb->hram); i--;) {
        if (*bitmap & mask) {
            goto skip;
        }
        bool result = false;
        if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
            // The last byte of each section always fails on 16-bit searches
            if ((new_data != gb->ram + gb->ram_size - 1 &&
                 new_data != gb->mbc_ram + gb->mbc_ram_size - 1 &&
                 new_data != gb->hram + sizeof(gb->hram) - 1)) {
                uint16_t old = old_data[0] | (old_data[1] << 8);
                uint16_t new = new_data[0] | (new_data[1] << 8);
                if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) {
                    old = __builtin_bswap16(old);
                    new = __builtin_bswap16(new);
                }
                GB_debugger_evaluate_cheat_filter(gb, expression, &result, old, new);
            }
        }
        else {
            GB_debugger_evaluate_cheat_filter(gb, expression, &result, *old_data, *new_data);
        }
        if (result) {
            // Filter passed, update old value
            *old_data = *new_data;
            if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
                old_data[1] = new_data[1];
            }
        }
        else {
            // Did not pass filter, remove address
            *bitmap |= mask;
            gb->cheat_search_count--;
        }
    skip:;
        old_data++;
        if (new_data == gb->ram + gb->ram_size - 1) {
            new_data = gb->mbc_ram;
        }
        else if (new_data == gb->mbc_ram + gb->mbc_ram_size - 1) {
            new_data = gb->hram;
        }
        else {
            new_data++;
        }
        mask <<= 1;
        if (mask == 0) {
            mask = 1;
            bitmap++;
        }
    }
    
    return true;
}

size_t GB_cheat_search_result_count(GB_gameboy_t *gb)
{
    return gb->cheat_search_count;
}

void GB_cheat_search_get_results(GB_gameboy_t *gb, GB_cheat_search_result_t *results)
{
    uint8_t mask = 1;
    uint8_t *old_data = gb->cheat_search_data;
    uint8_t *bitmap = gb->cheat_search_bitmap;
    size_t count = gb->cheat_search_count;
    while (count) {
        if (!(*bitmap & mask)) {
            count--;
            if (gb->cheat_search_data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
                // Do not check for end of section, data_type is required to be the same as the last filter call
                uint16_t old = old_data[0] | (old_data[1] << 8);
                if (gb->cheat_search_data_type & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) {
                    old = __builtin_bswap16(old);
                }
                results->value = old;
            }
            else {
                results->value = *old_data;
            }
            size_t offset = old_data - gb->cheat_search_data;
            if (offset < gb->ram_size) {
                results->bank = offset / 0x1000;
                results->addr = (offset & 0xfff) + (results->bank? 0xd000 : 0xc000);
            }
            else if (offset < gb->ram_size + gb->mbc_ram_size) {
                results->addr = (offset & 0x1fff) + 0xa000;
                results->bank = (offset - gb->ram_size) / 0x2000;
            }
            else {
                results->addr = (offset & 0x7f) + 0xff80;
                results->bank = 0;
            }
            results++;
        }
        old_data++;
        mask <<= 1;
        if (mask == 0) {
            mask = 1;
            bitmap++;
        }
    }
}
