import sublime
import sublime_plugin

import re
import imp
import json
import sys
import os.path
import traceback

BASE_PATH = os.path.abspath(os.path.dirname(__file__))
PACKAGES_PATH = sublime.packages_path() or os.path.dirname(BASE_PATH)
# EMMET_GRAMMAR = os.path.join(BASE_PATH, 'Emmet.tmLanguage')
EMMET_GRAMMAR = 'Packages/%s/Emmet.tmLanguage' % os.path.basename(BASE_PATH)
sys.path += [BASE_PATH] + [os.path.join(BASE_PATH, f) for f in ['emmet_completions', 'emmet']]


# Make sure all dependencies are reloaded on upgrade
if 'emmet.reloader' in sys.modules:
	imp.reload(sys.modules['emmet.reloader'])
import emmet.reloader

# import completions as cmpl
import emmet.pyv8loader as pyv8loader
import emmet_completions as cmpl
from emmet_completions.meta import HTML_ELEMENTS_ATTRIBUTES, HTML_ATTRIBUTES_VALUES
from emmet.context import Context
from emmet.context import js_file_reader as _js_file_reader
from emmet.pyv8loader import LoaderDelegate

__version__      = '1.1'
__core_version__ = '1.0'
__authors__      = ['"Sergey Chikuyonok" <serge.che@gmail.com>'
					'"Nicholas Dudfield" <ndudfield@gmail.com>']

is_python3 = sys.version_info[0] > 2

# JS context
ctx = None
# Emmet Settings
settings = None

# Default ST settings
user_settings = None

def is_st3():
	return sublime.version()[0] == '3'

def js_file_reader(file_path, use_unicode=True):
	if hasattr(sublime, 'load_resource'):
		rel_path = file_path
		for prefix in [sublime.packages_path(), sublime.installed_packages_path()]:
			if rel_path.startswith(prefix):
				rel_path = os.path.join('Packages', rel_path[len(prefix) + 1:])
				break

		rel_path = rel_path.replace('.sublime-package', '')
		# for Windows we have to replace slashes
		rel_path = rel_path.replace('\\', '/')
		return sublime.load_resource(rel_path)

	return _js_file_reader(file_path, use_unicode)

def init():
	"Init Emmet plugin"
	# load settings
	globals()['user_settings'] = sublime.load_settings('Preferences.sublime-settings')
	globals()['settings'] = sublime.load_settings('Emmet.sublime-settings')
	settings.add_on_change('extensions_path', update_settings)

	# setup environment for PyV8 loading
	pyv8_paths = [
		os.path.join(PACKAGES_PATH, 'PyV8'),
		os.path.join(PACKAGES_PATH, 'PyV8', pyv8loader.get_arch()),
		os.path.join(PACKAGES_PATH, 'PyV8', 'pyv8-%s' % pyv8loader.get_arch())
	]

	sys.path += pyv8_paths

	# unpack recently loaded binary, is exists
	for p in pyv8_paths:
		pyv8loader.unpack_pyv8(p)
	
	# provide some contributions to JS
	contrib = {
		'sublime': sublime, 
		'sublimeReplaceSubstring': replace_substring,
		'sublimeGetOption': settings.get
	}

	# create JS environment
	delegate = SublimeLoaderDelegate()
	globals()['ctx'] = Context(
		files=['../editor.js'], 
		ext_path=settings.get('extensions_path', None), 
		contrib=contrib, 
		logger=delegate.log,
		reader=js_file_reader
	)

	update_settings()

	pyv8loader.load(pyv8_paths[1], delegate) 

	if settings.get('remove_html_completions', False):
		sublime.set_timeout(cmpl.remove_html_completions, 2000)

class SublimeLoaderDelegate(LoaderDelegate):
	def __init__(self, settings=None):

		if settings is None:
			settings = {}
			for k in ['http_proxy', 'https_proxy', 'timeout']:
				if user_settings.has(k):
					settings[k] = user_settings.get(k, None)

		LoaderDelegate.__init__(self, settings)
		self.state = None
		self.message = 'Loading PyV8 binary, please wait'
		self.i = 0
		self.addend = 1
		self.size = 8

	def on_start(self, *args, **kwargs):
		self.state = 'loading'

	def on_progress(self, *args, **kwargs):
		if kwargs['progress'].is_background:
			return

		before = self.i % self.size
		after = (self.size - 1) - before
		msg = '%s [%s=%s]' % (self.message, ' ' * before, ' ' * after)
		if not after:
			self.addend = -1
		if not before:
			self.addend = 1
		self.i += self.addend

		sublime.set_timeout(lambda: sublime.status_message(msg), 0)

	def on_complete(self, *args, **kwargs):
		self.state = 'complete'

		if kwargs['progress'].is_background:
			return

		sublime.set_timeout(lambda: sublime.status_message('PyV8 binary successfully loaded'), 0)

	def on_error(self, exit_code=-1, thread=None):
		self.state = 'error'
		sublime.set_timeout(lambda: show_pyv8_error(exit_code), 0)

	def setting(self, name, default=None):
		"Returns specified setting name"
		return self.settings.get(name, default)

	def log(self, message):
		print('Emmet: %s' % message)

