/*
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/Format.h>
#include <AK/Platform.h>
#include <AK/UFixedBigInt.h>
#include <string.h>

namespace UserspaceEmulator {

template<typename T>
class ValueAndShadowReference;

template<typename T>
class ValueWithShadow {
public:
    using ValueType = T;
    using ShadowType = Array<u8, sizeof(T)>;

    ValueWithShadow() = default;

    ValueWithShadow(T value, T shadow)
        : m_value(value)
    {
        ReadonlyBytes { &shadow, sizeof(shadow) }.copy_to(m_shadow);
    }

    ValueWithShadow(T value, ShadowType shadow)
        : m_value(value)
        , m_shadow(shadow)
    {
    }

    static ValueWithShadow create_initialized(T value)
    {
        ShadowType shadow;
        shadow.fill(0x01);
        return {
            value,
            shadow,
        };
    }

    ValueWithShadow(ValueAndShadowReference<T> const&);

    T value() const { return m_value; }
    ShadowType const& shadow() const { return m_shadow; }

    T shadow_as_value() const
    requires(IsTriviallyConstructible<T>)
    {
        return *bit_cast<T const*>(m_shadow.data());
    }

    template<auto member>
    auto reference_to()
    requires(IsClass<T> || IsUnion<T>)
    {
        using ResultType = ValueAndShadowReference<RemoveReference<decltype(declval<T>().*member)>>;
        return ResultType {
            m_value.*member,
            *bit_cast<typename ResultType::ShadowType*>(m_shadow.span().offset_pointer(bit_cast<u8*>(member) - bit_cast<u8*>(nullptr))),
        };
    }

    template<auto member>
    auto slice() const
    requires(IsClass<T> || IsUnion<T>)
    {
        using ResultType = ValueWithShadow<RemoveReference<decltype(declval<T>().*member)>>;
        return ResultType {
            m_value.*member,
            *bit_cast<typename ResultType::ShadowType*>(m_shadow.span().offset_pointer(bit_cast<u8*>(member) - bit_cast<u8*>(nullptr))),
        };
    }

    bool is_uninitialized() const
    {
        for (size_t i = 0; i < sizeof(ShadowType); ++i) {
            if ((m_shadow[i] & 0x01) != 0x01)
                return true;
        }
        return false;
    }

    void set_initialized()
    {
        m_shadow.fill(0x01);
    }

private:
    T m_value {};
    ShadowType m_shadow {};
};

template<typename T>
class ValueAndShadowReference {
public:
    using ValueType = T;
    using ShadowType = Array<u8, sizeof(T)>;

    ValueAndShadowReference(T& value, ShadowType& shadow)
        : m_value(value)
        , m_shadow(shadow)
    {
    }

    bool is_uninitialized() const
    {
        for (size_t i = 0; i < sizeof(ShadowType); ++i) {
            if ((m_shadow[i] & 0x01) != 0x01)
                return true;
        }
        return false;
    }

    ValueAndShadowReference<T>& operator=(ValueWithShadow<T> const&);

    T shadow_as_value() const
    requires(IsTriviallyConstructible<T>)
    {
        return *bit_cast<T const*>(m_shadow.data());
    }

    T& value() { return m_value; }
    ShadowType& shadow() { return m_shadow; }

    T const& value() const { return m_value; }
    ShadowType const& shadow() const { return m_shadow; }

private:
    T& m_value;
    ShadowType& m_shadow;
};

template<typename T>
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_as_initialized(T value)
{
    return ValueWithShadow<T>::create_initialized(value);
}

template<typename T, typename U>
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a)
{
    if (taint_a.is_uninitialized())
        return { value, 0 };
    return shadow_wrap_as_initialized(value);
}

template<typename T, typename U, typename V>
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a, V const& taint_b)
{
    if (taint_a.is_uninitialized() || taint_b.is_uninitialized())
        return { value, 0 };
    return shadow_wrap_as_initialized(value);
}

template<typename T, typename U, typename V, typename X>
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a, V const& taint_b, X const& taint_c)
{
    if (taint_a.is_uninitialized() || taint_b.is_uninitialized() || taint_c.is_uninitialized())
        return { value, 0 };
    return shadow_wrap_as_initialized(value);
}

template<typename T>
inline ValueWithShadow<T>::ValueWithShadow(ValueAndShadowReference<T> const& other)
    : m_value(other.value())
    , m_shadow(other.shadow())
{
}

template<typename T>
inline ValueAndShadowReference<T>& ValueAndShadowReference<T>::operator=(ValueWithShadow<T> const& other)
{
    m_value = other.value();
    m_shadow = other.shadow();
    return *this;
}

}

template<typename T>
struct AK::Formatter<UserspaceEmulator::ValueWithShadow<T>> : AK::Formatter<T> {
    ErrorOr<void> format(FormatBuilder& builder, UserspaceEmulator::ValueWithShadow<T> value)
    {
        return Formatter<T>::format(builder, value.value());
    }
};