#include "pfc.h"


#ifdef _WINDOWS

namespace {
	template<typename t_char, bool isChecked = true> 
	class string_writer_t {
	public:
		string_writer_t(t_char * p_buffer,t_size p_size) : m_buffer(p_buffer), m_size(p_size), m_writeptr(0) {}
		
		void write(t_char p_char) {
			if (isChecked) {
				if (m_writeptr < m_size) {
					m_buffer[m_writeptr++] = p_char;
				}
			} else {
				m_buffer[m_writeptr++] = p_char;
			}
		}
		void write_multi(const t_char * p_buffer,t_size p_count) {
			if (isChecked) {
				const t_size delta = pfc::min_t<t_size>(p_count,m_size-m_writeptr);
				for(t_size n=0;n<delta;n++) {
					m_buffer[m_writeptr++] = p_buffer[n];
				}
			} else {
				for(t_size n = 0; n < p_count; ++n) {
					m_buffer[m_writeptr++] = p_buffer[n];
				}
			}
		}

		void write_as_utf8(unsigned p_char) {
			if (isChecked) {
				char temp[6];
				t_size n = pfc::utf8_encode_char(p_char,temp);
				write_multi(temp,n);
			} else {
				m_writeptr += pfc::utf8_encode_char(p_char, m_buffer + m_writeptr);
			}
		}

		void write_as_wide(unsigned p_char) {
			if (isChecked) {
				wchar_t temp[2];
				t_size n = pfc::utf16_encode_char(p_char,temp);
				write_multi(temp,n);
			} else {
				m_writeptr += pfc::utf16_encode_char(p_char, m_buffer + m_writeptr);
			}
		}

		t_size finalize() {
			if (isChecked) {
				if (m_size == 0) return 0;
				t_size terminator = pfc::min_t<t_size>(m_writeptr,m_size-1);
				m_buffer[terminator] = 0;
				return terminator;
			} else {
				m_buffer[m_writeptr] = 0;
				return m_writeptr;
			}
		}
		bool is_overrun() const {
			return m_writeptr >= m_size;
		}
	private:
		t_char * m_buffer;
		t_size m_size;
		t_size m_writeptr;
	};



};

namespace pfc {
	namespace stringcvt {


		t_size convert_utf8_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_in,t_size p_in_size) {
			const t_size insize = p_in_size;
			t_size inptr = 0;
			string_writer_t<wchar_t> writer(p_out,p_out_size);

			while(inptr < insize && !writer.is_overrun()) {
				unsigned newchar = 0;
				t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr);
				if (delta == 0 || newchar == 0) break;
				PFC_ASSERT(inptr + delta <= insize);
				inptr += delta;
				writer.write_as_wide(newchar);
			}

			return writer.finalize();
		}

		t_size convert_utf8_to_wide_unchecked(wchar_t * p_out,const char * p_in) {
			t_size inptr = 0;
			string_writer_t<wchar_t,false> writer(p_out,~0);

			while(!writer.is_overrun()) {
				unsigned newchar = 0;
				t_size delta = utf8_decode_char(p_in + inptr,newchar);
				if (delta == 0 || newchar == 0) break;
				inptr += delta;
				writer.write_as_wide(newchar);
			}

			return writer.finalize();
		}

		t_size convert_wide_to_utf8(char * p_out,t_size p_out_size,const wchar_t * p_in,t_size p_in_size) {
			const t_size insize = p_in_size;
			t_size inptr = 0;
			string_writer_t<char> writer(p_out,p_out_size);

			while(inptr < insize && !writer.is_overrun()) {
				unsigned newchar = 0;
				t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr);
				if (delta == 0 || newchar == 0) break;
				PFC_ASSERT(inptr + delta <= insize);
				inptr += delta;
				writer.write_as_utf8(newchar);
			}

			return writer.finalize();
		}

		t_size estimate_utf8_to_wide(const char * p_in) {
			t_size inptr = 0;
			t_size retval = 1;//1 for null terminator
			for(;;) {
				unsigned newchar = 0;
				t_size delta = utf8_decode_char(p_in + inptr,newchar);
				if (delta == 0 || newchar == 0) break;
				inptr += delta;
				
				{
					wchar_t temp[2];
					delta = utf16_encode_char(newchar,temp);
					if (delta == 0) break;
					retval += delta;
				}
			}
			return retval;
		}

		t_size estimate_utf8_to_wide(const char * p_in,t_size p_in_size) {
			const t_size insize = p_in_size;
			t_size inptr = 0;
			t_size retval = 1;//1 for null terminator
			while(inptr < insize) {
				unsigned newchar = 0;
				t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr);
				if (delta == 0 || newchar == 0) break;
				PFC_ASSERT(inptr + delta <= insize);
				inptr += delta;
				
				{
					wchar_t temp[2];
					delta = utf16_encode_char(newchar,temp);
					if (delta == 0) break;
					retval += delta;
				}
			}
			return retval;
		}

		t_size estimate_wide_to_utf8(const wchar_t * p_in,t_size p_in_size) {
			const t_size insize = p_in_size;
			t_size inptr = 0;
			t_size retval = 1;//1 for null terminator
			while(inptr < insize) {
				unsigned newchar = 0;
				t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr);
				if (delta == 0 || newchar == 0) break;
				PFC_ASSERT(inptr + delta <= insize);
				inptr += delta;
				
				{
					char temp[6];
					delta = utf8_encode_char(newchar,temp);
					if (delta == 0) break;
					retval += delta;
				}
			}
			return retval;
		}


		t_size convert_codepage_to_wide(unsigned p_codepage,wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size) {
			if (p_out_size == 0) return 0;
			memset(p_out,0,p_out_size * sizeof(*p_out));
			MultiByteToWideChar(p_codepage,0,p_source,p_source_size,p_out,p_out_size);
			p_out[p_out_size-1] = 0;
			return wcslen(p_out);
		}

		t_size convert_wide_to_codepage(unsigned p_codepage,char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size) {
			if (p_out_size == 0) return 0;
			memset(p_out,0,p_out_size * sizeof(*p_out));
			WideCharToMultiByte(p_codepage,0,p_source,p_source_size,p_out,p_out_size,0,FALSE);
			p_out[p_out_size-1] = 0;
			return strlen(p_out);
		}

		t_size estimate_codepage_to_wide(unsigned p_codepage,const char * p_source,t_size p_source_size) {
			return MultiByteToWideChar(p_codepage,0,p_source,strlen_max(p_source,p_source_size),0,0) + 1;
		}
		t_size estimate_wide_to_codepage(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size) {
			return WideCharToMultiByte(p_codepage,0,p_source,wcslen_max(p_source,p_source_size),0,0,0,FALSE) + 1;
		}
	}

}

#endif //_WINDOWS

