
import bpy

import bpy_ops # XXX - should not need to do this
del bpy_ops

class CONSOLE_HT_header(bpy.types.Header):
	__space_type__ = 'CONSOLE'

	def draw(self, context):
		sc = context.space_data
		# text = sc.text
		layout = self.layout

		row= layout.row(align=True)
		row.template_header()

		if context.area.show_menus:
			sub = row.row(align=True)

			if sc.console_type == 'REPORT':
				sub.itemM("CONSOLE_MT_report")
			else:
				sub.itemM("CONSOLE_MT_console")

		layout.itemS()
		layout.itemR(sc, "console_type", expand=True)

		if sc.console_type == 'REPORT':
			row = layout.row(align=True)
			row.itemR(sc, "show_report_debug", text="Debug")
			row.itemR(sc, "show_report_info", text="Info")
			row.itemR(sc, "show_report_operator", text="Operators")
			row.itemR(sc, "show_report_warn", text="Warnings")
			row.itemR(sc, "show_report_error", text="Errors")
			
			row = layout.row()
			row.enabled = sc.show_report_operator
			row.itemO("console.report_replay")

class CONSOLE_MT_console(bpy.types.Menu):
	__space_type__ = 'CONSOLE'
	__label__ = "Console"

	def draw(self, context):
		layout = self.layout
		sc = context.space_data

		layout.column()
		layout.itemO("console.clear")
		layout.itemO("console.copy")
		layout.itemO("console.paste")

class CONSOLE_MT_report(bpy.types.Menu):
	__space_type__ = 'CONSOLE'
	__label__ = "Report"

	def draw(self, context):
		layout = self.layout
		sc = context.space_data

		layout.column()
		layout.itemO("console.select_all_toggle")
		layout.itemO("console.select_border")
		layout.itemO("console.report_delete")
		layout.itemO("console.report_copy")

def add_scrollback(text, text_type):
	for l in text.split('\n'):
		bpy.ops.console.scrollback_append(text=l.replace('\t', '    '), type=text_type)

def get_console(console_id):
	'''
	helper function for console operators
	currently each text datablock gets its own console - code.InteractiveConsole()
	...which is stored in this function.
	
	console_id can be any hashable type
	'''
	import sys, code
	
	try:	consoles = get_console.consoles
	except:consoles = get_console.consoles = {}
	
	# clear all dead consoles, use text names as IDs
	# TODO, find a way to clear IDs
	'''
	for console_id in list(consoles.keys()):
		if console_id not in bpy.data.texts:
			del consoles[id]
	'''
	
	try:
		namespace, console, stdout, stderr = consoles[console_id]
	except:
		namespace = {'__builtins__':__builtins__} # locals()
		namespace['bpy'] = bpy
		
		console = code.InteractiveConsole(namespace)
		
		import io
		stdout = io.StringIO()
		stderr = io.StringIO()
	
		consoles[console_id]= namespace, console, stdout, stderr
		
	return namespace, console, stdout, stderr

class CONSOLE_OT_exec(bpy.types.Operator):
	'''
	Operator documentatuon text, will be used for the operator tooltip and python docs.
	'''
	__idname__ = "console.exec"
	__label__ = "Console Execute"
	__register__ = False
	
	# Both prompts must be the same length
	PROMPT = '>>> ' 
	PROMPT_MULTI = '... '
	
	# is this working???
	'''
	def poll(self, context):
		return (context.space_data.type == 'PYTHON')
	''' # its not :|
	
	def execute(self, context):
		import sys
		
		sc = context.space_data
		
		try:
			line = sc.history[-1].line
		except:
			return ('CANCELLED',)
		
		if sc.console_type != 'PYTHON':
			return ('CANCELLED',)
		
		namespace, console, stdout, stderr = get_console(hash(context.region))
		
		# redirect output
		sys.stdout = stdout
		sys.stderr = stderr
		
		# run the console
		if not line.strip():
			line_exec = '\n' # executes a multiline statement
		else:
			line_exec = line
		
		is_multiline = console.push(line_exec)
		
		stdout.seek(0)
		stderr.seek(0)
		
		output = stdout.read()
		output_err = stderr.read()
	
		# cleanup
		sys.stdout = sys.__stdout__
		sys.stderr = sys.__stderr__
		sys.last_traceback = None
		
		# So we can reuse, clear all data
		stdout.truncate(0)
		stderr.truncate(0)
		
		bpy.ops.console.scrollback_append(text = sc.prompt+line, type='INPUT')
		
		if is_multiline:	sc.prompt = self.PROMPT_MULTI
		else:				sc.prompt = self.PROMPT
		
		# insert a new blank line
		bpy.ops.console.history_append(text="", current_character=0, remove_duplicates= True)
		
		# Insert the output into the editor
		# not quite correct because the order might have changed, but ok 99% of the time.
		if output:			add_scrollback(output, 'OUTPUT')
		if output_err:		add_scrollback(output_err, 'ERROR')
		
		
		return ('FINISHED',)


