#include "core/gba/gbaCheatSearch.h"

#include <cstdlib>
#include <cstring>

CheatSearchBlock cheatSearchBlocks[4];

CheatSearchData cheatSearchData = {
    0,
    cheatSearchBlocks
};

static bool cheatSearchEQ(uint32_t a, uint32_t b)
{
    return a == b;
}

static bool cheatSearchNE(uint32_t a, uint32_t b)
{
    return a != b;
}

static bool cheatSearchLT(uint32_t a, uint32_t b)
{
    return a < b;
}

static bool cheatSearchLE(uint32_t a, uint32_t b)
{
    return a <= b;
}

static bool cheatSearchGT(uint32_t a, uint32_t b)
{
    return a > b;
}

static bool cheatSearchGE(uint32_t a, uint32_t b)
{
    return a >= b;
}

static bool cheatSearchSignedEQ(int32_t a, int32_t b)
{
    return a == b;
}

static bool cheatSearchSignedNE(int32_t a, int32_t b)
{
    return a != b;
}

static bool cheatSearchSignedLT(int32_t a, int32_t b)
{
    return a < b;
}

static bool cheatSearchSignedLE(int32_t a, int32_t b)
{
    return a <= b;
}

static bool cheatSearchSignedGT(int32_t a, int32_t b)
{
    return a > b;
}

static bool cheatSearchSignedGE(int32_t a, int32_t b)
{
    return a >= b;
}

static bool (*cheatSearchFunc[])(uint32_t, uint32_t) = {
    cheatSearchEQ,
    cheatSearchNE,
    cheatSearchLT,
    cheatSearchLE,
    cheatSearchGT,
    cheatSearchGE
};

static bool (*cheatSearchSignedFunc[])(int32_t, int32_t) = {
    cheatSearchSignedEQ,
    cheatSearchSignedNE,
    cheatSearchSignedLT,
    cheatSearchSignedLE,
    cheatSearchSignedGT,
    cheatSearchSignedGE
};

void cheatSearchCleanup(CheatSearchData* cs)
{
    int count = cs->count;

    for (int i = 0; i < count; i++) {
        free(cs->blocks[i].saved);
        free(cs->blocks[i].bits);
    }
    cs->count = 0;
}

void cheatSearchStart(const CheatSearchData* cs)
{
    int count = cs->count;

    for (int i = 0; i < count; i++) {
        CheatSearchBlock* block = &cs->blocks[i];

        memset(block->bits, 0xff, block->size >> 3);
        memcpy(block->saved, block->data, block->size);
    }
}

int32_t cheatSearchSignedRead(uint8_t* data, int off, int size)
{
    uint32_t res = data[off++];

    switch (size) {
    case BITS_8:
        res <<= 24;
        return ((int32_t)res) >> 24;
    case BITS_16:
        res |= ((uint32_t)data[off++]) << 8;
        res <<= 16;
        return ((int32_t)res) >> 16;
    case BITS_32:
        res |= ((uint32_t)data[off++]) << 8;
        res |= ((uint32_t)data[off++]) << 16;
        res |= ((uint32_t)data[off++]) << 24;
        return (int32_t)res;
    }
    return (int32_t)res;
}

uint32_t cheatSearchRead(uint8_t* data, int off, int size)
{
    uint32_t res = data[off++];
    if (size == BITS_16)
        res |= ((uint32_t)data[off++]) << 8;
    else if (size == BITS_32) {
        res |= ((uint32_t)data[off++]) << 8;
        res |= ((uint32_t)data[off++]) << 16;
        res |= ((uint32_t)data[off++]) << 24;
    }
    return res;
}

