# -*- coding: utf-8 -*-

import sublime, sublime_plugin
import sys
import os
if sys.version_info < (3, 0):
	from chardet.universaldetector import UniversalDetector
	NONE_COMMAND = (None, None, 0)
	ST3 = False
else:
	from .chardet.universaldetector import UniversalDetector
	NONE_COMMAND = ('', None, 0)
	ST3 = True
import codecs
import threading
import json
import time
import hashlib
import shutil

SKIP_ENCODINGS = ('ASCII', 'UTF-8', 'UTF-16LE', 'UTF-16BE')

SETTINGS = {}
REVERTING_FILES = []

CONFIRM_IS_AVAILABLE = ('ok_cancel_dialog' in dir(sublime))

ENCODINGS_NAME = []
ENCODINGS_CODE = []

class EncodingCache(object):
	def __init__(self):
		self.file = os.path.join(sublime.packages_path(), 'User', 'encoding_cache.json')
		self.cache = []
		self.max_size = -1
		self.dirty = False
		self.load()
		self.save_on_dirty()

	def save_on_dirty(self):
		if self.dirty:
			self.save()
		sublime.set_timeout(self.save_on_dirty, 10000)

	def shrink(self):
		if self.max_size < 0:
			return
		if len(self.cache) > self.max_size:
			self.dirty = True
			del self.cache[self.max_size:]

	def set_max_size(self, max_size):
		self.max_size = max_size
		self.shrink()

	def load(self):
		if not os.path.exists(self.file):
			return
		fp = open(self.file, 'r')
		self.cache = json.load(fp)
		fp.close()
		if len(self.cache) > 0:
			if 'file' in self.cache[0]:
				# old style cache
				new_cache = []
				for item in self.cache:
					new_cache.append({
						item['file']: item['encoding']
					})
				self.cache = new_cache
				self.dirty = True

	def save(self):
		self.shrink()
		fp = open(self.file, 'w')
		json.dump(self.cache, fp)
		fp.close()
		self.dirty = False

	def pop(self, file_name):
		for item in self.cache:
			if file_name in item:
				self.cache.remove(item)
				self.dirty = True
				return item.get(file_name)
		return None

	def set(self, file_name, encoding):
		if self.max_size < 1:
			return
		self.pop(file_name)
		self.cache.insert(0, {
			file_name: encoding
		})
		self.dirty = True

encoding_cache = None

def get_settings():
	global ENCODINGS_NAME, ENCODINGS_CODE
	settings = sublime.load_settings('ConvertToUTF8.sublime-settings')
	encoding_list = settings.get('encoding_list', [])
	ENCODINGS_NAME = [pair[0] for pair in encoding_list]
	ENCODINGS_CODE = [pair[1] for pair in encoding_list]
	encoding_cache.set_max_size(settings.get('max_cache_size', 100))
	SETTINGS['max_detect_lines'] = settings.get('max_detect_lines', 600)
	SETTINGS['preview_action'] = settings.get('preview_action', 'no_action')
	SETTINGS['default_encoding_on_create'] = settings.get('default_encoding_on_create', '')
	SETTINGS['convert_on_load'] = settings.get('convert_on_load', 'always')
	SETTINGS['convert_on_save'] = settings.get('convert_on_save', 'always')
	SETTINGS['lazy_reload'] = settings.get('lazy_reload', True)

TMP_DIR = None

def get_temp_name(name):
	if not name:
		return None
	name = name.encode('UTF-8')
	return hashlib.md5(name).hexdigest()

def clean_temp_folder():
	tmp_files = os.listdir(TMP_DIR)
	for win in sublime.windows():
		for view in win.views():
			file_name = view.file_name()
			tmp_name = get_temp_name(file_name)
			if tmp_name in tmp_files:
				if not view.is_dirty():
					tmp_file = os.path.join(TMP_DIR, tmp_name)
					# check mtime
					mtime1 = os.path.getmtime(file_name)
					mtime2 = os.path.getmtime(tmp_file)
					if mtime1 != mtime2:
						# file was changed outside
						view.settings().erase('prevent_detect')
						continue
					shutil.move(tmp_file, file_name)
				tmp_files.remove(tmp_name)
	for tmp_name in tmp_files:
		tmp_file = os.path.join(TMP_DIR, tmp_name)
		os.unlink(tmp_file)

def init_settings():
	global encoding_cache, TMP_DIR
	encoding_cache = EncodingCache()
	get_settings()
	sublime.load_settings('ConvertToUTF8.sublime-settings').add_on_change('get_settings', get_settings)
	TMP_DIR = os.path.join(sublime.packages_path(), 'User', 'c2u_tmp')
	if not os.path.exists(TMP_DIR):
		os.mkdir(TMP_DIR)

