#include "check-structures-sanity.h"

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <windows.h>
#endif

bool Checker::is_in_global(const QueueItem & item)
{
    auto fields = df::global::_identity.getFields();
    for (auto field = fields; field->mode != struct_field_info::END; field++)
    {
        size_t size = CheckedStructure(field).full_size();
        auto start = *reinterpret_cast<const void * const*>(field->offset);
        auto offset = uintptr_t(item.ptr) - uintptr_t(start);
        if (offset < size)
        {
            return true;
        }
    }

    return false;
}
bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, size_t size, bool quiet)
{
    auto base = const_cast<void *>(item.ptr);
    if (!base)
    {
        // cannot dereference null pointer, but not an error
        return false;
    }

    // assumes MALLOC_PERTURB_=45
#ifdef DFHACK64
#define UNINIT_PTR 0xd2d2d2d2d2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#else
#define UNINIT_PTR 0xd2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#endif
    if (uintptr_t(base) == UNINIT_PTR)
    {
        if (!quiet)
        {
            FAIL_PTR("uninitialized pointer");
        }
        return false;
    }

    bool found = true;
    auto expected_start = base;
    size_t remaining_size = size;
    while (found)
    {
        found = false;

        for (auto & range : mapped)
        {
            if (!range.isInRange(expected_start))
            {
                continue;
            }

            found = true;

            if (!range.valid || !range.read)
            {
                if (!quiet)
                {
                    FAIL_PTR("pointer to invalid memory range");
                }
                return false;
            }

            auto expected_end = const_cast<void *>(PTR_ADD(expected_start, remaining_size - 1));
            if (size && !range.isInRange(expected_end))
            {
                auto next_start = PTR_ADD(range.end, 1);
                remaining_size -= ptrdiff_t(next_start) - ptrdiff_t(expected_start);
                expected_start = const_cast<void *>(next_start);
                break;
            }

            return true;
        }
    }

    if (quiet)
    {
        return false;
    }

    if (expected_start == base)
    {
        FAIL_PTR("pointer not in any mapped range");
    }
    else
    {
        FAIL_PTR("pointer exceeds mapped memory bounds (size " << size << ")");
    }

    return false;
#undef FAIL_PTR
}

int64_t Checker::get_int_value(const QueueItem & item, type_identity *type, bool quiet)
{
    if (type == df::identity_traits<int32_t>::get())
    {
        return validate_and_dereference<int32_t>(item, quiet);
    }
    else if (type == df::identity_traits<uint32_t>::get())
    {
        return validate_and_dereference<uint32_t>(item, quiet);
    }
    else if (type == df::identity_traits<int16_t>::get())
    {
        return validate_and_dereference<int16_t>(item, quiet);
    }
    else if (type == df::identity_traits<uint16_t>::get())
    {
        return validate_and_dereference<uint16_t>(item, quiet);
    }
    else if (type == df::identity_traits<int64_t>::get())
    {
        return validate_and_dereference<int64_t>(item, quiet);
    }
    else if (type == df::identity_traits<uint64_t>::get())
    {
        return int64_t(validate_and_dereference<uint64_t>(item, quiet));
    }
    else if (type == df::identity_traits<int8_t>::get())
    {
        return validate_and_dereference<int8_t>(item, quiet);
    }
    else if (type == df::identity_traits<uint8_t>::get())
    {
        return validate_and_dereference<uint8_t>(item, quiet);
    }
    else
    {
        UNEXPECTED;
        return 0;
    }
}

const char *Checker::get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
    auto vtable = validate_and_dereference<const void *const*>(QueueItem(item, "?vtable?", item.ptr), quiet);
    if (!vtable)
        return nullptr;

    auto info = validate_and_dereference<const char *const*>(QueueItem(item, "?vtable?.info", vtable - 1), quiet);
    if (!info)
        return nullptr;

#ifdef WIN32
#ifdef DFHACK64
    void *base;
    if (!RtlPcToFileHeader(const_cast<void *>(reinterpret_cast<const void *>(info)), &base))
        return nullptr;

    const char *typeinfo = reinterpret_cast<const char *>(base) + reinterpret_cast<const int32_t *>(info)[3];
    const char *name = typeinfo + 16;
#else
    const char *name = reinterpret_cast<const char *>(info) + 8;
#endif
#else
    auto name = validate_and_dereference<const char *>(QueueItem(item, "?vtable?.info.name", info + 1), quiet);
#endif

    for (auto & range : mapped)
    {
        if (!range.isInRange(const_cast<char *>(name)))
        {
            continue;
        }

        if (!range.valid || !range.read)
        {
            if (!quiet)
            {
                FAIL("pointer to invalid memory range");
            }
            return nullptr;
        }

        const char *first_letter = nullptr;
        bool letter = false;
        for (const char *p = name; ; p++)
        {
            if (!range.isInRange(const_cast<char *>(p)))
            {
                return nullptr;
            }

            if ((*p >= 'a' && *p <= 'z') || *p == '_')
            {
                if (!letter)
                {
                    first_letter = p;
                }
                letter = true;
            }
            else if (!*p)
            {
                return first_letter;
            }
        }
    }

    return nullptr;
}

