import sublime, sublime_plugin
import re
import os
import threading
import coffee_utils
from coffee_utils import debug
from copy import copy

COFFEESCRIPT_AUTOCOMPLETE_STATUS_KEY = "coffee_autocomplete"
COFFEESCRIPT_AUTOCOMPLETE_STATUS_MESSAGE = "Coffee: Autocompleting \"%s\"..."

final_completions = []
status = {"working": False}

# TODO:
# - Type hinting using comments containing square brackets [Type] on same line or previous line
# - Codo docs searching for function parameter types
# - Better symbol parsing. Assignment lookups should consider the entire set of operands.
# X Consider all super classes (support extends)
# - Consider another feature: Override/implement methods
# - Full assignment traceback (that = this, a = b = c, knows what c is)
# - Check contents of currently open views
# - Built in types

class CoffeeAutocomplete(sublime_plugin.EventListener):

	def on_query_completions(self, view, prefix, locations):

		completions = copy(final_completions)
		working = status["working"]

		# If there is a word selection and we're looking at a coffee file...
		if not completions and coffee_utils.is_coffee_syntax(view) and not working:

			status["working"] = True

			current_location = locations[0]

			# Get the window
			self.window = sublime.active_window()

			# http://www.sublimetext.com/forum/viewtopic.php?f=6&t=9076
			settings = sublime.load_settings(coffee_utils.SETTINGS_FILE_NAME)

			built_in_types_settings = sublime.load_settings(coffee_utils.BUILT_IN_TYPES_SETTINGS_FILE_NAME)
			built_in_types = built_in_types_settings.get(coffee_utils.BUILT_IN_TYPES_SETTINGS_KEY)
			if not built_in_types:
				built_in_types = []

			custom_types_settings = sublime.load_settings(coffee_utils.CUSTOM_TYPES_SETTINGS_FILE_NAME)
			custom_types = custom_types_settings.get(coffee_utils.CUSTOM_TYPES_SETTINGS_KEY)
			if not custom_types:
				custom_types = []

			built_in_types.extend(custom_types)

			# Pull the excluded dirs from preferences
			excluded_dirs = settings.get(coffee_utils.PREFERENCES_COFFEE_EXCLUDED_DIRS)
			if not excluded_dirs:
				excluded_dirs = []

			restricted_to_dirs = settings.get(coffee_utils.PREFERENCES_COFFEE_RESTRICTED_TO_PATHS)
			if not restricted_to_dirs:
				restricted_to_dirs = []

			# List of all project folders
			project_folder_list = self.window.folders()

			if restricted_to_dirs:
				specific_project_folders = []
				for next_restricted_dir in restricted_to_dirs:
					for next_project_folder in project_folder_list:
						next_specific_folder = os.path.normpath(os.path.join(next_project_folder, next_restricted_dir))
						specific_project_folders.append(next_specific_folder)
				project_folder_list = specific_project_folders

			function_return_types = settings.get(coffee_utils.FUNCTION_RETURN_TYPES_SETTINGS_KEY)
			if not function_return_types:
				function_return_types = []

			this_aliases = settings.get(coffee_utils.PREFERENCES_THIS_ALIASES)
			if not this_aliases:
				this_aliases = []

			member_exclusion_regexes = settings.get(coffee_utils.PREFERENCES_MEMBER_EXCLUSION_REGEXES)
			if not member_exclusion_regexes:
				member_exclusion_regexes = []

			# Lines for the current file in view
			current_file_lines = coffee_utils.get_view_content_lines(view)

			# TODO: Smarter previous word selection
			preceding_symbol = coffee_utils.get_preceding_symbol(view, prefix, locations)
			immediately_preceding_symbol = coffee_utils.get_preceding_symbol(view, "", locations)

			preceding_function_call = coffee_utils.get_preceding_function_call(view).strip()

			# Determine preceding token, if any (if a period was typed).
			token = coffee_utils.get_preceding_token(view).strip()

			# TODO: Smarter region location
			symbol_region = sublime.Region(locations[0] - len(prefix), locations[0] - len(prefix))

			if (preceding_function_call or token or coffee_utils.THIS_SUGAR_SYMBOL == preceding_symbol) and coffee_utils.is_autocomplete_trigger(immediately_preceding_symbol):
				self.window.active_view().run_command('hide_auto_complete')

				thread = CoffeeAutocompleteThread(project_folder_list, excluded_dirs, this_aliases, current_file_lines, preceding_symbol, prefix, preceding_function_call, function_return_types, token, symbol_region, built_in_types, member_exclusion_regexes)
				thread.start()
				self.check_operation(thread, final_completions, current_location, token, status)
			else: 
				status["working"] = False

		elif completions:
			self.clear_completions(final_completions)

		return completions

	def check_operation(self, thread, final_completions, current_location, token, status, previous_progress_indicator_tuple=None):

		if not thread.is_alive():
			if thread.completions:
				final_completions.extend(thread.completions)
				# Hide the default auto-complete and show ours
				self.window.active_view().run_command('hide_auto_complete')
				sublime.set_timeout(lambda: self.window.active_view().run_command('auto_complete'), 1)

			self.window.active_view().erase_status(COFFEESCRIPT_AUTOCOMPLETE_STATUS_KEY)
			status["working"] = False
		else:
			token = thread.token
			# Create the command's goto definition text, including the selected word. For the status bar.
			status_text = COFFEESCRIPT_AUTOCOMPLETE_STATUS_MESSAGE % token
			# Get a tuple containing the progress text, progress position, and progress direction.
			# This is used to animate a progress indicator in the status bar.
			current_progress_indicator_tuple = coffee_utils.get_progress_indicator_tuple(previous_progress_indicator_tuple)
			# Get the progress text
			progress_indicator_status_text = current_progress_indicator_tuple[0]
			# Set the status bar text so the user knows what's going on
			self.window.active_view().set_status(COFFEESCRIPT_AUTOCOMPLETE_STATUS_KEY, status_text + " " + progress_indicator_status_text)
			# Check again momentarily to see if the operation has completed.
			sublime.set_timeout(lambda: self.check_operation(thread, final_completions, current_location, token, status, current_progress_indicator_tuple), 100)

	def clear_completions(self, final_completions):
		debug("Clearing completions...")
		while len(final_completions) > 0:
			final_completions.pop()