def setup_views():
	clean_temp_folder()
	if SETTINGS['convert_on_load'] == 'never':
		return
	# existing views should be checked
	for win in sublime.windows():
		for view in win.views():
			if view.is_dirty() or view.settings().get('origin_encoding'):
				show_encoding_status(view)
				continue
			file_name = view.file_name()
			encoding = view.encoding()
			threading.Thread(target=lambda: detect(view, file_name, encoding)).start()

def plugin_loaded():
	init_settings()
	setup_views()

def wait_for_ready():
	if sublime.windows():
		setup_views()
	else:
		sublime.set_timeout(wait_for_ready, 100)

if not ST3:
	init_settings()
	wait_for_ready()

def detect(view, file_name, encoding):
	if not file_name or not os.path.exists(file_name):
		return
	if not encoding.endswith(' with BOM'):
		encoding = encoding_cache.pop(file_name)
	if encoding:
		sublime.set_timeout(lambda: init_encoding_vars(view, encoding, detect_on_fail=True), 0)
		return
	sublime.set_timeout(lambda: view.set_status('origin_encoding', 'Detecting encoding, please wait...'), 0)
	detector = UniversalDetector()
	cnt = SETTINGS['max_detect_lines']
	fp = open(file_name, 'rb')
	for line in fp:
		# cut MS-Windows CR code
		line = line.replace(b'\r',b'')
		detector.feed(line)
		cnt -= 1
		if detector.done or cnt == 0:
			break
	fp.close()
	detector.close()
	encoding = detector.result['encoding']
	if encoding:
		encoding = encoding.upper()
		if encoding == 'BIG5':
			encoding = 'BIG5-HKSCS'
		elif encoding == 'GB2312':
			encoding = 'GBK'
	confidence = detector.result['confidence']
	sublime.set_timeout(lambda: check_encoding(view, encoding, confidence), 0)

def check_encoding(view, encoding, confidence):
	view.set_status('origin_encoding', ('Detected {0} with {1:.0%} confidence'.format(encoding, confidence)) if encoding else 'Encoding can not be detected')
	if not encoding or confidence < 0.95:
		view_encoding = view.encoding()
		if view_encoding == view.settings().get('fallback_encoding'):
			# show error only when the ST2 can't detect the encoding either
			show_selection(view)
			return
		else:
			# using encoding detected by ST2
			if view_encoding == 'Undefined':
				view_encoding = 'ASCII'
			encodin
g = view_encoding
	init_encoding_vars(view, encoding)

def show_encoding_status(view):
	encoding = view.settings().get('force_encoding')
	if not 
encoding:
		encoding = view.settings().get('origin_encoding')
		if not encoding:
			return
	view.set_status('origin_encoding', encoding)

def init_encoding_vars(view, encoding, run_convert=True, stamp=None, detect_on_fail=False):
	if not encoding:
		return
	view.settings().set('origin_encoding', encoding)
	show_encoding_status(view)
	if encoding in SKIP_ENCODINGS or encoding == view.encoding():
		encoding_cache.pop(view.file_name())
		return
	view.settings().set('in_converting', True)
	if run_convert:
		if stamp == None:
			stamp = '{0}'.format(time.time())
		translate_tabs_to_spaces = view.settings().get('translate_tabs_to_spaces')
		view.settings().set('translate_tabs_to_spaces', False)
		view.run_command('convert_to_utf8', {'detect_on_fail': detect_on_fail, 'stamp': stamp})
		view.settings().set('translate_tabs_to_spaces', translate_tabs_to_spaces)

def clean_encoding_vars(view):
	view.settings().erase('in_converting')
	view.settings().erase('origin_encoding')
	view.erase_status('origin_encoding')
	view.set_scratch(False)
	encoding_cache.pop(view.file_name())

def remove_reverting(file_name):
	while file_name in REVERTING_FILES:
		REVERTING_FILES.remove(file_name)

class EncodingSelection(threading.Thread):
	def __init__(self, view):
		threading.Thread.__init__(self)
		self.view = view

	def run(self):
		sublime.set_timeout(self.show_panel, 0)

	def show_panel(self):
		window = self.view.window()
		if window:
			window.show_quick_panel(ENCODINGS_NAME, self.on_done)

	def on_done(self, selected):
		if selected == -1:
			clean_encoding_vars(self.view)
		else:
			init_encoding_vars(self.view, ENCODINGS_CODE[selected])

def show_selection(view):
	EncodingSelection(view).start()

class ReloadWithEncoding(threading.Thread):
    def __init__(self, view, encoding):
        threading.Thread.__init__(self)
        self.view = view
        self.encodi