#include "Library/Yaml/ByamlData.h"

#include <byteswap.h>

namespace al {

ByamlData::ByamlData() = default;

void ByamlData::set(const ByamlHashPair* hash_pair, bool isRev) {
    mType = hash_pair->getType();
    mValue = hash_pair->getValue(isRev);
}

void ByamlData::set(u8 type, u32 value, bool isRev) {
    mType = (ByamlDataType)type;
    mValue = isRev ? bswap_32(value) : value;
}

ByamlDataType ByamlData::getType() const {
    return mType;
}

u32 ByamlData::getValue() const {
    return mValue;
}

s32 ByamlHashPair::getKey(bool isRev) const {
    return isRev ? bswap_24(mData) : mData & 0xFFFFFF;
}

ByamlDataType ByamlHashPair::getType() const {
    return (ByamlDataType)(mData >> 24);
}

s32 ByamlHashPair::getValue(bool isRev) const {
    return isRev ? bswap_32(mValue) : mValue;
}

ByamlHashIter::ByamlHashIter(const u8* data, bool isRev) : mData(data), mIsRev(isRev) {}

ByamlHashIter::ByamlHashIter() : mData(nullptr), mIsRev(false) {}

const ByamlHashPair* ByamlHashIter::findPair(s32 key) const {
    const ByamlHashPair* pairTable = getPairTable();
    if (!mData)
        return nullptr;

    s32 lowerBound = 0;
    s32 upperBound = getSize();
    asm("");  // solves isRev leaking over between getSize() and getKey()
    while (lowerBound < upperBound) {
        s32 avg = (lowerBound + upperBound) / 2;
        const ByamlHashPair* pair = &pairTable[avg];
        s32 result = key - pair->getKey(mIsRev);
        if (result == 0)
            return pair;

        if (result > 0)
            lowerBound = avg + 1;
        else
            upperBound = avg;
    }
    return nullptr;
}

bool ByamlHashIter::getDataByIndex(ByamlData* data, s32 index) const {
    if (!mData)
        return false;
    if (((s32)getSize()) < 1)
        return false;

    data->set(&getPairTable()[index], mIsRev);
    return true;
}

// NON_MATCHING: minor additional instructions, probably not inlined
// (https://decomp.me/scratch/5VLcG)
bool ByamlHashIter::getDataByKey(ByamlData* data, s32 key) const {
    if (!mData)
        return false;
    if (((s32)getSize()) < 1)
        return false;

    // probably inlined from findPair()
    const ByamlHashPair* pairTable = getPairTable();
    if (!mData)
        return false;
    s32 lowerBound = 0;
    s32 upperBound = getSize();
    const ByamlHashPair* pair;

    if (lowerBound >= upperBound)
        return false;
    while (true) {
        s32 avg = (lowerBound + upperBound) / 2;
        pair = &pairTable[avg];
        s32 result = key - pair->getKey(mIsRev);
        if (result == 0)
            break;

        if (result > 0)
            lowerBound = avg + 1;
        else
            upperBound = avg;
        if (lowerBound >= upperBound)
            return false;
    }
    if (!pair)
        return false;
    data->set(pair, mIsRev);
    return true;
}

const u8* ByamlHashIter::getOffsetData(u32 off) const {
    return &mData[off];
}

const ByamlHashPair* ByamlHashIter::getPairByIndex(s32 index) const {
    if (index < 0)
        return nullptr;
    if (((s32)getSize()) <= index)
        return nullptr;

    return &getPairTable()[index];
}

const ByamlHashPair* ByamlHashIter::getPairTable() const {
    if (!mData)
        return nullptr;
    return reinterpret_cast<const ByamlHashPair*>(mData + 4);
}

u32 ByamlHashIter::getSize() const {
    if (!mData)
        return 0;
    u32 val = *reinterpret_cast<const u32*>(mData);
    return mIsRev ? bswap_24(val >> 8) : val >> 8;
}

ByamlArrayIter::ByamlArrayIter(const u8* data, bool isRev) : mData(data), mIsRev(isRev) {}

ByamlArrayIter::ByamlArrayIter() : mData(nullptr), mIsRev(false) {}

bool ByamlArrayIter::getDataByIndex(ByamlData* data, s32 index) const {
    if (index < 0)
        return false;
    if (((s32)getSize()) <= index)
        return false;

    data->set(getTypeTable()[index], getDataTable()[index], mIsRev);
    return true;
}

// NON_MATCHING: regalloc (https://decomp.me/scratch/dGFdU)
const u32* ByamlArrayIter::getDataTable() const {
    return reinterpret_cast<const u32*>(getOffsetData((getSize() + 7) & 0xFFFFFFFC));
}

const u8* ByamlArrayIter::getOffsetData(u32 off) const {
    return &mData[off];
}

u32 ByamlArrayIter::getSize() const {
    u32 val = *reinterpret_cast<const u32*>(mData);
    return mIsRev ? bswap_24(val >> 8) : val >> 8;
}

const u8* ByamlArrayIter::getTypeTable() const {
    return mData + 4;
}
}  // namespace al