def autocomp(bcon):
	'''
	This function has been taken from a BGE console autocomp I wrote a while ago
	the dictionaty bcon is not needed but it means I can copy and paste from the old func
	which works ok for now.
	
	could be moved into its own module.
	'''
	
	
	def is_delimiter(ch):
		'''
		For skipping words
		'''
		if ch == '_':
			return False
		if ch.isalnum():
			return False
		
		return True
	
	def is_delimiter_autocomp(ch):
		'''
		When autocompleteing will earch back and 
		'''
		if ch in '._[] "\'':
			return False
		if ch.isalnum():
			return False
		
		return True

	
	def do_autocomp(autocomp_prefix, autocomp_members):
		'''
		return text to insert and a list of options
		'''
		autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
		
		print("AUTO: '%s'" % autocomp_prefix)
		print("MEMBERS: '%s'" % str(autocomp_members))
		
		if not autocomp_prefix:
			return '', autocomp_members
		elif len(autocomp_members) > 1:
			# find a common string between all members after the prefix 
			# 'ge' [getA, getB, getC] --> 'get'
			
			# get the shortest member
			min_len = min([len(v) for v in autocomp_members])
			
			autocomp_prefix_ret = ''
			
			for i in range(len(autocomp_prefix), min_len):
				char_soup = set()
				for v in autocomp_members:
					char_soup.add(v[i])
				
				if len(char_soup) > 1:
					break
				else:
					autocomp_prefix_ret += char_soup.pop()
				
			print(autocomp_prefix_ret)
			return autocomp_prefix_ret, autocomp_members
		elif len(autocomp_members) == 1:
			return autocomp_members[0][len(autocomp_prefix):], []
		else:
			return '', []
	

	def BCon_PrevChar(bcon):
		cursor = bcon['cursor']-1
		if cursor<0:
			return None
			
		try:
			return bcon['edit_text'][cursor]
		except:
			return None
		
		
	def BCon_NextChar(bcon):
		try:
			return bcon['edit_text'][bcon['cursor']]
		except:
			return None
	
	def BCon_cursorLeft(bcon):
		bcon['cursor'] -= 1
		if bcon['cursor'] < 0:
			bcon['cursor'] = 0

	def BCon_cursorRight(bcon):
			bcon['cursor'] += 1
			if bcon['cursor'] > len(bcon['edit_text']):
				bcon['cursor'] = len(bcon['edit_text'])
	
	def BCon_AddScrollback(bcon, text):
		
		bcon['scrollback'] = bcon['scrollback'] + text
		
	
	def BCon_cursorInsertChar(bcon, ch):
		if bcon['cursor']==0:
			bcon['edit_text'] = ch + bcon['edit_text']
		elif bcon['cursor']==len(bcon['edit_text']):
			bcon['edit_text'] = bcon['edit_text'] + ch
		else:
			bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
			
		bcon['cursor'] 
		if bcon['cursor'] > len(bcon['edit_text']):
			bcon['cursor'] = len(bcon['edit_text'])
		BCon_cursorRight(bcon)
	
	
	TEMP_NAME = '___tempname___'
	
	cursor_orig = bcon['cursor']
	
	ch = BCon_PrevChar(bcon)
	while ch != None and (not is_delimiter(ch)):
		ch = BCon_PrevChar(bcon)
		BCon_cursorLeft(bcon)
	
	if ch != None:
		BCon_cursorRight(bcon)
	
	#print (cursor_orig, bcon['cursor'])
	
	cursor_base = bcon['cursor']
	
	autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
	
	print("PREFIX:'%s'" % autocomp_prefix)
	
	# Get the previous word
	if BCon_PrevChar(bcon)=='.':
		BCon_cursorLeft(bcon)
		ch = BCon_PrevChar(bcon)
		while ch != None and is_delimiter_autocomp(ch)==False:
			ch = BCon_PrevChar(bcon)
			BCon_cursorLeft(bcon)
		
		cursor_new = bcon['cursor']
		
		if ch != None:
			cursor_new+=1
		
		pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
		print("AUTOCOMP EVAL: '%s'" % pytxt)
		#try:
		if pytxt:
			bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
			# print val
		else: ##except:
			val = None
		
		try:
			val = bcon['namespace'][TEMP_NAME]
			del bcon['namespace'][TEMP_NAME]
		except:
			val = None
		
		if val:
			autocomp_members = dir(val)
			
			autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
			
			bcon['cursor'] = cursor_orig
			for v in autocomp_prefix_ret:
				BCon_cursorInsertChar(bcon, v)
			cursor_orig = bcon['cursor']
			
			if autocomp_members:
				BCon_AddScrollback(bcon, ', '.join(autocomp_members))
		
		del val
		
	else:
		# Autocomp global namespace
		autocomp_members = bcon['namespace'].keys()
		
		if autocomp_prefix:
			autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
		
		autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
		
		bcon['cursor'] = cursor_orig
		for v in autocomp_prefix_ret:
			BCon_cursorInsertChar(bcon, v)
		cursor_orig = bcon['cursor']
		
		if autocomp_members:
			BCon_AddScrollback(bcon, ', '.join(autocomp_members))
	
	bcon['cursor'] = cursor_orig


