#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# pip3 install googletrans
# https://github.com/ssut/py-googletrans

from tkinter import *
from tkinter import ttk, messagebox
from locale import getdefaultlocale
from os import listdir, path
import re
import googletrans
import json

PLUGINS_PATH = '../plugins'
LANGS_PATH = '../core/lang'
DEFAULT_LANG = 'en'
KEY_PATTERN = re.compile(r'''('|")(\w+)\1\s*=>\s*(\'[^\']*\'|"[^"]*"|\w+)''')
DEFAULT_LANG_WARNING = 'You are using %s as default language. It\' a better way to use english(en) instead.'

class Interface(Frame):

	def getPlugins(self):
		'''Look for every plugin'''
		result = [f for f in listdir(PLUGINS_PATH) if
			path.isdir(path.sep.join([PLUGINS_PATH, f])) and
			f[-4:] != '.bak' and
			path.isfile(path.sep.join([PLUGINS_PATH, f, f + '.php']))
		]
		result.sort()
		return result

	def getCore(self):
		''' Look for every file in core for translation in the default language'''
		result = [f for f in listdir(path.sep.join([LANGS_PATH, DEFAULT_LANG])) if
			path.isfile(path.sep.join([LANGS_PATH, DEFAULT_LANG, f])) and
			f[-4:] == '.php'
		]
		result.sort()
		return result

	def getLangs(self):
		'''Look for every language in the core of PluXml. Default and locale languages are setting first'''
		locale_full, charset = getdefaultlocale()
		locale = locale_full[0:2]
		prefix = ['en'] if locale == 'en' and locale not in googletrans.LANGUAGES else ['en', locale]
		result = [f for f in listdir(LANGS_PATH) if
			path.isdir(path.sep.join([LANGS_PATH, f])) and f not in prefix
		]
		result.sort()
		return prefix + result

	def getKeys(self, filename, lang):
		'''Look for every key in a file'''
		# print(filename)
		buf = {}
		if path.isfile(filename):
			with open(filename, encoding='utf-8') as f:
				prevKey = ''
				content = f.read().replace("\\'", '##').replace('\\"', '#_#')
				for enclosure, key, value in KEY_PATTERN.findall(content):
					buf[key] = value[1:-1].replace('##', "'").replace('#_#', '"')
					if key not in self.keys:
						if len(prevKey) == 0 or self.plugins.get():
							# for plugin
							self.keys.append(key)
						else:
							# for core
							self.keys.insert(self.keys.index(prevKey) + 1, key)
					prevKey = key
		self.items[lang]['translations'] = buf

	def getKeysCore(self, context):
		''' Scan each translation file for the core of PluXml. No sort !'''
		enLang = False
		lastLang = ''
		for lang in self.langs:
			if lang == 'en':
				enLang = True
			else:
				lastLang = lang
			buf = {}
			self.getKeys(path.sep.join([LANGS_PATH, lang, context]), lang)
		if enLang:
			lastLang = 'en'
		self.plxLangDefault.set(lastLang)

	def getKeysPlugin(self, pluginName):
		if path.isdir(path.sep.join([PLUGINS_PATH, pluginName, 'lang'])):
			enLang = False
			lastLang = ''
			for lang in self.langs:
				if lang == 'en':
					enLang = True
				else:
					lastLang = lang
				buf = {}
				self.getKeys(path.sep.join([PLUGINS_PATH, pluginName, 'lang', lang + '.php']), lang)
			if enLang:
				lastLang = 'en'
			self.plxLangDefault.set(lastLang)
			if len(self.keys) > 0:
				self.keys.sort()
		else:
			for lang in self.langs:
				self.items[lang]['translations'] = {}

	def translationsDisplay(self, name, index, mode):
		position = self.keyOptionMenu.current()
		self.keysIndex = position
		self.statusbar.set('%3d/%d keys' % (position + 1, len(self.keys)))
		key = self.plxKey.get()
		for lang, item in self.items.items():
			if key in item['translations']:
				item['var'].set(item['translations'][key])
			else:
				item['var'].set('')

	def targetsUpdate(self, name, index, mode):
		if self.plugins.get():
			options = self.getPlugins()
		else:
			options = self.getCore()
		menu = self.targetOptionMenu['menu']
		menu.delete(0, END)
		for v in options:
			menu.add_command(label=v, command=lambda value=v : self.plxTarget.set(value))
		self.plxTarget.set(options[0])

	def setKey(self, position):
		maxPos = len(self.keys)
		if maxPos > 0:
			if position < 0:
				position = 0
			if position > maxPos - 1:
				position = maxPos - 1
			self.keysIndex = position
			self.plxKey.set(self.keys[position])
			# self.statusbar.set('%3d/%d keys' % (position + 1, len(self.keys)))
		else:
			position = 0
			self.keysIndex = position
			self.statusbar.set('No key')

		state = DISABLED if position <= 0 else NORMAL
		self.keysBtns['first']['state'] = state
		self.keysBtns['prev']['state'] = state
		state = DISABLED if position >= maxPos-1 else NORMAL
		self.keysBtns['next']['state'] = state
		self.keysBtns['last']['state'] = state
		state = DISABLED if maxPos == 0 else NORMAL
		self.keysBtns['empty']['state'] = state

	def firstKey(self):
		self.setKey(0)

	def prevKey(self):
		self.setKey(self.keysIndex - 1)

	def nextKey(self):
		self.setKey(self.keysIndex + 1)

	def lastKey(self):
		self.setKey(99999)

	def newKey(self):
		print('Coming soon')

	def keysUpdate(self, name, index, mode):
		self.keys = []
		target = self.plxTarget.get()
		if self.plugins.get():
			# print('Plugin: ' + target)
			self.getKeysPlugin(target)
		else:
			# print('Core: ' + target)
			self.getKeysCore(target)
		self.statusbar.set('%d keys' % len(self.keys))
		if len(self.keys) > 0:
			self.keyOptionMenu['values'] = self.keys
			# self.plxKey.set(self.keys[0])
		else:
			self.keyOptionMenu['values'] = ''
		self.setKey(0)
		self.nextEmptyKey()

	def nextEmptyKey(self):
		iMax = len(self.keys)
		if iMax > 0:
			x = self.keysIndex + 1
			for i in range(x, iMax):
				key = self.keys[i]
				missing = False
				for lang in self.langs:
					# Don't check for any unused language
					if len(self.items[lang]['translations']) > 0 and key not in self.items[lang]['translations']:
						missing = True
						break
				if missing:
					x = i
					break
			self.setKey(x)
		else:
			self.setKey(0)

	def delKey(self):
		print('coming soon')
		pass

	def jsonToClipboard(self):
		if self.plugins.get():
			buf = {}
			for lang in self.langs:
				buf[lang] = self.items[lang]['translations']
		else:
			buf = self.lastTranslations

		# https://stackoverflow.com/questions/579687/how-do-i-copy-a-string-to-the-clipboard-on-windows-using-python
		clp = Tk()
		clp.withdraw()
		clp.clipboard_clear()
		clp.clipboard_append(json.dumps(buf,  ensure_ascii=False, sort_keys=True, indent=4, separators=(',', ': ')))
		clp.update()
		clp.destroy()

	def sentenceUpdate(self, name, index, mode):
		lang = name[-2:]
		entry = self.items[lang]
		value = entry['var'].get()
		if len(value.strip()) > 0:
			key = self.plxKey.get()
			if key not in entry['translations'] or entry['translations'][key] != value:
				entry['translations'][key] = value
				entry['updated'] = True
				self.saveBtn['state'] = NORMAL
				if not self.plugins.get():
					if lang not in self.lastTranslations:
						self.lastTranslations[lang] = {}
					self.lastTranslations[lang][key] = value

	def translate(self):
		'''Download translation for each empty string for each language, except default language.'''
		defaultLang = self.plxLangDefault.get()
		if	defaultLang == 'en' or 	messagebox.askokcancel(
				'Bad default language',
				message=(DEFAULT_LANG_WARNING % googletrans.LANGUAGES[defaultLang])
			) == 'ok' :

			# checks if not empty key in default language
			missingKeys = []
			for key in self.keys:
				if key not in self.items[defaultLang]['translations'] or len(self.items[defaultLang]['translations'][key].strip()) == 0:
					missingKeys.append(key)

			if len(missingKeys) == 0:
				# OK! We have every key in default language
				# loop for each foreign language
				self.lastTranslations = {}
				for lang in self.langs:
					if lang != defaultLang:
						if lang in googletrans.LANGUAGES:
							keys = []
							sentences = []
							for k in self.keys:
								if k not in self.items[lang]['translations'] or len(self.items[lang]['translations'][k].strip()) == 0:
									sentence = self.items[defaultLang]['translations'][k].strip()
									# print('%s => %s' % (k, sentence))
									keys.append(k)
									sentences.append(sentence) # convert HTML entities
							if len(sentences) > 0:
								# print('%d sentences to translate to %s' % (len(sentences), lang))
								self.statusbar.set('%d sentences for translating into %s ...' % (len(sentences), googletrans.LANGUAGES[lang]))
								self.top.update()
								translator = googletrans.Translator()
								translations = translator.translate(sentences, dest=lang, src=defaultLang)
								self.lastTranslations[lang] = {}
								for i in range(len(keys)):
									k = keys[i]
									value = translations[i].text
									self.items[lang]['translations'][k] = value
									self.lastTranslations[lang][k] = value
									# print('%s -> %s' % (k, translations[i].text))
								self.items[lang]['missingKeys'] = keys # Required for core in self::langsSave()
								self.items[lang]['updated'] = True
								self.saveBtn['state'] = NORMAL
							else:
								self.statusbar.set('Translation not required for ' + googletrans.LANGUAGES[lang])
						else:
							print('The %s language is unknown' % lang)
				self.statusbar.set('Done')
				self.setKey(self.keysIndex)
			else:
				messagebox.showerror('Missing keys', message='The following keys are missing in the default language %s ' % googletrans.LANGUAGES[defaultLang] + '\n'.join(missingKeys))
				self.statusbar.set('Some keys are missing in the default language ' + googletrans.LANGUAGES[defaultLang])

	def langsSave(self):
		'''Save any update after downloading or manual input'''
		print('We are saving translations in :')
		for lang in self.langs:
			print(lang)
			if self.items[lang]['updated']:
				self.statusbar.set('Saving for ' + lang)
				if self.plugins.get():
					lines = []
					for key in self.keys:
						if key in self.items[lang]['translations'] and len(self.items[lang]['translations'][key].strip()) > 0:
							value = self.items[lang]['translations'][key].strip().replace('"', '\\"').replace('\n', '\\n')
							tabs = '\t' * int((33-len(key)) / 4)
							lines.append("'%s'%s=> \"%s\"" % (key, tabs, value))
					if len(lines) > 0:
						if self.plugins.get():
							# for plugin
							folder = path.sep.join([PLUGINS_PATH, self.plxTarget.get(), 'lang'])
							if not path.isdir(folder):
								path.mkdir(folder)
							buf = '''<?php
$LANG = array(
	'''
							buf += ",\n\t".join(lines)
							buf += '''
);
?>
'''
							filename = path.sep.join([folder, lang + '.php'])
							print(filename)
							with open(filename, 'w', encoding='utf-8') as f:
								f.write(buf)
				else:
					# for core.
					context = self.plxTarget.get()
					for lang in self.lastTranslations.keys():
						filename = path.sep.join([LANGS_PATH, lang, context])
						content = ''
						with open(filename, encoding='utf-8') as f:
							content = f.read()

						for k in range(len(self.items[lang]['missingKeys'])-1, -1, -1):
							key = self.items[lang]['missingKeys'][k]
							# Tester si la clé existe déjà
							if key in self.keys:
								value = self.lastTranslations[lang][key].replace("'", "\'")
								tabs = "\t" * int((37 - len(key)) / 4)

								# we suppose : one key, one line
								mask = "'%s'\s*=>.*,?\s$" % key
								if re.match(mask, content):
									# the key already exists. Replace his value
									replace = "'%s'%s=> '%s'," % (key, tabs, value)
									content = re.sub(mask, replace, content)
								else:
									# insert the new key. Look for the next key in the content for inserting before it
									i = self.keys.index(key)
									nextKeyContent = self.keys[i+1]
									replace = "'%s'%s=> '%s',\n'%s'" % (key, tabs, value, nextKeyContent)
									content = re.sub("'%s'" % nextKeyContent, replace, content)

						with open(filename, 'w', encoding='utf-8') as f:
							f.write(content)

					self.lastTranslations = {} # re-init
				self.items[lang]['updated'] = False
		self.saveBtn['state'] = DISABLED

	def __init__(self, top):

		self.top = top
		self.plugins = BooleanVar()
		self.plugins.trace('w', self.targetsUpdate)
		self.plxTarget = StringVar(name='target')
		self.plxTarget.trace('w', self.keysUpdate)
		self.plxKey = StringVar(name='currentKey')
		self.plxKey.trace('w', self.translationsDisplay)
		self.plxLangDefault = StringVar(name='defaultLang')
		self.statusbar = StringVar(name='statusbar')
		self.lastTranslations = {}

		# target dropbox
		targetFrame = Frame(top)
		Label(targetFrame, text='Target', width=5).pack(side=LEFT)
		self.targetOptionMenu = OptionMenu(targetFrame, self.plxTarget, ' ')
		self.targetOptionMenu.pack(side=LEFT, fill=X, expand=True)
		# select between core or plugins
		for text, value in [('Core', False), ('Plugins', True)]:
			Radiobutton(targetFrame,
				text=text,
				variable=self.plugins,
				value=value
			).pack(side=LEFT)
		targetFrame.pack(fill=X)

		# key dropbox
		keyFrame = Frame(top)
		Label(keyFrame, text='Key', width=5).pack(side=LEFT)
		widthBtn = 3
		self.keysBtns = {}
		for name, caption, command1 in [
			('first',	'<<',	self.firstKey),
			('prev',	'<',	self.prevKey),
			('next',	'>',	self.nextKey),
			('last',	'>>',	self.lastKey),
			('empty',	'?',	self.nextEmptyKey),
			('del',		'Del',	self.delKey)	,
			('new',		'New',	self.newKey)
		]:
			if name == 'next' :
				# self.keyOptionMenu = ttk.Combobox(keyFrame, self.plxKey, ' ')
				self.keyOptionMenu = ttk.Combobox(
					keyFrame, textvariable=self.plxKey,
					height=30,
					state='readonly'
				)
				self.keyOptionMenu.pack(side=LEFT, fill=X, expand=True)
			btn = Button(keyFrame, text=caption, command=command1, width=widthBtn)
			btn.pack(side=LEFT)
			self.keysBtns[name] = btn
		keyFrame.pack(fill=X)

		# translation for each language and default language
		items = {}
		langs = self.getLangs()
		for lang in langs:
			f = Frame(top)
			v = StringVar(name='text-' + lang)
			v.trace('w', self.sentenceUpdate)
			Radiobutton(f,
				text=lang, variable=self.plxLangDefault, value=lang, width=3,
				anchor=W).pack(side=LEFT
			)
			entry = Entry(f, textvariable=v).pack(side=LEFT, fill=X, expand=True)
			f.pack(fill=X)
			items[lang] = {
				'var' : v,
				'translations' : {},
				'updated' : False
			}
		self.langs = langs # For preserving the order of languages and it's faster than self.langs
		self.items = items

		# toolbox
		bottomFrame = Frame(top)
		Button(bottomFrame, text='Exit', command=top.destroy).pack(side=LEFT, fill=X, expand=True)
		Button(bottomFrame, text='Copy (JSON)', command=self.jsonToClipboard).pack(side=LEFT, fill=X, expand=True)
		Button(bottomFrame, text='Translate', command=self.translate).pack(side=LEFT, fill=X, expand=True)
		btn = Button(bottomFrame, text='Save', command=self.langsSave)
		btn.pack(side=LEFT, fill=X, expand=True)
		self.saveBtn = btn
		bottomFrame.pack(fill=X)

		Label(top, textvariable=self.statusbar, anchor=W, relief="sunken").pack(fill=X)

		top.title('Translator for PluXml')

		# update and display target list for plugins
		self.plxLangDefault.set(DEFAULT_LANG)
		self.plugins.set(True)

def main():
	tk = Tk()
	tk.minsize(width=800, height=150)
	interface = Interface(tk)
	tk.mainloop()

if __name__ == '__main__':
	main()