
from __future__ import print_function # PY2

import sys
import traceback
import warnings
import ctypes.util
from ctypes import (pythonapi, cdll, cast, 
	c_char_p, c_void_p, c_size_t, CFUNCTYPE)

from .info import WINDOWS

try:
	import pyreadline
except ImportError:
	pyreadline = None


def get_libc():
	if WINDOWS:
		path = "msvcrt"
	else:
		path = ctypes.util.find_library("c")
		if path is None:
			raise RuntimeError("cannot locate libc")
	
	return cdll[path]

LIBC = get_libc()

PyMem_Malloc = pythonapi.PyMem_Malloc
PyMem_Malloc.restype = c_size_t
PyMem_Malloc.argtypes = [c_size_t]

strncpy = LIBC.strncpy
strncpy.restype = c_char_p
strncpy.argtypes = [c_char_p, c_char_p, c_size_t]

HOOKFUNC = CFUNCTYPE(c_char_p, c_void_p, c_void_p, c_char_p)

PyOS_ReadlineFunctionPointer = c_void_p.in_dll(pythonapi, "PyOS_ReadlineFunctionPointer")


def new_zero_terminated_string(b):
	p = PyMem_Malloc(len(b) + 1)
	strncpy(cast(p, c_char_p), b, len(b) + 1)
	return p

def check_encodings():
	if sys.stdin.encoding != sys.stdout.encoding:
		# raise RuntimeError("sys.stdin.encoding != sys.stdout.encoding, readline hook doesn't know, which one to use to decode prompt")
		
		warnings.warn("sys.stdin.encoding == {!r}, whereas sys.stdout.encoding == {!r}, readline hook consumer may assume they are the same".format(sys.stdin.encoding, sys.stdout.encoding), 
			RuntimeWarning, stacklevel=3)

def stdio_readline(prompt=""):
	sys.stdout.write(prompt)
	sys.stdout.flush()
	return sys.stdin.readline()


class ReadlineHookManager:
	def __init__(self):
		self.readline_wrapper_ref = HOOKFUNC(self.readline_wrapper)
		self.address = cast(self.readline_wrapper_ref, c_void_p).value
		self.original_address = PyOS_ReadlineFunctionPointer.value
		self.readline_hook = None
	
	def readline_wrapper(self, stdin, stdout, prompt):
		try:
			try:
				check_encodings()
			except RuntimeError:
				traceback.print_exc(file=sys.stderr)
				try:
					prompt = prompt.decode("utf-8")
				except UnicodeDecodeError:
					prompt = ""
				
			else:
				prompt = prompt.decode(sys.stdout.encoding)
			
			try:
				line = self.readline_hook(prompt)
			except KeyboardInterrupt:
				return 0
			else:
				return new_zero_terminated_string(line.encode(sys.stdin.encoding))
			
		except:
			self.restore_original()
			print("Internal win_unicode_console error, disabling custom readline hook...", file=sys.stderr)
			traceback.print_exc(file=sys.stderr)
			return new_zero_terminated_string(b"\n")
	
	def install_hook(self, hook):
		self.readline_hook = hook
		PyOS_ReadlineFunctionPointer.value = self.address
	
	def restore_original(self):
		self.readline_hook = None
		PyOS_ReadlineFunctionPointer.value = self.original_address


class PyReadlineManager:
	def __init__(self):
		self.original_codepage = pyreadline.unicode_helper.pyreadline_codepage
	
	def set_codepage(self, codepage):
		pyreadline.unicode_helper.pyreadline_codepage = codepage
	
	def restore_original(self):
		self.set_codepage(self.original_codepage)

def pyreadline_is_active():
	if not pyreadline:
		return False
	
	ref = pyreadline.console.console.readline_ref
	if ref is None:
		return False
	
	return cast(ref, c_void_p).value == PyOS_ReadlineFunctionPointer.value


manager = ReadlineHookManager()

if pyreadline:
	pyreadline_manager = PyReadlineManager()


# PY3 # def enable(*, use_pyreadline=True):
def enable(use_pyreadline=True):
	check_encodings()
	
	if use_pyreadline and pyreadline:
		pyreadline_manager.set_codepage(sys.stdin.encoding)
			# pyreadline assumes that encoding of all sys.stdio objects is the same
		if not pyreadline_is_active():
			manager.install_hook(stdio_readline)
		
	else:
		manager.install_hook(stdio_readline)

def disable():
	if pyreadline:
		pyreadline_manager.restore_original()
	else:
		manager.restore_original()
