#pragma once
#include <Move.h>
#include <PlacementNew.h>
#include <Types.h>

struct Error
{
    Error(int err)
    {
        error = err;
    }

    int error;
};

template <typename T> class Result
{
  public:
    Result(const T& value)
    {
        m_storage.store_reference(value);
        m_has_value = true;
        m_has_error = false;
    }

    Result(T&& value)
    {
        m_storage.store_movable_reference(move(value));
        m_has_value = true;
        m_has_error = false;
    }

    Result(const Result<T>& other)
    {
        if (!other.m_has_error)
        {
            m_storage.store_reference(other.m_storage.fetch_reference());
            m_has_value = true;
            m_has_error = false;
        }
        else
        {
            m_has_error = true;
            m_has_value = false;
            m_error = other.m_error;
        }
    }

    Result(Result<T>&& other)
    {
        if (!other.m_has_error)
        {
            m_storage.store_movable_reference(move(other.m_storage.fetch_reference()));
            m_has_value = true;
            m_has_error = false;
        }
        else
        {
            m_has_error = true;
            m_has_value = false;
            m_error = other.m_error;
        }
    }

    Result(const Error& err)
    {
        m_error = err.error;
        m_has_error = true;
        m_has_value = false;
    }

    bool has_error()
    {
        return m_has_error;
    }

    bool has_value()
    {
        return m_has_value;
    }

    int error()
    {
        // ensure(has_error());
        return m_error;
    }

    Error release_error()
    {
        // ensure(has_error());
        return {m_error};
    }

    T value()
    {
        // ensure(has_value());
        return m_storage.fetch_reference();
    }

    T value_or(T other)
    {
        if (has_value()) return m_storage.fetch_reference();
        return other;
    }

    T release_value()
    {
        // ensure(has_value());
        T item = m_storage.fetch_reference();
        m_has_value = false;
        m_storage.destroy();
        return move(item);
    }

    ~Result()
    {
        if (has_value()) m_storage.destroy();
    }

  private:
    struct Storage
    {
        u8 buffer[sizeof(T)];

        T* fetch_ptr()
        {
            return (T*)buffer;
        }

        T& fetch_reference()
        {
            return *fetch_ptr();
        }

        const T* fetch_ptr() const
        {
            return (const T*)buffer;
        }

        const T& fetch_reference() const
        {
            return *fetch_ptr();
        }

        void store_ptr(T* ptr)
        {
            new (buffer) T(*ptr);
        }

        void store_reference(const T& ref)
        {
            new (buffer) T(ref);
        }

        void store_movable_reference(T&& ref)
        {
            new (buffer) T(ref);
        }

        void destroy()
        {
            fetch_reference().~T();
        }
    };
    Storage m_storage;
    int m_error;
    bool m_has_error;
    bool m_has_value;
};

template <> class Result<void>
{
  public:
    Result()
    {
        m_has_error = false;
    }

    Result(const Result<void>& other)
    {
        m_has_error = other.m_has_error;
        m_error = other.m_error;
    }

    Result(Result<void>&& other)
    {
        m_has_error = other.m_has_error;
        m_error = other.m_error;
    }

    Result(const Error& err)
    {
        m_error = err.error;
        m_has_error = true;
    }

    bool has_error()
    {
        return m_has_error;
    }

    bool has_value()
    {
        return !m_has_error;
    }

    int error()
    {
        // ensure(has_error());
        return m_error;
    }

    Error release_error()
    {
        // ensure(has_error());
        return {m_error};
    }

    void value()
    {
        // ensure(has_value());
        return;
    }

    void release_value()
    {
        // ensure(has_value());
        return;
    }

  private:
    int m_error;
    bool m_has_error;
};

// clang-format off
#define err Error{0}
// clang-format on