class CoffeeAutocompleteThread(threading.Thread):

	def __init__(self, project_folder_list, excluded_dirs, this_aliases, current_file_lines, preceding_symbol, prefix, preceding_function_call, function_return_types, token, symbol_region, built_in_types, member_exclusion_regexes):
		
		self.project_folder_list = project_folder_list
		self.excluded_dirs = excluded_dirs
		self.this_aliases = this_aliases
		self.current_file_lines = current_file_lines
		self.preceding_symbol = preceding_symbol
		self.prefix = prefix
		self.preceding_function_call = preceding_function_call
		self.function_return_types = function_return_types
		self.token = token
		self.symbol_region = symbol_region
		self.built_in_types = built_in_types
		self.member_exclusion_regexes = member_exclusion_regexes

		# None if no completions found, or an array of the completion tuples
		self.completions = None
		threading.Thread.__init__(self)

	def run(self):

		project_folder_list = self.project_folder_list
		excluded_dirs = self.excluded_dirs
		this_aliases = self.this_aliases
		current_file_lines = self.current_file_lines
		preceding_symbol = self.preceding_symbol
		prefix = self.prefix
		preceding_function_call = self.preceding_function_call
		function_return_types = self.function_return_types
		token = self.token
		symbol_region = self.symbol_region
		built_in_types = self.built_in_types
		member_exclusion_regexes = self.member_exclusion_regexes

		selected_word = token[token.rfind(".") + 1:]

		completions = []

		# First see if it is a special function return definition, like $ for $("#selector")
		if preceding_function_call:
			for next_return_type in function_return_types:
				function_names = next_return_type[coffee_utils.FUNCTION_RETURN_TYPE_FUNCTION_NAMES_KEY]
				if preceding_function_call in function_names:
					return_type = next_return_type[coffee_utils.FUNCTION_RETURN_TYPE_TYPE_NAME_KEY]
					completions = coffee_utils.get_completions_for_class(return_type, False, None, prefix, None, built_in_types, member_exclusion_regexes, False)

		if not completions:
			# Prepare to search globally if we need to...
			# Coffeescript filename regex
			coffeescript_filename_regex = coffee_utils.COFFEE_FILENAME_REGEX
			# All coffeescript file paths
			all_coffee_file_paths = coffee_utils.get_files_in(project_folder_list, coffeescript_filename_regex, excluded_dirs)

			# If @ typed, process as "this."
			if preceding_symbol == coffee_utils.THIS_SUGAR_SYMBOL:
				# Process as "this."
				this_type = coffee_utils.get_this_type(current_file_lines, symbol_region)
				if this_type:
					completions = coffee_utils.get_completions_for_class(this_type, False, current_file_lines, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, True)
				pass
			elif preceding_symbol == coffee_utils.PERIOD_OPERATOR:
				# If "this" or a substitute for it, process as "this."
				if selected_word == coffee_utils.THIS_KEYWORD or selected_word in this_aliases:
					# Process as "this."
					this_type = coffee_utils.get_this_type(current_file_lines, symbol_region)
					if this_type:
						completions = coffee_utils.get_completions_for_class(this_type, False, current_file_lines, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, True)
				else:
					# If TitleCase, assume a class, and that we want static properties and functions.
					if coffee_utils.is_capitalized(selected_word):
						# Assume it is either in the current view or in a coffee file somewhere
						completions = coffee_utils.get_completions_for_class(selected_word, True, current_file_lines, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, False)
						if not completions:
							# Now we search globally...
							completions = coffee_utils.get_completions_for_class(selected_word, True, None, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, False)

					# If nothing yet, assume a variable.
					if not completions:
						variable_type = coffee_utils.get_variable_type(current_file_lines, token, symbol_region, all_coffee_file_paths, built_in_types, [])
						if variable_type:
							# Assume it is either in the current view or in a coffee file somewhere
							completions = coffee_utils.get_completions_for_class(variable_type, False, current_file_lines, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, False)
					if not completions:
						# Now we search globally for a class... Maybe they're making a static call on something lowercase? Bad design, but check anyways.
						completions = coffee_utils.get_completions_for_class(selected_word, True, None, prefix, all_coffee_file_paths, built_in_types, member_exclusion_regexes, False)
		if completions:
			self.completions = completions