std::pair<const void *, CheckedStructure> Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
    using ret_type = std::pair<const void *, CheckedStructure>;
    struct vector_data
    {
        uintptr_t start;
        uintptr_t finish;
        uintptr_t end_of_storage;
    };

    vector_data vector = *reinterpret_cast<const vector_data *>(item.ptr);

    ptrdiff_t length = vector.finish - vector.start;
    ptrdiff_t capacity = vector.end_of_storage - vector.start;

    bool local_ok = true;
    auto item_size = cs.identity ? cs.identity->byte_size() : 0;
    if (!item_size)
    {
        item_size = 1;
        local_ok = false;
    }

    if (vector.start > vector.finish)
    {
        local_ok = false;
        if (!quiet)
        {
            FAIL("vector length is negative (" << (length / ptrdiff_t(item_size)) << ")");
        }
    }
    if (vector.start > vector.end_of_storage)
    {
        local_ok = false;
        if (!quiet)
        {
            FAIL("vector capacity is negative (" << (capacity / ptrdiff_t(item_size)) << ")");
        }
    }
    else if (vector.finish > vector.end_of_storage)
    {
        local_ok = false;
        if (!quiet)
        {
            FAIL("vector capacity (" << (capacity / ptrdiff_t(item_size)) << ") is less than its length (" << (length / ptrdiff_t(item_size)) << ")");
        }
    }

    size_t ulength = size_t(length);
    size_t ucapacity = size_t(capacity);
    if (ulength % item_size != 0)
    {
        local_ok = false;
        if (!quiet)
        {
            FAIL("vector length is non-integer (" << (ulength / item_size) << " items plus " << (ulength % item_size) << " bytes)");
        }
    }
    if (ucapacity % item_size != 0)
    {
        local_ok = false;
        if (!quiet)
        {
            FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)");
        }
    }

    if (local_ok && capacity && !vector.start)
    {
        if (!quiet)
        {
            FAIL("vector has null pointer but capacity " << (capacity / item_size));
        }
        return ret_type();
    }

    auto start_ptr = reinterpret_cast<const void *>(vector.start);

    if (capacity && !is_valid_dereference(QueueItem(item, "?items?", start_ptr), CheckedStructure(cs.identity, capacity / item_size), quiet))
    {
        local_ok = false;
    }

    if (!local_ok)
    {
        return ret_type();
    }

    CheckedStructure ret_cs(cs.identity, ulength / item_size);
    ret_cs.allocated_count = ucapacity / item_size;
    return std::make_pair(start_ptr, ret_cs);
}

size_t Checker::get_allocated_size(const QueueItem & item)
{
    if (!sizes)
    {
        return 0;
    }

    if (uintptr_t(item.ptr) % 32 != 16)
    {
        return 0;
    }

    uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(item.ptr, -8));
    if (tag == 0xdfdf4ac8)
    {
        return *reinterpret_cast<const size_t *>(PTR_ADD(item.ptr, -16));
    }

    return 0;
}

#ifndef WIN32
const std::string *Checker::validate_stl_string_pointer(const void *const* base)
{
    std::string empty_string;
    if (*base == *reinterpret_cast<void **>(&empty_string))
    {
        return reinterpret_cast<const std::string *>(base);
    }

    const struct string_data_inner
    {
        size_t length;
        size_t capacity;
        int32_t refcount;
    } *str_data = static_cast<const string_data_inner *>(*base) - 1;

    if (!is_valid_dereference(QueueItem("str", PTR_ADD(str_data, -16)), 16, true))
    {
        return nullptr;
    }

    uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(str_data, -8));
    if (tag == 0xdfdf4ac8)
    {
        size_t allocated_size = *reinterpret_cast<const size_t *>(PTR_ADD(str_data, -16));
        size_t expected_size = sizeof(*str_data) + str_data->capacity + 1;

        if (allocated_size != expected_size)
        {
            return nullptr;
        }
    }
    else
    {
        return nullptr;
    }

    if (str_data->capacity < str_data->length)
    {
        return nullptr;
    }

    const char *ptr = reinterpret_cast<const char *>(*base);
    for (size_t i = 0; i < str_data->length; i++)
    {
        if (!*ptr++)
        {
            return nullptr;
        }
    }

    if (*ptr++)
    {
        return nullptr;
    }

    return reinterpret_cast<const std::string *>(base);
}
#endif

const char *const *Checker::get_enum_item_key(enum_identity *identity, int64_t value)
{
    return get_enum_item_attr_or_key(identity, value, nullptr);
}

const char *const *Checker::get_enum_item_attr_or_key(enum_identity *identity, int64_t value, const char *attr_name)
{
    size_t index;
    if (auto cplx = identity->getComplex())
    {
        auto it = cplx->value_index_map.find(value);
        if (it == cplx->value_index_map.cend())
        {
            return nullptr;
        }
        index = it->second;
    }
    else
    {
        if (value < identity->getFirstItem() || value > identity->getLastItem())
        {
            return nullptr;
        }
        index = value - identity->getFirstItem();
    }

    if (attr_name)
    {
        auto attrs = identity->getAttrs();
        auto attr_type = identity->getAttrType();
        if (!attrs || !attr_type)
        {
            return nullptr;
        }

        attrs = PTR_ADD(attrs, attr_type->byte_size() * index);
        for (auto field = attr_type->getFields(); field->mode != struct_field_info::END; field++)
        {
            if (!strcmp(field->name, attr_name))
            {
                return reinterpret_cast<const char *const *>(PTR_ADD(attrs, field->offset));
            }
        }

        return nullptr;
    }

    return &identity->getKeys()[index];
}
