import threading
import time
import sys
import os
import ctypes
import collections

sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from _CloseableQueue import CloseableQueueFactory
from _utils import eprint

CloseableQueue = CloseableQueueFactory()

class OrderedDict(collections.OrderedDict):
	def __init__(self, **kwargs):
		collections.OrderedDict.__init__(self, **kwargs)

	def __setitem__(self, name, value):
		if isinstance(name, int):
			collections.OrderedDict.__setitem__(self, list(self.keys())[name], value)
		else:
			collections.OrderedDict.__setitem__(self, name, value)

	def __getitem__(self, name):
		if isinstance(name, int):
			return collections.OrderedDict.__getitem__(self, list(self.keys())[name])
		else:
			return collections.OrderedDict.__getitem__(self, name)

	def __delitem__(self, name):
		if isinstance(name, int):
			collections.OrderedDict.__delitem__(self, list(self.keys())[name])
		else:
			collections.OrderedDict.__delitem__(self, name)

class QueueDict(dict):
	def __init__(self):
		dict.__init__(self)

	def __getitem__(self, name):
		if not dict.__contains__(self, name):
			dict.__setitem__(self, name, CloseableQueue())
		return dict.__getitem__(self, name)

	def clear(self):
		for name in self:
			try:
				self[name].close()
			except:
				pass
		dict.clear(self)

class Pipe:
	def __init__(self, node, name):
		self._node = node
		self._name = name

	def send(self, value):
		self._node._locals_pipe_send(self._name, value)

	def recv(self):
		return self._node._locals_pipe_recv(self._name)

	def close(self):
		return self._node._locals_pipe_close(self._name)

class Pipes:
	def __init__(self, node):
		self._node = node

	def __getitem__(self, name):
		return Pipe(self._node, name)

	def __delitem__(self, name):
		self._node._locals_pipes_delitem(name)

	def __len__(self):
		return self._node._locals_pipes_len()

	def __contains__(self, name):
		return self._node._locals_pipes_contains(name)

	def __iter__(self):
		return self._node._locals_pipes_iter()

	def keys(self):
		return self._node._locals_pipes_keys()

	def values(self):
		values = []
		for key in self.keys():
			values.append(self[key])

		return values

	def items(self):
		items = []
		for key in self.keys():
			items.append((key, self[key]))

		return items

	def clear(self):
		self._node._locals_pipes_clear()
	
class Queue:
	def __init__(self, node, name, private=False):
		self._node = node
		self._name = name
		self._is_private = private

	def get(self, timeout = None, block = True):
		return self._node._locals_queue_get(self._name, timeout=timeout, block=block, is_private=self._is_private)

	def put(self, value, timeout = None, block = True):
		self._node._locals_queue_put(self._name, value, timeout=timeout, block=block, is_private=self._is_private)

	def close(self):
		self._node._locals_queue_close(self._name, is_private=self._is_private)

	def qsize(self):
		return self._node._locals_queue_len(self._name, is_private=self._is_private)

	def __len__(self):
		return self._node._locals_queue_len(self._name, is_private=self._is_private)

class Queues:
	def __init__(self, node, private=False):
		self._node = node
		self._is_private = private

	def __getitem__(self, name):
		return Queue(self._node, name, private=self._is_private)

	def __delitem__(self, name):
		self._node._locals_queues_delitem(name, is_private=self._is_private)

	def __len__(self):
		return self._node._locals_queues_len(is_private=self._is_private)

	def __contains__(self, name):
		return self._node._locals_queues_contains(name, is_private=self._is_private)

	def keys(self):
		return self._node._locals_queues_keys(is_private=self._is_private)

	def values(self):
		values = []
		for key in self.keys():
			values.append(self[key])

		return values

	def items(self):
		items = []
		for key in self.keys():
			items.append((key, self[key]))
		return items

	def __iter__(self):
		return self._node._locals_queues_iter(is_private=self._is_private)

	def clear(self):
		self._node._locals_queues_clear(is_private=self._is_private)

