import json
import weakref
import uuid
import importlib
from time import time as getTime

from candy_editor.core import *
from candy_editor.core.selection import getSelectionManager
# from candy_editor.moai.MOAIRuntime import MOAIException
from candy_editor.qt.dialogs import alertMessage
from AKU import _LuaTable, _LuaObject, LuaError


def wrapLuaCaller ( func ):
	def caller ( *args ):
		try:
			return func ( *args )
		except Exception as e:
			logging.error ( str ( e ) )
		return None

	return caller


####################################
# COMMON COMMUNICATION FUNCTIONS
####################################
_luaSignalConnections = []
_luaSignalRegistration = []


def emitPythonSignal ( name, *args ):
	signals.emit ( name, *args )


def emitPythonSignalNow ( name, *args ):
	signals.emitNow ( name, *args )


def connectPythonSignal ( name, func ):
	caller = wrapLuaCaller ( func )
	signals.connect ( name, caller )
	sig = signals.affirm ( name )
	_luaSignalConnections.append ( (sig, caller) )
	print ( "connectPythonSignal: %s %s" % ( name, caller ) )


def clearSignalConnections ():
	global _luaSignalConnections
	for m in _luaSignalConnections:
		( sig, handler ) = m
		sig.disconnect ( handler )
	_luaSignalConnections = []


def registerPythonSignal ( name ):
	signals.register ( name )
	_luaSignalRegistration.append ( name )


def clearLuaRegisteredSignals ():
	global _luaSignalRegistration
	for name in _luaSignalRegistration:
		signals.unregister ( name )


def throwPythonException ( name, data = None ):
	raise MOAIException ( name )

def showAlertMessage ( title, msg ):
	alertMessage ( title, msg )

def pyLogInfo ( msg ):
	logging.info ( msg )

def pyLogWarn ( msg ):
	logging.warning ( msg )

def pyLogError ( msg ):
	logging.error ( msg )


##----------------------------------------------------------------##
def importModule ( id ):
	return importlib.import_module ( id )


####################################
# GUI BRIDGE
####################################
# todo
def GUIYield ():
	app.doMainLoop ()


def getSelection ( key ):
	selectionManager = getSelectionManager ( key )
	s = selectionManager.getSelection ()
	return s


def changeSelection ( key, targets = None ):
	selectionManager = getSelectionManager ( key )
	selectionManager.changeSelection ( targets )


def addSelection ( key, targets = None ):
	selectionManager = getSelectionManager ( key )
	selectionManager.addSelection ( targets )


def toggleSelection ( key, targets = None ):
	selectionManager = getSelectionManager ( key )
	selectionManager.toggleSelection ( targets )


def removeSelection ( key, targets = None ):
	selectionManager = getSelectionManager ( key )
	selectionManager.removeSelection ( targets )


####################################
# COMMON DATA BRIDGE
####################################
def newDict ():
	return {}

def getDict ( d, key, default = None ):
	return d.get ( key, default )

def setDict ( d, key, value ):
	d[ key ] = value

def decodeDict ( data ):
	return json.loads ( data )

def encodeDict ( dict ):
	return json.dumps ( dict ).encode ( 'utf-8' )

def luaTableToDict ( luat, deepCopy = False ):  # no deep conversion
	assert isinstance ( luat, _LuaTable )
	res = {}
	if deepCopy:
		for k in luat:
			v = luat[ k ]
			if isinstance ( v, _LuaTable ):
				v = luaTableToDict ( v, deepCopy )
			res[ k ] = v
	else:
		for k in luat:
			res[ k ] = luat[ k ]
	return res

def newPythonList ( *arg ):
	return list ( arg )

def newPythonDict ():
	return {}

def appendPythonList ( list, data ):
	list.append ( data )

def deletePythonList ( list, i ):
	del list[ i ]

def sizeOfPythonObject ( list ):
	return len ( list )

def tupleToList ( t ):
	return list ( t )


####################################
# MODEL BRIDGE
####################################
class LuaObjectModelProvider ( ModelProvider ):

	def __init__ ( self, name, priority, getTypeId, getModel, getModelFromTypeId ):
		self.name = name
		self.priority = priority
		self._getTypeId = getTypeId
		self._getModel = getModel
		self._getModelFromTypeId = getModelFromTypeId

	def getPriority ( self ):
		return self.priority

	def getTypeId ( self, obj ):
		if isinstance ( obj, _LuaObject ):
			return self._getTypeId ( obj )
		else:
			return None

	def getModel ( self, obj ):
		if isinstance ( obj, _LuaObject ):
			return self._getModel ( obj )
		else:
			return None

	def getModelFromTypeId ( self, typeId ):
		return self._getModelFromTypeId ( typeId )

	def getPriority ( self ):
		return 1

	def clear ( self ):
		pass


