#pragma once

#include <cstdint>
#include <format>
#include <iostream>
#include <string>
#include <utility>
#include <variant>

namespace sliger {

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;
};
struct Ptr {
    uint32_t value;
};

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; };

    inline auto as_null() -> Null&
    {
        //
        try {
            return std::get<Null>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Null\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }
    inline auto as_null() const -> const Null&
    {
        //
        try {
            return std::get<Null>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Null\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }

    inline auto as_int() -> Int&
    {
        //
        try {
            return std::get<Int>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Int\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }
    inline auto as_int() const -> const Int&
    {
        //
        try {
            return std::get<Int>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Int\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }

    inline auto as_bool() -> Bool&
    {
        //
        try {
            return std::get<Bool>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Bool\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }
    inline auto as_bool() const -> const Bool&
    {
        //
        try {
            return std::get<Bool>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Bool\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }

    inline auto as_string() -> String&
    {
        //
        try {
            return std::get<String>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as String\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }
    inline auto as_string() const -> const String&
    {
        //
        try {
            return std::get<String>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as String\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }

    inline auto as_ptr() -> Ptr&
    {
        //
        try {
            return std::get<Ptr>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Ptr\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }
    inline auto as_ptr() const -> const Ptr&
    {
        //
        try {
            return std::get<Ptr>(value);
        } catch (const std::bad_variant_access& ex) {
            std::cout << std::format("tried to unwrap {} as Ptr\n",
                value_type_to_string(this->m_type));
            throw;
        }
    }

    inline auto to_string() const -> std::string
    {
        switch (this->m_type) {
            case ValueType::Null:
                return "null";
            case ValueType::Int:
                return std::to_string(as_int().value);
            case ValueType::Bool:
                return as_bool().value ? "true" : "false";
            case ValueType::String:
                return std::format("\"{}\"", as_string().value);
            case ValueType::Ptr:
                return std::to_string(as_ptr().value);
        }
        std::unreachable();
    }

    inline auto to_repr_string() const -> std::string
    {
        return std::format(
            "{}({})", value_type_to_string(this->m_type), to_string());
    }

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

}