// ---------------------------------------------------------------------------------
//  clipboard.h:
//      'clipboard_t' class implementation.
//
//  2009 Written by Luis García.
//
//  History:
//    09.08.20 - Added STL like functions
//    09.08.19 - First release
// ---------------------------------------------------------------------------------

#include <windows.h>

#include <stdexcept>
#include <assert.h>
#include "clipboard.h"

namespace newcastle {

	_Clipboard<char> clipboard;

	_Clipboard<wchar_t> wclipboard;

	template <
		typename _CharTy
	>
	struct _Supported_format;

	template <>
	struct _Supported_format<char> {
		static const UINT _Format = CF_TEXT;
		static const char _CR = '\r', _LF = '\n', _Null = 0;
	};

	template <>
	struct _Supported_format<wchar_t> {
		static const UINT _Format = CF_UNICODETEXT;
		static const char _CR = L'\r', _LF = L'\n', _Null = 0;
	};

	class _Clipboard_impl {

		HWND _Handle;

		bool _Create(
			) {
				HINSTANCE _Instance = GetModuleHandle(NULL);
				if (!_Instance)
					return false;

				_Handle = CreateWindow(L"STATIC", L"", WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT,
					CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, _Instance, NULL);
				if (!_Handle)
					return false;

				return true;
			}

		void _Destroy(
			) {
				if (_Handle)
					DestroyWindow(_Handle);
				_Handle = NULL;
			}

		template <
			typename _CharTy
		>
		size_t _Get_Windows_length(
			const _CharTy * _C_string, size_t _Src_count
			) {
				typedef _Supported_format<_CharTy> _Format;

				size_t _Count;
				for (_Count = 0; * _C_string != _Format::_Null && _Src_count > 0; ++_Count, ++_C_string, --_Src_count)
					if (* _C_string == _Format::_LF) ++_Count;

				return _Count;
			}

		template <
			typename _CharTy
		>
		size_t _Get_C_length(
			const _CharTy * _Windows_string
			) {
				typedef _Supported_format<_CharTy> _Format;

				size_t _Count, _Remove;
				for (_Count = _Remove = 0; * _Windows_string != _Format::_Null; ++_Count, ++_Windows_string)
					if (* _Windows_string == _Format::_CR && * (_Windows_string + 1) == _Format::_LF) ++_Remove;

				return _Count - _Remove;
			}

		template <
			typename _CharTy
		>
		void _Copy_to_Windows(
			_CharTy * _Destination, const _CharTy * _Source, size_t _Src_count
			) {
				typedef _Supported_format<_CharTy> _Format;

				size_t _Count;
				for (_Count = 0; _Source[_Count] != _Format::_Null && _Src_count > 0; ++_Count, --_Src_count)
					if (_Source[_Count] == _Format::_LF) {
						memcpy(_Destination, _Source, _Count * sizeof(_CharTy));
						_Destination += _Count;
						* _Destination++ = _Format::_CR;
						_Source += _Count;
						_Count = 0;
					}

				memcpy(_Destination, _Source, (_Count + 1) * sizeof(_CharTy));
			}

		template <
			typename _CharTy, typename _TraitsTy, typename _AllocatorTy
		>
		void _Copy_to_C(
			std::basic_string<_CharTy, _TraitsTy, _AllocatorTy> & _Destination, const _CharTy * _Source
			) {
				typedef _Supported_format<_CharTy> _Format;

				for (size_t _I = 0; * _Source != _Format::_Null; ++_I) {
					if (* _Source == _Format::_CR && * (_Source + 1) == _Format::_LF) ++_Source;
					_Destination[_I] = * _Source++;
				}
			}

	public:

		_Clipboard_impl(
			) {
				if (!_Create())
					throw std::runtime_error("failed to create window");
			}

		~_Clipboard_impl(
			) {
				_Destroy();
			}

		template <
			typename _CharTy
		>
		void _Set(
			const _CharTy * _Source, size_t _Count
			) {
				typedef _Supported_format<_CharTy> _Format_handler;

				assert(_Handle != NULL);

				if (!OpenClipboard(_Handle))
					throw std::runtime_error("failed to open the clipboard");

				EmptyClipboard();

				size_t _Length = _Get_Windows_length(_Source, _Count);

				HGLOBAL _Memory_handle = GlobalAlloc(GMEM_MOVEABLE, (_Length + 1) * sizeof(_CharTy));
				if (!_Memory_handle) {
					CloseClipboard();
					throw std::runtime_error("failed to allocate memory");
				}

				_CharTy * _Buffer = reinterpret_cast<_CharTy *>(GlobalLock(_Memory_handle));
				if (!_Buffer) {
					CloseClipboard();
					GlobalFree(_Memory_handle);
					throw std::runtime_error("failed to lock memory");
				}

				_Copy_to_Windows(_Buffer, _Source, _Count);

				GlobalUnlock(_Memory_handle);

				UINT _Format = _Format_handler::_Format;
				if (!SetClipboardData(_Format, _Memory_handle)) {
					CloseClipboard();
					throw std::runtime_error("failed to set clipboard data");
				}

				CloseClipboard();
			}

