#pragma once
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/Result.h>
#include <luna/Types.h>

template <typename T> class Vector
{
  public:
    typedef T* Iterator;
    typedef const T* ConstIterator;

    Vector()
    {
    }

    Vector(const Vector<T>& other)
    {
        reserve(other.capacity());
        memcpy(m_data, other.data(), other.size());
        m_size = other.size();
    }

    Vector(Vector<T>&& other)
    {
        m_data = other.data();
        m_capacity = other.capacity();
        m_size = other.size();

        other.m_capacity = other.m_size = 0;
        other.m_data = nullptr;
    }

    Vector<T>& operator=(const Vector<T>& other)
    {
        if (&other == this) return *this;

        if (m_data) free_impl(m_data);

        m_data = nullptr;
        m_capacity = m_size = 0;

        reserve(other.capacity());
        memcpy(m_data, other.data(), other.size());
        m_size = other.size();

        return *this;
    }

    Vector<T>& operator=(Vector<T>&& other)
    {
        if (&other == this) return *this;

        if (m_data) free_impl(m_data);

        m_data = other.data();
        m_capacity = other.capacity();
        m_size = other.size();

        other.m_capacity = other.m_size = 0;
        other.m_data = nullptr;

        return *this;
    }

    ~Vector()
    {
        if (m_data) free_impl(m_data);
    }

    Result<void> try_reserve(usize capacity)
    {
        return resize(capacity);
    }

    void reserve(usize capacity)
    {
        resize(capacity).release_value();
    }

    Result<void> try_append(T&& item)
    {
        if (m_capacity == m_size) TRY(resize(m_capacity + 8));

        new (&m_data[m_size]) T(move(item));

        m_size++;

        return {};
    }

    Result<void> try_append(const T& item)
    {
        return try_append(T(item));
    }

    Option<T> try_pop()
    {
        if (m_size == 0) return {};

        m_size--;

        return move(m_data[m_size]);
    }

    Option<T> try_dequeue()
    {
        if (m_size == 0) return {};

        T item = move(m_data[0]);

        memmove(m_data, m_data + 1, m_size - 1);

        m_size--;

        return move(item);
    }

    const T& operator[](usize index) const
    {
        check(index < m_size);
        return m_data[index];
    }

    T& operator[](usize index)
    {
        check(index < m_size);
        return m_data[index];
    }

    Iterator begin()
    {
        return m_data;
    }

    ConstIterator begin() const
    {
        return m_data;
    }

    Iterator end()
    {
        return m_data + m_size;
    }

    ConstIterator end() const
    {
        return m_data + m_size;
    }

    const T* data() const
    {
        return m_data;
    }

    T* data()
    {
        return m_data;
    }

    usize capacity() const
    {
        return m_capacity;
    }

    usize byte_capacity() const
    {
        return m_capacity * sizeof(T);
    }

    usize size() const
    {
        return m_size;
    }

    void clear()
    {
        m_size = m_capacity = 0;
        free_impl(m_data);
        m_data = nullptr;
    }

  private:
    T* m_data { nullptr };
    usize m_capacity { 0 };
    usize m_size { 0 };

    Result<void> resize(usize new_capacity)
    {
        const usize new_byte_capacity = new_capacity * sizeof(T);

        void* const ptr = TRY(realloc_impl(m_data, new_byte_capacity));

        m_capacity = new_capacity;
        m_data = (T*)ptr;
        return {};
    }
};