##----------------------------------------------------------------##
class LuaObjectEnumerator ( ObjectEnumerator ):

	def __init__ ( self, name, enumerateObject, getObjectRepr, getObjectTypeRepr ):
		self.name = name
		self._enumerateObject = enumerateObject
		self._getObjectTypeRepr = getObjectTypeRepr
		self._getObjectRepr = getObjectRepr

	def getName ( self ):
		return self.name

	def enumerateObjects ( self, typeId, context, option ):
		result = self._enumerateObject ( self, typeId, context, option )
		if not result: return None
		return [ obj for obj in result.values () ]

	def getObjectRepr ( self, obj ):
		return self._getObjectRepr ( self, obj )

	def getObjectTypeRepr ( self, obj ):
		return self._getObjectTypeRepr ( self, obj )


##----------------------------------------------------------------##
class LuaObjectField ( Field ):

	def __init__ ( self, model, id, _type, **option ):
		super ( LuaObjectField, self ).__init__ ( model, id, _type, **option )
		# init getter/setter
		if self.getter == False:
			self.getValue = self._getValueNone
		elif self.getter == True:
			self.getValue = self._getValueRaw
		else:
			self.getValue = self._getValueGetter

		if self.readonly:
			self.setValue = self._setValueNone
		elif self.setter == True:
			self.setValue = self._setValueRaw
		else:
			self.setValue = self._setValueSetter

	def _getValueNone ( self, obj, defaultValue = None ):
		return None

	def _getValueRaw ( self, obj, defaultValue = None ):
		return getattr ( obj, self.id, defaultValue )

	def _getValueGetter ( self, obj, defaultValue = None ):
		# caller
		v = self.getter ( obj, self.id )
		if v is None: return defaultValue
		return v

	def _setValueNone ( self, obj, value ):
		pass

	def _setValueRaw ( self, obj, value ):
		setattr ( obj, self.id, value )

	def _setValueSetter ( self, obj, value ):
		self.setter ( obj, value )


##----------------------------------------------------------------##
class LuaObjectModel ( ObjectModel ):
	_EnumCache = weakref.WeakValueDictionary ()

	# _AssetTypeCache = {}

	def createField ( self, id, t, **option ):
		return LuaObjectField ( self, id, t, **option )

	def isFieldOverrided ( self, obj, id ):
		overridedFields = obj[ '__overrided_fields' ]
		if not overridedFields: return False
		if overridedFields[ id ]: return True
		return False

	def addLuaFieldInfo ( self, name, typeId, data = None ):  # called by lua
		# convert lua-typeId -> pythontype
		typeId = luaTypeToPyType ( typeId )
		setting = data and luaTableToDict ( data ) or {}
		meta = setting.get ( 'meta', None )
		if meta:
			del setting[ 'meta' ]
			for k, v in meta.items ():
				if k not in setting:
					setting[ k ] = v
		return self.addFieldInfo ( name, typeId, **setting )

	def addLuaEnumFieldInfo ( self, name, enumItems, data = None ):  # called by lua
		enumType = LuaObjectModel._EnumCache.get ( enumItems, None )
		if not enumType:
			tuples = []
			for item in enumItems.values ():
				itemName = item[ 1 ]
				itemValue = item[ 2 ]
				tuples.append ( (itemName, itemValue) )
			enumType = EnumType ( '_LUAENUM_', tuples )
			LuaObjectModel._EnumCache[ enumItems ] = enumType
		return self.addLuaFieldInfo ( name, enumType, data )

	def addLuaAssetFieldInfo ( self, name, assetType, data = None ):  # called by lua
		# typeId = LuaObjectModel._AssetTypeCache.get( assetType )
		# if not typeId:
		typeId = AssetRefType ( assetType )
		# LuaObjectModel._AssetTypeCache[ assetType ] = typeId
		return self.addLuaFieldInfo ( name, typeId, data )

	def addLuaActionFieldInfo ( self, name, actionName, data = None ):  # called by lua
		actionType = ActionType ( name, actionName )
		return self.addLuaFieldInfo ( name, actionType, data )

	def addLuaCollectionFieldInfo ( self, name, itemType, data = None ):
		collectionType = CollectionType ( name, itemType )
		return self.addLuaFieldInfo ( name, collectionType, data )

	def serialize ( self, obj, objMap = None ):
		raise Exception ( 'Serializing Lua object in python is not supported, yet' )

	def deserialize ( self, obj, data, objMap = None ):
		raise Exception ( 'Deserializing Lua object in python is not supported, yet' )