class CONSOLE_OT_autocomplete(bpy.types.Operator):
	'''
	Operator documentatuon text, will be used for the operator tooltip and python docs.
	'''
	__idname__ = "console.autocomplete"
	__label__ = "Console Autocomplete"
	__register__ = False
	
	def poll(self, context):
		return context.space_data.type == 'PYTHON'
	
	def execute(self, context):
		
		sc = context.space_data
		
		namespace, console, stdout, stderr = get_console(hash(context.region))
		
		current_line = sc.history[-1]
		line = current_line.line
		
		if not console:
			return ('CANCELLED',)
		
		if sc.console_type != 'PYTHON':
			return ('CANCELLED',)
		
		# fake cursor, use for autocomp func.
		bcon = {}
		bcon['cursor'] = current_line.current_character
		bcon['console'] = console
		bcon['edit_text'] = line
		bcon['namespace'] = namespace
		bcon['scrollback'] = '' # nor from the BGE console
		
		
		# This function isnt aware of the text editor or being an operator
		# just does the autocomp then copy its results back
		autocomp(bcon)
		
		# Now we need to copy back the line from blender back into the text editor.
		# This will change when we dont use the text editor anymore
		if bcon['scrollback']:
			add_scrollback(bcon['scrollback'], 'INFO')
		
		# copy back
		current_line.line = bcon['edit_text']
		current_line.current_character = bcon['cursor']
		
		context.area.tag_redraw()
		
		return ('FINISHED',)



bpy.types.register(CONSOLE_HT_header)
bpy.types.register(CONSOLE_MT_console)
bpy.types.register(CONSOLE_MT_report)

bpy.ops.add(CONSOLE_OT_exec)
bpy.ops.add(CONSOLE_OT_autocomplete)