void cheatSearch(const CheatSearchData* cs, int compare, int size,
    bool isSigned)
{
    if (compare < 0 || compare > SEARCH_GE)
        return;
    int inc = 1;
    if (size == BITS_16)
        inc = 2;
    else if (size == BITS_32)
        inc = 4;

    if (isSigned) {
        bool (*func)(int32_t, int32_t) = cheatSearchSignedFunc[compare];

        for (int i = 0; i < cs->count; i++) {
            CheatSearchBlock* block = &cs->blocks[i];
            int size2 = block->size;
            uint8_t* bits = block->bits;
            uint8_t* data = block->data;
            uint8_t* saved = block->saved;

            for (int j = 0; j < size2; j += inc) {
                if (IS_BIT_SET(bits, j)) {
                    int32_t a = cheatSearchSignedRead(data, j, size);
                    int32_t b = cheatSearchSignedRead(saved, j, size);

                    if (!func(a, b)) {
                        CLEAR_BIT(bits, j);
                        if (size == BITS_16)
                            CLEAR_BIT(bits, j + 1);
                        if (size == BITS_32) {
                            CLEAR_BIT(bits, j + 2);
                            CLEAR_BIT(bits, j + 3);
                        }
                    }
                }
            }
        }
    } else {
        bool (*func)(uint32_t, uint32_t) = cheatSearchFunc[compare];

        for (int i = 0; i < cs->count; i++) {
            CheatSearchBlock* block = &cs->blocks[i];
            int size2 = block->size;
            uint8_t* bits = block->bits;
            uint8_t* data = block->data;
            uint8_t* saved = block->saved;

            for (int j = 0; j < size2; j += inc) {
                if (IS_BIT_SET(bits, j)) {
                    uint32_t a = cheatSearchRead(data, j, size);
                    uint32_t b = cheatSearchRead(saved, j, size);

                    if (!func(a, b)) {
                        CLEAR_BIT(bits, j);
                        if (size == BITS_16)
                            CLEAR_BIT(bits, j + 1);
                        if (size == BITS_32) {
                            CLEAR_BIT(bits, j + 2);
                            CLEAR_BIT(bits, j + 3);
                        }
                    }
                }
            }
        }
    }
}

void cheatSearchValue(const CheatSearchData* cs, int compare, int size,
    bool isSigned, uint32_t value)
{
    if (compare < 0 || compare > SEARCH_GE)
        return;
    int inc = 1;
    if (size == BITS_16)
        inc = 2;
    else if (size == BITS_32)
        inc = 4;

    if (isSigned) {
        bool (*func)(int32_t, int32_t) = cheatSearchSignedFunc[compare];

        for (int i = 0; i < cs->count; i++) {
            CheatSearchBlock* block = &cs->blocks[i];
            int size2 = block->size;
            uint8_t* bits = block->bits;
            uint8_t* data = block->data;

            for (int j = 0; j < size2; j += inc) {
                if (IS_BIT_SET(bits, j)) {
                    int32_t a = cheatSearchSignedRead(data, j, size);
                    int32_t b = (int32_t)value;

                    if (!func(a, b)) {
                        CLEAR_BIT(bits, j);
                        if (size == BITS_16)
                            CLEAR_BIT(bits, j + 1);
                        if (size == BITS_32) {
                            CLEAR_BIT(bits, j + 2);
                            CLEAR_BIT(bits, j + 3);
                        }
                    }
                }
            }
        }
    } else {
        bool (*func)(uint32_t, uint32_t) = cheatSearchFunc[compare];

        for (int i = 0; i < cs->count; i++) {
            CheatSearchBlock* block = &cs->blocks[i];
            int size2 = block->size;
            uint8_t* bits = block->bits;
            uint8_t* data = block->data;

            for (int j = 0; j < size2; j += inc) {
                if (IS_BIT_SET(bits, j)) {
                    uint32_t a = cheatSearchRead(data, j, size);

                    if (!func(a, value)) {
                        CLEAR_BIT(bits, j);
                        if (size == BITS_16)
                            CLEAR_BIT(bits, j + 1);
                        if (size == BITS_32) {
                            CLEAR_BIT(bits, j + 2);
                            CLEAR_BIT(bits, j + 3);
                        }
                    }
                }
            }
        }
    }
}

int cheatSearchGetCount(const CheatSearchData* cs, int size)
{
    int res = 0;
    int inc = 1;
    if (size == BITS_16)
        inc = 2;
    else if (size == BITS_32)
        inc = 4;

    for (int i = 0; i < cs->count; i++) {
        CheatSearchBlock* block = &cs->blocks[i];

        int size2 = block->size;
        uint8_t* bits = block->bits;
        for (int j = 0; j < size2; j += inc) {
            if (IS_BIT_SET(bits, j))
                res++;
        }
    }
    return res;
}

void cheatSearchUpdateValues(const CheatSearchData* cs)
{
    for (int i = 0; i < cs->count; i++) {
        CheatSearchBlock* block = &cs->blocks[i];

        memcpy(block->saved, block->data, block->size);
    }
}
