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

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

    Vector()
    {
    }

    Vector(const Vector<T>& other) = delete;

    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) = delete;

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

        if (m_data) clear();

        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) { clear(); }
    }

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

        return remove_at(0);
    }

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

    Slice<T> slice()
    {
        return { m_data, m_size };
    }

    Slice<T> slice(usize index)
    {
        check(index < m_size);

        return { m_data + index, m_size - index };
    }

    usize capacity() const
    {
        return m_capacity;
    }

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

    usize size() const
    {
        return m_size;
    }

    T remove_at(usize index)
    {
        check(index < m_size);

        T item = move(m_data[index]);

        memmove(m_data + index, m_data + (index + 1), (m_size - (index + 1)) * sizeof(T));

        m_size--;

        return move(item);
    }

    template <typename Callback> Option<T> remove_first_matching(Callback callback)
    {
        Option<usize> index = {};

        for (usize i = 0; i < m_size; i++)
        {
            if (callback(m_data[i]))
            {
                index = i;
                break;
            }
        }

        if (!index.has_value()) return {};

        return remove_at(index.value());
    }

    void clear()
    {
        for (usize i = 0; i < m_size; i++) { m_data[i].~T(); }

        m_size = m_capacity = 0;
        free_impl(m_data);
        m_data = nullptr;
    }

    Result<Vector<T>> shallow_copy()
    {
        Vector<T> other;
        TRY(other.try_reserve(m_capacity));
        memcpy(other.m_data, m_data, m_size);
        other.m_size = m_size;
        return other;
    }

    Result<Vector<T>> deep_copy()
    {
        Vector<T> other;
        TRY(other.try_reserve(m_capacity));
        for (usize i = 0; i < m_size; i++) { TRY(other.try_append(m_data[i])); }
        return other;
    }

    Result<Vector<T>> deep_copy(Result<T> (*copy_function)(const T&))
    {
        Vector<T> other;
        TRY(other.try_reserve(m_capacity));
        for (usize i = 0; i < m_size; i++)
        {
            auto copy = TRY(copy_function(m_data[i]));
            TRY(other.try_append(move(copy)));
        }
        return other;
    }

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