def show_pyv8_error(exit_code):
	if 'PyV8' not in sys.modules:
		sublime.error_message('Error while loading PyV8 binary: exit code %s \nTry to manually install PyV8 from\nhttps://github.com/emmetio/pyv8-binaries' % exit_code)

def active_view():
	return sublime.active_window().active_view()

def check_context(verbose=False):
	"Checks if JS context is completely available"
	if not ctx.js():
		if verbose:
			sublime.message_dialog('Please wait a bit while PyV8 binary is being downloaded')
		return False

	return True


def replace_substring(start, end, value, no_indent=False):
	view = active_view()

	view.sel().clear()
	view.sel().add(sublime.Region(start, end or start)) 

	if not is_python3:
		value = value.decode('utf-8')

	# XXX a bit naive indentation control. It handles most common
	# `no_indent` usages like replacing CSS rule content, but may not
	# produce expected result in all possible situations

	if no_indent:
		line = view.substr(view.line(view.sel()[0]))
		value = unindent_text(value, get_line_padding(line))

	view.run_command('insert_snippet', {'contents': value})

def unindent_text(text, pad):
	"""
	Removes padding at the beginning of each text's line
	@type text: str
	@type pad: str
	"""
	lines = text.splitlines()
	
	for i,line in enumerate(lines):
		if line.startswith(pad):
			lines[i] = line[len(pad):]
	
	return '\n'.join(lines)

def get_line_padding(line):
	"""
	Returns padding of current editor's line
	@return str
	"""
	m = re.match(r'^(\s+)', line)
	return m and m.group(0) or ''

def update_settings():
	ctx.set_ext_path(settings.get('extensions_path', None))

	keys = ['snippets', 'preferences', 'syntaxProfiles', 'profiles']
	payload = {}
	for k in keys:
		data = settings.get(k, None)
		if data:
			payload[k] = data

	ctx.reset()
	ctx.load_user_data(json.dumps(payload))
	ctx.js()

def get_scope(view, pt=-1):
	if pt == -1:
		# use current caret position
		pt = view.sel()[0].begin()

	if hasattr(view, 'scope_name'):
		return view.scope_name(pt)

	return view.syntax_name(pt)

def should_perform_action(name, view=None):
	if not view:
		view = active_view()

	# fallback to old check
	if not view.settings().get('enable_emmet_keymap', True):
		return False

	disabled_actions = settings.get('disabled_keymap_actions', '')

	if not disabled_actions: # no disabled actions
		return True

	if disabled_actions == 'all': # disable all actions
		return False

	return name not in re.split(r'\s*,\s*', disabled_actions.strip())

def should_handle_tab_key(syntax=None):
	view = active_view()
	scopes = settings.get('disabled_single_snippet_for_scopes', None)
	cur_scope = get_scope(view)

	if sublime.score_selector(cur_scope, 'source.css'):
		return True

	if not scopes or not sublime.score_selector(cur_scope, scopes):
		return True

	abbr = ctx.js().locals.pyExtractAbbreviation()

	disabled_snippets = settings.get('disabled_single_snippets', '').split()
	if disabled_snippets and abbr in disabled_snippets:
		return False

	if not re.match(r'^[\w\:%]+$', abbr):
		# it's a complex expression
		return True

	if re.match(r'^(lorem|lipsum)([a-z]{2})?\d*$', abbr):
		# hardcoded Lorem Ipsum generator
		return True

	# detect inline CSS
	if syntax is None:
		syntax = ctx.js().locals.pyGetSyntax();

	if syntax == 'css':
		return True

	known_tags = settings.get('known_html_tags', '').split()
	if abbr in known_tags or ctx.js().locals.pyHasSnippet(abbr):
		return True

	return False

def log(message):
	if settings.get('debug', False):
		print('Emmet: %s' % message)

class RunEmmetAction(sublime_plugin.TextCommand):
	def run(self, edit, action=None, **kw):
		run_action(lambda i, sel: ctx.js().locals.pyRunAction(action))
		# ctx.js().locals.pyRunAction(action)

class ActionContextHandler(sublime_plugin.EventListener):
	def on_query_context(self, view, key, op, operand, match_all):
		if not key.startswith('emmet_action_enabled.'):
			return None

		prefix, name = key.split('.')
		return should_perform_action(name, view)

def get_edit(view, edit_token=None):
	edit = None
	try:
		edit = view.begin_edit()
	except:
		pass

	if not edit and edit_token:
		try:
			edit = view.begin_edit(edit_token, 'Emmet')
		except Exception as e:
			pass

	return edit

def run_action(action, view=None):
	if not check_context(True):
		return

	"Runs Emmet action in multiselection mode"
	if not view:
		view = active_view()

	region_key = '__emmet__'
	sels = list(view.sel())
	r = ctx.js().locals.pyRunAction