#pragma once

#include <stdint.h>
#include <type_traits>

#include <SDL3/SDL.h>

template<typename T>
class crArray
{
    static_assert( std::is_trivially_copyable<T>::value, "crArray<T>: trivially-copyable (POD) only" );

private:
    static constexpr int32_t DEFAULT_CAPACITY = 8;

    T*      _data     = nullptr;
    int32_t _size     = 0;
    int32_t _capacity = 0;

public:
    crArray()
        : crArray( DEFAULT_CAPACITY )
    {}

    explicit crArray( int32_t capacity )
    {
        if( capacity > 0 )
        {
            _data     = static_cast<T*>( SDL_malloc( static_cast<size_t>( capacity ) * sizeof( T ) ) );
            _capacity = capacity;
        }
    }

    ~crArray()
    {
        SDL_free( _data );
    }

    crArray( const crArray& )            = delete;
    crArray& operator=( const crArray& ) = delete;

    int32_t Size() const
    {
        return _size;
    }

    int32_t Capacity() const
    {
        return _capacity;
    }

    T* Data()
    {
        return _data;
    }

    const T* Data() const
    {
        return _data;
    }

    T& At( int32_t index )
    {
        SDL_assert( ( index >= 0 ) && ( index < _size ) );
        return _data[ index ];
    }

    const T& At( int32_t index ) const
    {
        SDL_assert( ( index >= 0 ) && ( index < _size ) );
        return _data[ index ];
    }

    void Add( const T& value )
    {
        if( _size == _capacity )
            Grow( _size + 1 );
        _data[ _size ] = value;
        ++_size;
    }

    void Pop()
    {
        SDL_assert( _size > 0 );
        --_size;
    }

    void Clear()
    {
        _size = 0;
    }

    void Reserve( int32_t capacity )
    {
        if( capacity <= _capacity )
            return;

        _data     = static_cast<T*>( SDL_realloc( _data, static_cast<size_t>( capacity ) * sizeof( T ) ) );
        _capacity = capacity;
    }

    void Resize( int32_t size )   // set the count directly (grows capacity if needed); new elements are uninitialized (POD) — for fill-by-index writes
    {
        if( size > _capacity )
            Reserve( size );
        _size = size;
    }

    void Shrink()
    {
        if( _size == _capacity )
            return;

        if( _size == 0 )
        {
            SDL_free( _data );
            _data     = nullptr;
            _capacity = 0;
            return;
        }

        _data     = static_cast<T*>( SDL_realloc( _data, static_cast<size_t>( _size ) * sizeof( T ) ) );
        _capacity = _size;
    }

private:
    void Grow( int32_t minCapacity )
    {
        int32_t newCapacity = ( _capacity > 0 ) ? _capacity : DEFAULT_CAPACITY;
        while( newCapacity < minCapacity )
            newCapacity *= 2;

        Reserve( newCapacity );
    }
};
