#pragma once

#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
#include <variant>

namespace sliger {

inline auto escape_string(std::string str) -> std::string
{
    auto result = std::string();
    for (auto ch : str) {
        switch (ch) {
            case '\n':
                result += "\\n";
                break;
            case '\t':
                result += "\\t";
                break;
            case '\0':
                result += "\\0";
                break;
            case '\\':
                result += "\\\\";
                break;
            default:
                result += ch;
        }
    }
    return result;
}

enum class ValueType {
    Null,
    Int,
    Bool,
    String,
    Ptr,
};

inline auto value_type_to_string(ValueType type) -> std::string
{
    switch (type) {
        case ValueType::Null:
            return "Null";
        case ValueType::Int:
            return "Int";
        case ValueType::Bool:
            return "Bool";
        case ValueType::String:
            return "String";
        case ValueType::Ptr:
            return "Ptr";
    }
    std::unreachable();
}

class Values;

struct Null { };
struct Int {
    int32_t value;
};
struct Bool {
    bool value;
};
struct String {
    std::string value;

    auto at(int32_t index) -> int32_t;
};
struct Ptr {
    uint32_t value;
};

// clang-format off
template <ValueType op> struct ValueTypeToType {  };
template <> struct ValueTypeToType<ValueType::Null> { using Type = Null; };
template <> struct ValueTypeToType<ValueType::Int> { using Type = Int; };
template <> struct ValueTypeToType<ValueType::Bool> { using Type = Bool; };
template <> struct ValueTypeToType<ValueType::String> { using Type = String; };
template <> struct ValueTypeToType<ValueType::Ptr> { using Type = Ptr; };
// clang-format on

class Value {
public:
    Value(Null&& value)
        : m_type(ValueType::Null)
        , value(value)
    {
    }
    Value(Int&& value)
        : m_type(ValueType::Int)
        , value(value)
    {
    }
    Value(Bool&& value)
        : m_type(ValueType::Bool)
        , value(value)
    {
    }
    Value(String&& value)
        : m_type(ValueType::String)
        , value(value)
    {
    }
    Value(Ptr&& value)
        : m_type(ValueType::Ptr)
        , value(value)
    {
    }

    inline auto type() const -> ValueType { return m_type; };

    template <ValueType VT> inline auto as() -> ValueTypeToType<VT>::Type&
    {
        try {
            return std::get<typename ValueTypeToType<VT>::Type>(value);
        } catch (const std::bad_variant_access& ex) {
            print_tried_to_unwrap_error_message(VT);
            std::exit(1);
        }
    }

    template <ValueType VT>
    inline auto as() const -> const ValueTypeToType<VT>::Type&
    {
        try {
            return std::get<typename ValueTypeToType<VT>::Type>(value);
        } catch (const std::bad_variant_access& ex) {
            print_tried_to_unwrap_error_message(VT);
            std::exit(1);
        }
    }

    // clang-format off
    inline auto as_null() -> Null& { return as<ValueType::Null>(); }
    inline auto as_int() -> Int& { return as<ValueType::Int>(); }
    inline auto as_bool() -> Bool& { return as<ValueType::Bool>(); }
    inline auto as_string() -> String& { return as<ValueType::String>(); }
    inline auto as_ptr() -> Ptr& { return as<ValueType::Ptr>(); }

    inline auto as_null() const -> const Null& { return as<ValueType::Null>(); }
    inline auto as_int() const -> const Int& { return as<ValueType::Int>(); }
    inline auto as_bool() const -> const Bool& { return as<ValueType::Bool>(); }
    inline auto as_string() const -> const String& { return as<ValueType::String>(); }
    inline auto as_ptr() const -> const Ptr& { return as<ValueType::Ptr>(); }
    // clang-format on

    auto to_string() const -> std::string;
    auto to_repr_string() const -> std::string;

private:
    void print_tried_to_unwrap_error_message(ValueType vt) const;

    ValueType m_type;
    std::variant<Null, Int, Bool, String, Ptr> value;
};

}
