/*

Copyright (c) 2011-2012 星くず彼方に

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

---------------------------------------------------------------------------

The zlib/libpng License

Copyright (c) 2011-2012 星くず彼方に

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source
distribution.

*/

#pragma once

#include <cstdio>
#include <vector>
#include <limits>
#include <windows.h>
#include <Shlwapi.h>

#pragma comment(lib, "Shlwapi.lib")

namespace xtal{

	//	コンソールがUTF-8時にfwriteでは一部文字化けを起こすのでネイティブAPIで入出力
	//	※文字コード変換などしない
	class WinCStdioStdStreamLib : public StdStreamLib {
	public:
		virtual uint_t read_stdin_stream(void* stdin_stream_object, void* dest, uint_t size)
		{
			DWORD result(0);
			::ReadFile( ::GetStdHandle(STD_INPUT_HANDLE), dest, size, &result, NULL );
			return	result;
		}

		virtual uint_t write_stdout_stream(void* stdout_stream_object, const void* src, uint_t size)
		{
			DWORD result(0);
			::WriteFile( ::GetStdHandle(STD_OUTPUT_HANDLE), src, size, &result, NULL );
			return	result;
		}

		virtual uint_t write_stderr_stream(void* stderr_stream_object, const void* src, uint_t size)
		{
			DWORD result(0);
			::WriteFile( ::GetStdHandle(STD_ERROR_HANDLE), src, size, &result, NULL );
			return	result;
		}
	};

	namespace detail {

		//	大きい型を取得
		template<typename L,typename R,bool less_result>
		struct large_type_;
		template<typename L,typename R>
		struct large_type_<L,R,false>
		{
			typedef	L	type;
		};
		template<typename L,typename R>
		struct large_type_<L,R,true>
		{
			typedef	R	type;
		};

		//	大きい型を取得
		template<typename L,typename R>
		struct large_type: public large_type_<L,R,sizeof(L) < sizeof(R)> {};

		//	文字コードを変換
		struct converter_t
		{
			//	ワイド文字列からchar文字列へ変換
			uint_t wide_to_char( std::vector<char>& dest, wchar_t const* src, uint_t size, UINT dest_codepage ) const
			{
				if( size <= 0 ) return 0;

				typedef large_type<uint_t,unsigned int>::type large_uint_t;
				assert( static_cast<large_uint_t>(size) <= static_cast<large_uint_t>((std::numeric_limits<int>::max)()) );

				int const dest_size( ::WideCharToMultiByte( dest_codepage, 0, src, static_cast<int>(size), NULL, 0, NULL, NULL) );
				if( dest_size <= 0 ) return 0;

				assert( dest_size <= (std::numeric_limits<int>::max)() - 1 );
				dest.resize( dest_size + 1 );
				return	::WideCharToMultiByte( dest_codepage, 0, src, static_cast<int>(size), &dest[0], static_cast<int>(dest.size()), NULL, NULL );
			}

			//	char文字列からワイド文字列へ変換
			uint_t char_to_wide( std::vector<wchar_t>& dest, char const* src, uint_t size, UINT src_codepage ) const
			{
				if( size <= 0 ) return 0;

				typedef large_type<uint_t,unsigned int>::type large_uint_t;
				assert( static_cast<large_uint_t>(size) <= static_cast<large_uint_t>((std::numeric_limits<int>::max)()) );

				int const dest_size( ::MultiByteToWideChar( src_codepage, 0, src, static_cast<int>(size), NULL, 0) + 1 );
				if( dest_size <= 0 ) return 0;

				assert( dest_size <= (std::numeric_limits<int>::max)() - 1 );
				dest.resize( dest_size + 1 );
				return	::MultiByteToWideChar( src_codepage, 0, src, static_cast<int>(size), &dest[0], static_cast<int>(dest.size()) );
			}
		};

		//	文字コードを変換
		class converter_mlang_t
		{
			typedef HRESULT (WINAPI *ConvertINetUnicodeToMultiBytePtr)( LPDWORD lpdwMode, DWORD dwEncoding,    LPCWSTR lpSrcStr, LPINT lpnWideCharCount,  LPSTR  lpDstStr, LPINT lpnMultiCharCount );
			typedef HRESULT (WINAPI *ConvertINetMultiByteToUnicodePtr)( LPDWORD lpdwMode, DWORD dwSrcEncoding, LPCSTR  lpSrcStr, LPINT lpnMultiCharCount, LPWSTR lpDstStr, LPINT lpnWideCharCount  );

			HMODULE	const mlang_;
			ConvertINetUnicodeToMultiBytePtr const ConvertINetUnicodeToMultiByte;
			ConvertINetMultiByteToUnicodePtr const ConvertINetMultiByteToUnicode;

			static HMODULE load_library()
			{
				wchar_t	path[MAX_PATH];
				::GetSystemDirectoryW( path, MAX_PATH );
				::PathCombineW( path, path, L"mlang.dll" );
				return	::LoadLibraryW( path );
			}

