#include "macros.hpp"
#include "types.hpp"

void str_append( u32 dest_len, char* dest, u32 src_len, char const* src );
void str_concat( u32 dest_size, char* dest
	, u32 str_a_len, char const* str_a
	, u32 str_b_len, char const* str_b );
u32 str_length( char const* str );

#define str_ascii( str ) { sizeof( str ) - 1, ccast( char*, str) }

// Length tracked raw strings.
struct Str
{
	u32   len;
	char* ptr;

	void append( u32 src_len, char const* src ) {
		str_append( len, ptr, src_len, src );
	}
	void append( Str const src ) {
		str_append( len, ptr, src.len, src.ptr );
	}

	void concat( u32 dest_size, Str* dest, Str const str_a, Str const str_b )
	{
		str_concat( dest_size, dest->ptr
			, str_a.len, str_a.ptr
			, str_b.len, str_b.ptr );
		dest->len = str_a.len + str_b.len;
	}

	operator char*() const {
		return ptr;
	}
	char& operator []( u32 idx ) {
		return ptr[idx];
	}
	char const& operator []( u32 idx ) const {
		return ptr[idx];
	}
};

// Fixed length raw strings.
template< u32 capacity >
struct StrFixed
{
	constexpr static u32 Capacity = capacity;

	u32  len;
	char ptr[capacity];

	void append( u32 src_len, char const* src ) {
		str_append( len, data, src_len, src );
	}
	void append( Str const src ) {
		str_append( len, data, src.Len, src.data );
	}

	void concat( Str const str_a, Str const str_b )
	{
		str_concat( Capacity, ptr
			, str_a.len, str_a.ptr
			, str_b.len, str_b.ptr );
		len = str_a.len + str_b.len;
	}

	operator char*()           { return ptr;}
	operator char const*()     { return ptr; }
	operator Str()             { return { len, ptr }; }
	operator Str const() const { return { len, ptr }; }
	char& operator []( u32 idx ) {
		assert( idx < Capacity );
		return ptr[idx];
	}
	char const& operator []( u32 idx ) const {
		assert( idx < Capacity );
		return ptr[idx];
	}
};

inline
void str_append( u32 dest_len, char* dest, u32 src_len, char const* src )
{
	assert( dest_len > 0 );
	assert( dest != nullptr );
	assert( src_len > 0 );
	assert( src != nullptr );
	assert( dest_len >= src_len );

	char* dest_end = dest + dest_len;
	assert( *dest_end == '\0' );

	u32 left = src_len;
	while ( left-- )
	{
		*dest = *src;
		++ dest;
		++ src;
	}
}

void str_concat( u32 dest_size, char* dest
	, u32 str_a_len, char const* str_a
	, u32 str_b_len, char const* str_b )
{
	assert( dest_size > 0 );
	assert( dest != nullptr );
	assert( *dest == '\0' );
	assert( str_a_len > 0 );
	assert( str_a != nullptr );
	assert( str_b_len > 0 );
	assert( str_b != nullptr );
	assert( str_a_len + str_b_len < dest_size );

	char* dest_a = dest;
	char* dest_b = dest + str_a_len;
	if ( str_a_len > str_b_len )
	{
		u32 left = str_b_len;
		while ( left-- )
		{
			*dest_a = *str_a;
			*dest_b = *str_b;

			++ dest_a;
			++ dest_b;
			++ str_a;
			++ str_b;
		}

		left = str_a_len - str_b_len;
		while ( left-- )
		{
			*dest_a = *str_a;
			++ dest_a;
			++ str_a;
		}
	}
	else if ( str_a_len < str_b_len )
	{
		u32 left = str_a_len;
		while ( left-- )
		{
			*dest_a = *str_a;
			*dest_b = *str_b;
			++ dest_a;
			++ dest_b;
			++ str_a;
			++ str_b;
		}

		left = str_b_len - str_a_len;
		while ( left-- )
		{
			*dest_b = *str_b;
			++ dest_b;
			++ str_b;
		}
	}
	else
	{
		u32 left = str_a_len;
		while ( left-- )
		{
			*dest_a = *str_a;
			*dest_b = *str_b;
			++ dest_a;
			++ str_a;
			++ dest_b;
			++ str_b;
		}
	}
}

inline
u32 str_length( char const* str )
{
	assert( str != nullptr );

	u32 result = 0;
	while ( *str != '\0' )
	{
		++ result;
		++ str;
	}

	return result;
}

using StrPath = StrFixed< S16_MAX >;