class Globals:
	class Queue:
		def __init__(self, node, name):
			self._node = node
			self._name = name

		def get(self, timeout = None, block = True):
			return self._node._globals_queue_get(self._name, timeout=timeout, block=block)

		def put(self, value, timeout = None, block = True):
			self._node._globals_queue_put(self._name, value, timeout=timeout, block=block)

		def close(self):
			self._node._globals_queue_close(self._name)

		def qsize(self):
			return self._node._globals_queue_len(self._name)

		def __len__(self):
			return self._node._globals_queue_len(self._name)

	class Queues:
		def __init__(self, node):
			self._node = node

		def __getitem__(self, name):
			return Globals.Queue(self._node, name)

		def __delitem__(self, name):
			self._node._globals_queues_delitem(name)

		def __len__(self):
			return self._node._globals_queues_len()

		def __contains__(self, name):
			return self._node._globals_queues_contains(name)

		def keys(self):
			return self._node._globals_queues_keys()

		def values(self):
			values = []
			for key in self.keys():
				values.append(self[key])
			return values

		def items(self):
			items = []
			for key in self.keys():
				items.append((key, self[key]))
			return items

		def __iter__(self):
			return self._node._globals_queues_iter()

		def clear(self):
			self._node._globals_queues_clear()

	def __init__(self, node):
		self._node = node
		self._queues = Globals.Queues(node)

	def __setitem__(self, name, value):
		self._node._globals_setitem(name, value)

	def __getitem__(self, name):
		return self._node._globals_getitem(name)

	def __delitem__(self, name):
		return self._node._globals_delitem(name)

	def __iter__(self):
		return self._node._globals_iter()

	def __contains__(self, name):
		return self._node._globals_contains(name)

	def __len__(self):
		return self._node._globals_len()

	def clear(self):
		self._node._globals_clear()

	def keys(self):
		return self._node._globals_keys()

	def values(self):
		return self._node._globals_values()

	def items(self):
		return self._node._globals_items()

	def pop(self, name):
		return self._node._globals_pop(name)

	def get(self, timeout=None, block=True):
		return self._node._globals_get(timeout=timeout, block=block)

	def put(self, value, timeout=None, block=True):
		self._node._globals_put(value, timeout=timeout, block=block)

	def qsize(self):
		return self._node._globals_qsize()

	@property
	def queues(self):
		return self._queues

class AutoSendBuffer:
	def __init__(self, node, session_id, timeout=0.1, quiet=False):
		self._node = node
		self._session_id = session_id
		self._buffer_lock = threading.Lock()
		self._buffer = b''
		self._quiet = quiet
		self._timeout = timeout
		self._last_send_time = time.time()
		self._continue_send = True
		self._sending_thread = threading.Thread(target=self._timeout_send)
		self._sending_thread.start()

	def __del__(self):
		try:
			self.stop()
		except:
			pass

	def append(self, byte):
		with self._buffer_lock:
			self._buffer += byte

		if byte == b"\n" or len(self._buffer) >= 512:
			self.send()

	def send(self):
		with self._buffer_lock:
			if len(self._buffer) != 0:
				content = self._buffer.decode("utf-8")
				if not self._quiet:
					print(content, end="", flush=True)
				self._node._respond_ok(self._session_id, end=False, stdout=content)
				self._buffer = b''
				self._last_send_time = time.time()

	def stop(self):
		self._continue_send = False
		self._sending_thread.join()
		self.send()

	def _timeout_send(self):
		while self._continue_send:
			if time.time()-self._last_send_time >= self._timeout:
				self.send()
			time.sleep(1E-3)

class Thread(threading.Thread): 
	def __init__(self, target, args=(), kwargs={}): 
		threading.Thread.__init__(self, target=target, args=args, kwargs=kwargs) 
		
	def __get_id(self): 
		if hasattr(self, '_thread_id'): 
			return self._thread_id 
		for id, thread in threading._active.items(): 
			if thread is self: 
				return id

	def kill(self):
		thread_id = self.__get_id()
		res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
		if res > 1:
			ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
			print('Exception raise failure')

class Future:
	def __init__(self, node, session_id):
		self._node = node
		self._session_id = session_id
		self._cancelled = False
		self._done = None
		self._result = None
		self._monitor_done_thread = None
		self._done_callbacks = []

	def cancel(self):
		if self.done():
			return False

		self._node._stop_send = True
		self._node._send_signal(self._session_id, cancel=True)
		self._result = self._node._result_queues_for_future[self._session_id].get()
		self._cancelled = True
		self._done = True
		return True

	def cancelled(self):
		return self._cancelled

	def done(self):
		if self._done != None:
			return self._done
		else:
			return (len(self._node._result_queues_for_future[self._session_id]) != 0)

	def result(self):
		if self._result == None:
			self._result = self._node._result_queues_for_future[self._session_id].get()

		self._done = True
		if self._result["success"]:
			return self._result["return_value"]
		else:
			eprint(self._result["traceback"])
			raise self._result["exception"]

	def exception(self):
		if self._result == None:
			self._result = self._node._result_queues_for_future[self._session_id].get()

		self._done = True
		return self._result["exception"]

	def _get_result(self):
		if self._result == None:
			self._result = self._node._result_queues_for_future[self._session_id].get()

		for func in self._done_callbacks:
			func(self)

	def add_done_callback(self, func):
		self._done_callbacks.append(func)
		
		if self._monitor_done_thread == None:
			self._monitor_done_thread = threading.Thread(target=self._get_result)
			self._monitor_done_thread.start()

	def remove_done_callback(self, func):
		try:
			self._done_callbacks.remove(func)
		except:
			pass