// $package. http://www.slack.net/~ant/

#include "blargg_common.h"

/* Copyright (C) 2008-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

#include "blargg_source.h"

BLARGG_NAMESPACE_BEGIN

// defined here to avoid need for blargg_errors.cpp in simple programs
blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY;

void blargg_vector_::init()
{
	begin_ = NULL;
	size_  = 0;
}

void blargg_vector_::clear()
{
	void* p = begin_;
	begin_  = NULL;
	size_   = 0;
	free( p );
}

blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size )
{
	if ( n != size_ )
	{
		if ( n == 0 )
		{
			// Simpler to handle explicitly. Realloc will handle a size of 0,
			// but then we have to avoid raising an error for a NULL return.
			clear();
		}
		else
		{
			void* p = realloc( begin_, n * elem_size );
			CHECK_ALLOC( p );
			begin_ = p;
			size_  = n;
		}
	}
	return blargg_ok;
}

static const BOOST::uint8_t mask_tab[6]={0x80,0xE0,0xF0,0xF8,0xFC,0xFE};

static const BOOST::uint8_t val_tab[6]={0,0xC0,0xE0,0xF0,0xF8,0xFC};

size_t utf8_char_len_from_header( char p_c )
{
	size_t cnt = 0;
	for(;;)
	{
		if ( ( p_c & mask_tab[cnt] ) == val_tab[cnt] ) break;
		if ( ++cnt >= 6 ) return 0;
	}

	return cnt + 1;
}

size_t utf8_decode_char( const char *p_utf8, unsigned & wide, size_t mmax )
{
	const BOOST::uint8_t * utf8 = ( const BOOST::uint8_t* )p_utf8;

	if ( mmax == 0 )
	{
		wide = 0;
		return 0;
	}

	if ( utf8[0] < 0x80 )
	{
		wide = utf8[0];
		return utf8[0]>0 ? 1 : 0;
	}
	if ( mmax > 6 ) mmax = 6;
	wide = 0;

	unsigned res=0;
	unsigned n;
	unsigned cnt=0;
	for(;;)
	{
		if ( ( *utf8 & mask_tab[cnt] ) == val_tab[cnt] ) break;
		if ( ++cnt >= mmax ) return 0;
	}
	cnt++;

	if ( cnt==2 && !( *utf8 & 0x1E ) ) return 0;

	if ( cnt == 1 )
		res = *utf8;
	else
		res = ( 0xFF >> ( cnt + 1 ) ) & *utf8;

	for ( n = 1; n < cnt; n++ )
	{
		if ( ( utf8[n] & 0xC0 ) != 0x80 )
			return 0;
		if ( !res && n == 2 && !( ( utf8[n] & 0x7F ) >> ( 7 - cnt ) ) )
			return 0;

		res = ( res << 6 ) | ( utf8[n] & 0x3F );
	}

	wide = res;

	return cnt;
}

size_t utf8_encode_char( unsigned wide, char * target )
{
	size_t count;

	if ( wide < 0x80 )
		count = 1;
	else if ( wide < 0x800 )
		count = 2;
	else if ( wide < 0x10000 )
		count = 3;
	else if ( wide < 0x200000 )
		count = 4;
	else if ( wide < 0x4000000 )
		count = 5;
	else if ( wide <= 0x7FFFFFFF )
		count = 6;
	else
		return 0;

	if ( target == 0 )
		return count;

	switch ( count )
	{
    case 6:
		target[5] = 0x80 | ( wide & 0x3F );
		wide = wide >> 6;
		wide |= 0x4000000;
    case 5:
		target[4] = 0x80 | ( wide & 0x3F );
		wide = wide >> 6;
		wide |= 0x200000;
    case 4:
		target[3] = 0x80 | ( wide & 0x3F );
		wide = wide >> 6;
		wide |= 0x10000;
    case 3:
		target[2] = 0x80 | ( wide & 0x3F );
		wide = wide >> 6;
		wide |= 0x800;
    case 2:
		target[1] = 0x80 | ( wide & 0x3F );
		wide = wide >> 6;
		wide |= 0xC0;
	case 1:
		target[0] = wide;
	}

	return count;
}

size_t utf16_encode_char( unsigned cur_wchar, blargg_wchar_t * out )
{
	if ( cur_wchar < 0x10000 )
	{
        if ( out ) *out = (blargg_wchar_t) cur_wchar; return 1;
	}
	else if ( cur_wchar < ( 1 << 20 ) )
	{
		unsigned c = cur_wchar - 0x10000;
		//MSDN:
        //The first (high) surrogate is a 16-bit code value in the range U+D800 to U+DBFF. The second (low) surrogate is a 16-bit code value in the range U+DC00 to U+DFFF. Using surrogates, Unicode can support over one million characters. For more details about surrogates, refer to The Unicode Standard, version 2.0.
		if ( out )
		{
            out[0] = ( blargg_wchar_t )( 0xD800 | ( 0x3FF & ( c >> 10 ) ) );
            out[1] = ( blargg_wchar_t )( 0xDC00 | ( 0x3FF & c ) ) ;
		}
		return 2;
	}
	else
	{
		if ( out ) *out = '?'; return 1;
	}
}

size_t utf16_decode_char( const blargg_wchar_t * p_source, unsigned * p_out, size_t p_source_length )
{
	if ( p_source_length == 0 ) return 0;
	else if ( p_source_length == 1 )
	{
		*p_out = p_source[0];
		return 1;
	}
	else
	{
		size_t retval = 0;
		unsigned decoded = p_source[0];
		if ( decoded != 0 )
		{
			retval = 1;
			if ( ( decoded & 0xFC00 ) == 0xD800 )
			{
				unsigned low = p_source[1];
				if ( ( low & 0xFC00 ) == 0xDC00 )
				{
					decoded = 0x10000 + ( ( ( decoded & 0x3FF ) << 10 ) | ( low & 0x3FF ) );
					retval = 2;
				}
			}
		}
		*p_out = decoded;
		return retval;
	}
}

// Converts wide-character path to UTF-8. Free result with free(). Only supported on Windows.
char* blargg_to_utf8( const blargg_wchar_t* wpath )
{
	if ( wpath == NULL )
		return NULL;

	size_t needed = 0;
    size_t mmax = blargg_wcslen( wpath );
	if ( mmax <= 0 )
		return NULL;

	size_t ptr = 0;
	while ( ptr < mmax )
	{
		unsigned wide = 0;
		size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr );
		if ( !char_len ) break;
		ptr += char_len;
		needed += utf8_encode_char( wide, 0 );
	}
	if ( needed <= 0 )
		return NULL;

	char* path = (char*) calloc( needed + 1, 1 );
	if ( path == NULL )
		return NULL;

	ptr = 0;
	size_t actual = 0;
	while ( ptr < mmax && actual < needed )
	{
		unsigned wide = 0;
		size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr );
		if ( !char_len ) break;
		ptr += char_len;
		actual += utf8_encode_char( wide, path + actual );
	}

	if ( actual == 0 )
	{
		free( path );
		return NULL;
	}

	assert( actual == needed );
	return path;
}

// Converts UTF-8 path to wide-character. Free result with free() Only supported on Windows.
blargg_wchar_t* blargg_to_wide( const char* path )
{
	if ( path == NULL )
		return NULL;

	size_t mmax = strlen( path );
	if ( mmax <= 0 )
		return NULL;

	size_t needed = 0;
	size_t ptr = 0;
	while ( ptr < mmax )
	{
		unsigned wide = 0;
		size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr );
		if ( !char_len ) break;
		ptr += char_len;
		needed += utf16_encode_char( wide, 0 );
	}
	if ( needed <= 0 )
		return NULL;

    blargg_wchar_t* wpath = (blargg_wchar_t*) calloc( needed + 1, sizeof *wpath );
	if ( wpath == NULL )
		return NULL;

	ptr = 0;
	size_t actual = 0;
	while ( ptr < mmax && actual < needed )
	{
		unsigned wide = 0;
		size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr );
		if ( !char_len ) break;
		ptr += char_len;
		actual += utf16_encode_char( wide, wpath + actual );
	}
	if ( actual == 0 )
	{
		free( wpath );
		return NULL;
	}

	assert( actual == needed );
	return wpath;
}

BLARGG_NAMESPACE_END