		public:
			converter_mlang_t():
				mlang_(load_library()),
				ConvertINetUnicodeToMultiByte( reinterpret_cast<ConvertINetUnicodeToMultiBytePtr>(GetProcAddress( mlang_, "ConvertINetUnicodeToMultiByte" )) ),
				ConvertINetMultiByteToUnicode( reinterpret_cast<ConvertINetMultiByteToUnicodePtr>(GetProcAddress( mlang_, "ConvertINetMultiByteToUnicode" )) )
			{}

			~converter_mlang_t(){
				::FreeLibrary( mlang_ );
			}

			//	ワイド文字列からchar文字列へ変換
			uint_t wide_to_char( std::vector<char>& dest, wchar_t const* src, uint_t size, UINT dest_codepage ) const
			{
				if( size <= 0 ) return 0;

				typedef large_type<uint_t,unsigned int>::type large_uint_t;
				assert( static_cast<large_uint_t>(size) <= static_cast<large_uint_t>((std::numeric_limits<int>::max)()) );

				INT src_size( static_cast<int>(size) ), dest_size(0);
				HRESULT rsl( ConvertINetUnicodeToMultiByte( 0, dest_codepage, src, &src_size, NULL, &dest_size ) );
				if( FAILED(rsl) || rsl == S_FALSE ) return 0;
				if( dest_size <= 0 ) return 0;

				assert( dest_size <= (std::numeric_limits<int>::max)() - 1 );
				dest.resize( dest_size + 1 );
				dest_size = static_cast<int>(dest.size());
				if( FAILED(ConvertINetUnicodeToMultiByte( 0, dest_codepage, src, &src_size, &dest[0], &dest_size )) ) return 0;
				return	dest_size;
			}

			//	char文字列からワイド文字列へ変換
			uint_t char_to_wide( std::vector<wchar_t>& dest, char const* src, uint_t size, UINT src_codepage ) const
			{
				if( size <= 0 ) return 0;

				typedef large_type<uint_t,unsigned int>::type large_uint_t;
				assert( static_cast<large_uint_t>(size) <= static_cast<large_uint_t>((std::numeric_limits<int>::max)()) );

				INT src_size( static_cast<int>(size) ), dest_size(0);
				HRESULT rsl( ConvertINetMultiByteToUnicode( 0, src_codepage, src, &src_size, NULL, &dest_size ) );
				if( FAILED(rsl) || rsl == S_FALSE ) return 0;
				if( dest_size <= 0 ) return 0;

				assert( dest_size <= (std::numeric_limits<int>::max)() - 1 );
				dest.resize( dest_size + 1 );
				dest_size = static_cast<int>(dest.size());
				if( FAILED(ConvertINetMultiByteToUnicode( 0, src_codepage, src, &src_size, &dest[0], &dest_size )) ) return 0;
				return	dest_size;
			}
		};
		
		//	char文字列をコンソールのコードページに変換して出力する
		//	※入力は未対応　スレッドアンセーフ
		class WinCharCStdStreamLib : public StdStreamLib {
		protected:
			struct work_type {
				std::vector<char>		char_buffer;
				std::vector<wchar_t>	wide_buffer;
			};

			detail::converter_t		  const converter_;
			UINT const src_codepage_;

			WinCharCStdStreamLib( UINT src_codepage ): src_codepage_(src_codepage) {}
			virtual uint_t write( DWORD output_handle, void* stream_object, const void* src, uint_t size ) = 0;
		public:
			virtual void*	new_stdout_stream(){ return new(std::nothrow) work_type; }
			virtual void delete_stdout_stream(void* stdout_stream_object){ delete reinterpret_cast<work_type*>(stdout_stream_object); }
			virtual void*	new_stderr_stream(){ return new(std::nothrow) work_type; }
			virtual void delete_stderr_stream(void* stderr_stream_object){ delete reinterpret_cast<work_type*>(stderr_stream_object); }

			virtual uint_t read_stdin_stream(void* stdin_stream_object, void* dest, uint_t size)
			{
				DWORD result(0);
				::ReadFile( ::GetStdHandle(STD_INPUT_HANDLE), dest, size, &result, NULL );
				return	result;
			}

			virtual uint_t write_stdout_stream(void* stdout_stream_object, const void* src, uint_t size)
			{
				return	write( STD_OUTPUT_HANDLE, stdout_stream_object, src, size );
			}

			virtual uint_t write_stderr_stream(void* stderr_stream_object, const void* src, uint_t size)
			{
				return	write( STD_ERROR_HANDLE, stderr_stream_object, src, size );
			}
		};

	};


	//	char文字列をコンソールのコードページに変換して出力する
	//	※入力は未対応　スレッドアンセーフ
	class WinCharCStdStreamLib : public detail::WinCharCStdStreamLib
	{
		virtual uint_t write( DWORD output_handle, void* stream_object, const void* src, uint_t size )
		{
			if( size <= 0 ) return 0;
			UINT const dest_codepage( ::GetConsoleOutputCP() );
			if( dest_codepage == src_codepage_ ){
				DWORD result(0);
				::WriteFile( ::GetStdHandle(output_handle), src, size, &result, NULL );
			}else{
				work_type& work( *reinterpret_cast<work_type*>(stream_object) );

				uint_t const wide_size( converter_.char_to_wide( work.wide_buffer, reinterpret_cast<char const*>(src), size / sizeof(char), src_codepage_ ) );
				if( wide_size <= 0 ) return 0;

				uint_t const char_size( converter_.wide_to_char( work.char_buffer, &work.wide_buffer[0], wide_size, dest_codepage ) );
				if( char_size <= 0 ) return 0;

				DWORD result(0);
				::WriteFile( ::GetStdHandle(output_handle), &work.char_buffer[0], char_size, &result, NULL );
			}
			return	size;
		}
	public:
		WinCharCStdStreamLib( UINT src_codepage ): detail::WinCharCStdStreamLib(src_codepage) {}
	};

