#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
#
# This file is free software by d0n <d0n@janeiskla.de>
#
# 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
#
# This is distributed in the hope that it will be useful somehow.
#
# !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.
#
"""
clips - clipboard for various systems
"""
from os import name as osname

from platform import system

from time import sleep, time

from subprocess import Popen, PIPE, DEVNULL

def winclips():
	"""windows clipboards - the ugliest thing i've ever seen"""
	from ctypes import \
        windll, memmove, c_size_t, \
        sizeof, c_wchar_p, get_errno, c_wchar
	from ctypes.wintypes import \
        INT, HWND, DWORD, LPCSTR, HGLOBAL, \
        LPVOID, HINSTANCE, HMENU, BOOL, UINT, HANDLE
	from contextlib import contextmanager
	GMEM_MOVEABLE = 0x0002
	CF_UNICODETEXT = 13
	class CheckedCall(object):
		"""fancy windows callchecker"""
		def __init__(self, f):
			self.f = f
			super(CheckedCall, self).__setattr__("f", f)
		def __call__(self, *args):
			ret = self.f(*args)
			if not ret and get_errno():
				raise RuntimeError("Error calling %s"%self.f.__name__)
			return ret
		def __setattr__(self, key, value):
			setattr(self.f, key, value)
	mkwin = CheckedCall(windll.user32.CreateWindowExA)
	setattr('mkwin', 'argtypes', [
        DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
        INT, INT, HWND, HMENU, HINSTANCE, LPVOID])
	setattr('mkwin', 'restype', HWND)
	delwin = CheckedCall(windll.user32.DestroyWindow)
	setattr('delwin', 'argtypes', [HWND])
	setattr('delwin', 'restype', BOOL)
	clip = windll.user32.OpenClipboard
	setattr('clip', 'argtypes', [HWND])
	setattr('clip', 'restype', BOOL)
	clsclip = CheckedCall(windll.user32.CloseClipboard)
	setattr('clsclip', 'argtypes', [])
	setattr('clsclip', 'restype', BOOL)
	delclip = CheckedCall(windll.user32.EmptyClipboard)
	setattr('delclip', 'argtypes', [])
	setattr('delclip', 'restype', BOOL)
	getclip = CheckedCall(windll.user32.GetClipboardData)
	setattr('getclip', 'argtypes', [UINT])
	setattr('getclip', 'restype', HANDLE)
	setclip = CheckedCall(windll.user32.SetClipboardData)
	setattr('setclip', 'argtypes', [UINT, HANDLE])
	setattr('setclip', 'restype', HANDLE)
	allock = CheckedCall(windll.kernel32.GlobalAlloc)
	setattr('allock', 'argtypes', [UINT, c_size_t])
	setattr('setclip', 'restype', HGLOBAL)
	dolock = CheckedCall(windll.kernel32.GlobalLock)
	setattr('dolock', 'argtypes', [HGLOBAL])
	setattr('doclip', 'restype', LPVOID)
	unlock = CheckedCall(windll.kernel32.GlobalUnlock)
	setattr('unlock', 'argtypes', [HGLOBAL])
	setattr('unclip', 'restype', BOOL)
	@contextmanager
	def window():
		"""context that provides a valid window (hwnd)"""
		hwnd = mkwin(0, b"STATIC", None, 0, 0, 0, 0, 0, None, None, None, None)
		try:
			yield hwnd
		finally:
			delwin(hwnd)
	@contextmanager
	def clipboard(hwnd):
		"""windows clipboard context manager"""
		t = time() + 0.5
		success = False
		while time() < t:
			success = clip(hwnd)
			if success:
				break
			sleep(0.01)
		if not success:
			raise Exception("could not open clipboard")
		try:
			yield
		finally:
			clsclip()
	def _copy(text, mode=None):
		"""windows copy function"""
		if mode == 'b':
			return
		with window() as hwnd:
			with clipboard(hwnd):
				delclip()
				if text:
					count = len(text) + 1
					handle = allock(GMEM_MOVEABLE, count*sizeof(c_wchar))
					locked_handle = dolock(handle)
					memmove(
                        c_wchar_p(locked_handle),
                        c_wchar_p(text), count*sizeof(c_wchar))
					unlock(handle)
					setclip(CF_UNICODETEXT, handle)
	def _paste(_=None):
		"""windows paste function"""
		with clipboard(None):
			handle = getclip(CF_UNICODETEXT)
			if not handle:
				return "", ""
		return c_wchar_p(handle).value, c_wchar_p(handle).value
	return _copy, _paste

def osxclips():
	""""OSX clipboards"""
	def _copy(text, mode=None):
		"""osx copy function"""
		text = text if text else ''
		if mode != 'b':
			with Popen(['pbcopy'], stdin=PIPE, close_fds=True) as prc:
				prc.communicate(input=str(text).encode('utf-8'))
		return False
	def _paste(_=None):
		"""osx paste function"""
		with Popen(['pbpaste'], stdout=PIPE, close_fds=True) as prc:
			out, _ = prc.communicate()
			return out.decode('utf-8'), None
		return False
	return _copy, _paste

def linclips():
	"""linux clipboards"""
	xsel = ['xsel']
	def _copy(text, mode='p'):
		"""linux copy function"""
		_xsel = xsel + ['-i']
		text = text if text else ''
		for m in mode:
			with Popen(
				  _xsel + ['-%s'%m], stdin=PIPE, stderr=DEVNULL) as prc:
				prc.communicate(input=str(text).encode('utf-8'))
				prc.terminate()
	def _paste(mode='p'):
		"""linux paste function"""
		_xsel = xsel + ['-o']
		if mode == 'p':
			out, _ = Popen(
				_xsel + ['-p'], stdout=PIPE, stderr=DEVNULL).communicate()
			ret = out.decode()
		elif mode == 'b':
			out, _ = Popen(
				_xsel + ['-b'], stdout=PIPE, stderr=DEVNULL).communicate()
			ret = out.decode()
		else:
			pout, _ = Popen(
				_xsel + ['-p'], stdout=PIPE, stderr=DEVNULL).communicate()
			bout, _ = Popen(
				_xsel + ['-b'], stdout=PIPE, stderr=DEVNULL).communicate()
			ret = pout.decode(), bout.decode()
		return ret
	return _copy, _paste

def clips():
	"""return `copy`, `paste` as system independent functions"""
	if osname == 'nt' or system() == 'Windows':
		return winclips()
	elif osname == 'mac' or system() == 'Darwin':
		return osxclips()
	return linclips()

copy, paste = clips()