		template <
			typename _CharTy, typename _TraitsTy, typename _AllocatorTy
		>
		void _Get(
			std::basic_string<_CharTy, _TraitsTy, _AllocatorTy> & _Text
			) {
				assert(_Handle != NULL);

				if (!OpenClipboard(_Handle))
					throw std::runtime_error("failed to open the clipboard");

				UINT _Format = _Supported_format<_CharTy>::_Format;
				HGLOBAL _Memory_handle = GetClipboardData(_Format);
				if (!_Memory_handle) {
					CloseClipboard();
					throw std::runtime_error("failed to get clipboard data");
				}

				_CharTy * _Buffer = reinterpret_cast<_CharTy *>(GlobalLock(_Memory_handle));
				if (!_Buffer) {
					CloseClipboard();
					throw std::runtime_error("failed to lock memory");
				}

				size_t _Length = _Get_C_length(_Buffer);
				_Copy_to_C(_Text.assign(_Length, ' '), _Buffer);

				GlobalUnlock(_Memory_handle);

				CloseClipboard();
			}

		void _Clear(
			) {
				assert(_Handle != NULL);

				if (!OpenClipboard(_Handle))
					throw std::runtime_error("failed to open the clipboard");

				EmptyClipboard();

				CloseClipboard();
			}

		template <
			typename _CharTy
		>
		size_t _Size(
			) {
				assert(_Handle != NULL);

				if (!OpenClipboard(_Handle))
					throw std::runtime_error("failed to open the clipboard");

				UINT _Format = _Supported_format<_CharTy>::_Format;
				HGLOBAL _Memory_handle = GetClipboardData(_Format);
				if (!_Memory_handle) {
					CloseClipboard();
					throw std::runtime_error("failed to get clipboard data");
				}

				_CharTy * _Buffer = reinterpret_cast<_CharTy *>(GlobalLock(_Memory_handle));
				if (!_Buffer) {
					CloseClipboard();
					throw std::runtime_error("failed to lock memory");
				}

				size_t _Length = _Get_C_length(_Buffer);

				GlobalUnlock(_Memory_handle);

				CloseClipboard();

				return _Length;
			}

	};

	class _Clipboard_watcher {
		_Clipboard_impl * _Impl;

	public:
		_Clipboard_watcher(
			)
			: _Impl(NULL)
			{
			}

		~_Clipboard_watcher(
			) {
				delete _Impl;
			}

		_Clipboard_impl & _Get_instance(
			) {
				if (!_Impl)
					_Impl = new _Clipboard_impl;
				return * _Impl;
			}

	};

	_Clipboard_watcher _Watcher;

	void _Clipboard_traits_A::_Assign(
		_Const_pointer _Ptr, _Size_type _Count
		) {
			_Watcher._Get_instance()._Set(_Ptr, _Count);
		}

	bool _Clipboard_traits_A::_IsAvailable(
		) {
			return IsClipboardFormatAvailable(_Supported_format<char>::_Format) != 0;
		}

	bool _Clipboard_traits_A::_IsEmpty(
		) {
			if (!_IsAvailable()) return false;
			return _Size() > 0;
		}

	void _Clipboard_traits_A::_Clear(
		) {
			_Watcher._Get_instance()._Clear();
		}

	_Clipboard_traits_A::_Size_type _Clipboard_traits_A::_Size(
		) {
			return _Watcher._Get_instance()._Size<char>();
		}

	_Clipboard_traits_A::_String _Clipboard_traits_A::_Str(
		) {
			_String _Text;
			if (_IsAvailable())
				_Watcher._Get_instance()._Get(_Text);
			return _Text;
		}

	void _Clipboard_traits_W::_Assign(
		_Const_pointer _Ptr, _Size_type _Count
		) {
			_Watcher._Get_instance()._Set(_Ptr, _Count);
		}

	bool _Clipboard_traits_W::_IsAvailable(
		) {
			return IsClipboardFormatAvailable(_Supported_format<wchar_t>::_Format) != 0;
		}

	bool _Clipboard_traits_W::_IsEmpty(
		) {
			if (!_IsAvailable()) return false;
			return _Size() > 0;
		}

	void _Clipboard_traits_W::_Clear(
		) {
			_Watcher._Get_instance()._Clear();
		}

	_Clipboard_traits_W::_Size_type _Clipboard_traits_W::_Size(
		) {
			return _Watcher._Get_instance()._Size<wchar_t>();
		}

	_Clipboard_traits_W::_String _Clipboard_traits_W::_Str(
		) {
			_String _Text;
			if (_IsAvailable())
				_Watcher._Get_instance()._Get(_Text);
			return _Text;
		}

}

// ---------------------------------------------------------------------------------
//  End of file
// ---------------------------------------------------------------------------------