	//	char文字列をコンソールのコードページに変換して出力する
	//	コードページをmlang用のとOS用のと二種類指定する
	//	※入力は未対応　スレッドアンセーフ
	class WinMLangCStdStreamLib : public detail::WinCharCStdStreamLib
	{
		detail::converter_mlang_t const converter_mlang_;
		UINT const src_codepage_mlang_;

		virtual uint_t write( DWORD output_handle, void* stream_object, const void* src, uint_t size )
		{
			if( size <= 0 ) return 0;
			UINT const dest_codepage( ::GetConsoleOutputCP() );
			if( dest_codepage == src_codepage_ || dest_codepage == src_codepage_mlang_ ){
				DWORD result(0);
				::WriteFile( ::GetStdHandle(output_handle), src, size, &result, NULL );
			}else{
				work_type& work( *reinterpret_cast<work_type*>(stream_object) );

				uint_t wide_size( converter_mlang_.char_to_wide( work.wide_buffer, reinterpret_cast<char const*>(src), size / sizeof(char), src_codepage_mlang_ ) );
				if( wide_size <= 0 ){
					wide_size = converter_.char_to_wide( work.wide_buffer, reinterpret_cast<char const*>(src), size / sizeof(char), src_codepage_ );
					if( wide_size <= 0 ) return 0;
				}

				uint_t const char_size( converter_.wide_to_char( work.char_buffer, &work.wide_buffer[0], wide_size, dest_codepage ) );
				if( char_size <= 0 ) return 0;

				DWORD result(0);
				::WriteFile( ::GetStdHandle(output_handle), &work.char_buffer[0], char_size, &result, NULL );
			}
			return	size;
		}
	public:
		WinMLangCStdStreamLib( UINT src_codepage_mlang, UINT src_codepage ): detail::WinCharCStdStreamLib(src_codepage), src_codepage_mlang_(src_codepage_mlang) {}
	};

	//	ワイド文字列をコンソールのコードページに変換して出力する
	//	※入力は未対応　スレッドアンセーフ
	class WinUTF16CStdStreamLib : public StdStreamLib
	{
		typedef	std::vector<char>	work_type;

		detail::converter_t	const converter_;

		uint_t write( DWORD output_handle, void* stream_object, const void* src, uint_t size )
		{
			work_type& buffer( *reinterpret_cast<work_type*>(stream_object) );

			uint_t const buf_size( converter_.wide_to_char( buffer, reinterpret_cast<wchar_t const*>(src), size / sizeof(wchar_t), ::GetConsoleOutputCP() ) );
			if( buf_size <= 0 ) return 0;

			DWORD result(0);
			::WriteFile( ::GetStdHandle(output_handle), &buffer[0], buf_size, &result, NULL );
			return	size;
		}
	public:
		WinUTF16CStdStreamLib(){}

		virtual void*	new_stdout_stream(){ return new(std::nothrow) work_type; }
		virtual void delete_stdout_stream(void* stdout_stream_object){ delete reinterpret_cast<work_type*>(stdout_stream_object); }
		virtual void*	new_stderr_stream(){ return new(std::nothrow) work_type; }
		virtual void delete_stderr_stream(void* stderr_stream_object){ delete reinterpret_cast<work_type*>(stderr_stream_object); }

		virtual uint_t read_stdin_stream(void* stdin_stream_object, void* dest, uint_t size)
		{
			DWORD result(0);
			::ReadFile( ::GetStdHandle(STD_INPUT_HANDLE), dest, size, &result, NULL );
			return	result;
		}

		virtual uint_t write_stdout_stream(void* stdout_stream_object, const void* src, uint_t size)
		{
			return	write( STD_OUTPUT_HANDLE, stdout_stream_object, src, size );
		}

		virtual uint_t write_stderr_stream(void* stderr_stream_object, const void* src, uint_t size)
		{
			return	write( STD_ERROR_HANDLE, stderr_stream_object, src, size );
		}
	};

	struct WinUTF8CStdStreamLib: public WinCharCStdStreamLib  {	WinUTF8CStdStreamLib(): WinCharCStdStreamLib (65001		 ) {} };
	struct WinSJISCStdStreamLib: public WinCharCStdStreamLib  {	WinSJISCStdStreamLib(): WinCharCStdStreamLib (932		 ) {} };
	struct WinEUCCStdStreamLib : public WinMLangCStdStreamLib {	WinEUCCStdStreamLib():  WinMLangCStdStreamLib(51932,20932) {} };
}