##----------------------------------------------------------------##
def luaTypeToPyType ( tname ):
	if tname == 'int':
		return int
	elif tname == 'string':
		return str
	elif tname == 'number':
		return float
	elif tname == 'boolean':
		return bool
	elif tname == 'nil':
		return None
	return tname  # no conversion


##----------------------------------------------------------------##
class ModelBridge ( object ):
	_singleton = None

	@staticmethod
	def get ():
		return ModelBridge._singleton

	def __init__ ( self ):
		assert (not ModelBridge._singleton)
		ModelBridge._singleton = self
		self.modelProviders = []
		self.enumerators = []
		self.registeredTypes = {}
		signals.connect ( 'moai.clean', self.cleanLuaBridgeReference )

	def newLuaObjectMoel ( self, name ):
		return LuaObjectModel ( name )

	def buildLuaObjectModelProvider ( self, name, priority, getTypeId, getModel, getModelFromTypeId ):
		provider = LuaObjectModelProvider ( name, priority, getTypeId, getModel, getModelFromTypeId )
		ModelManager.get ().RegisterModelProvider ( provider )
		self.modelProviders.append ( provider )
		return provider

	def buildLuaObjectEnumerator ( self, name, enumerateObjects, getObjectRepr, getObjectTypeRepr ):
		enumerator = LuaObjectEnumerator ( name, enumerateObjects, getObjectRepr, getObjectTypeRepr )
		ModelManager.get ().registerObjectEnumerator ( enumerator )
		self.enumerators.append ( enumerator )
		return enumerator

	def getTypeId ( self, obj ):
		return ModelManager.get ().getTypeId ( obj )

	def cleanLuaBridgeReference ( self ):
		# clean type getter
		for provider in self.modelProviders:
			logging.info ( 'unregister provider:%s' % repr ( provider ) )
			provider.clear ()
			ModelManager.get ().unregisterModelProvider ( provider )

		for enumerator in self.enumerators:
			logging.info ( 'unregister enumerator:%s' % repr ( enumerator ) )
			ModelManager.get ().unregisterObjectEnumerator ( enumerator )

		self.modelProviders = []
		self.enumerators = []

ModelBridge ()


##----------------------------------------------------------------##
class SafeDict ( object ):

	def __init__ ( self, dict ):
		self.__dict = dict

	def __setitem__ ( self, key, value ):
		self.__dict[ key ] = value

	def __getitem__ ( self, key ):
		return self.__dict.get ( key, None )

	def __iter__ ( self ):
		return self.__dict

	def values ( self ):
		return self.__dict.values ()


def registerLuaEditorCommand ( fullname, cmdCreator ):

	class LuaEditorCommand ( EditorCommand ):
		name = fullname

		def __init__ ( self ):
			self.luaCmd = cmdCreator ()

		def init ( self, **kwargs ):
			cmd = self.luaCmd
			try:
				return cmd.init ( cmd, SafeDict ( kwargs ) )
			except LuaError as err:
				print ( "LuaException: {0}".format ( err ) )
				return None

		def redo ( self ):
			cmd = self.luaCmd
			try:
				return cmd.redo ( cmd )
			except LuaError as err:
				print ( "LuaException: {0}".format ( err ) )
				return False

		def undo ( self ):
			cmd = self.luaCmd
			try:
				return cmd.undo ( cmd )
			except LuaError as err:
				print ( "LuaException: {0}".format ( err ) )
				return False

		def canUndo ( self ):
			cmd = self.luaCmd
			return cmd.canUndo ( cmd )

		def hasHistory ( self ):
			cmd = self.luaCmd
			return cmd.hasHistory ( cmd )

		def getResult ( self ):
			cmd = self.luaCmd
			return cmd.getResult ( cmd )

		def getLuaCommand ( self ):
			return self.luaCmd

		def __repr__ ( self ):
			cmd = self.luaCmd
			return cmd.toString ( cmd )

	return LuaEditorCommand


def doCommand ( cmdId, argTable ):
	pyArgTable = luaTableToDict ( argTable )
	return app.doCommand ( cmdId, **pyArgTable )


def undoCommand ( popOnly = False ):
	return app.undoCommand ( popOnly )


####
# EXTRA
####
def generateGUID ():
	# return str( uuid.uuid1() )
	return uuid.uuid1 ().hex
