#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
#	Copyright (C) 2003-2010 Daniel Naber, Lutz Haseloff
#	Copyright (C) 2013-2021 Mechtilde Stehmann
#
#	Copyright (C) 2013-2016 for the gettext integration: Dr. Michael Stehmann
#
#	This program is free software; you can redistribute it and/or
#	modify it under the terms of the GNU General Public License
#	as published by the Free Software Foundation; either version 2
#	of the License, or any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#	MA 02110-1301, USA.

import configparser
import codecs
import os
import re
import string
import time
import zipfile
import locale
import gettext
import sys

from tkinter import *
import tkinter.filedialog
import tkinter.messagebox

class Application(object):

	CONFIGFILE = ".loook.cfg"

	def __init__(self, master=None):
		"""Load configuration or use sensible default values."""
		self.master = master
		self.stopped = 0
		self.ooo_path_str = None
		self.search_path_str = None
		config_path = None
		if os.getenv('USERPROFILE'):
			config_path = os.getenv('USERPROFILE')
		elif os.getenv('HOME'):
			config_path = os.getenv('HOME')
		if config_path:
			self.configfile = os.path.join(config_path, self.CONFIGFILE)
			self.config =  configparser.ConfigParser()
			self.config.read(self.configfile)
			try:
				self.ooo_path_str = self.config.get("General", "ooo_path")
				self.search_path_str = self.config.get("General", "search_path")
			except configparser.NoSectionError:
				pass
		else:
			print >> sys.stderr, _("Cannot find home directory, settings will not be saved.")
			self.configfile = None
		self.createWidgets()
		return

	def createWidgets(self):
		"""Build and show the GUI elements."""
		Label(self.master, text=_("Viewer:")).grid(row=0, sticky=E)
		Label(self.master, text=_("Search path:")).grid(row=1, sticky=E)
		Label(self.master, text=_("Search terms:")).grid(row=2, sticky=E)
		Label(self.master, text=_("Mode:")).grid(row=3, sticky=E)
		Label(self.master, text=_("Matches:")).grid(row=4, sticky=N+E)

		self.ooo_path = Entry(self.master)
		if self.ooo_path_str:
			self.ooo_path.insert(END, self.ooo_path_str)
		else:
			self.ooo_path.insert(END, "soffice")
		self.ooo_path_b = Button(self.master)
		self.ooo_path_b.bind("<Button-1>", self.selectOOoPath)
		self.ooo_path_b["text"] = ">"

		self.search_path = Entry(self.master)
		if len(sys.argv) >= 2:
			self.search_path.insert(END, sys.argv[1])
		elif self.search_path_str:
			self.search_path.insert(END, self.search_path_str)
		else:
			self.search_path.insert(END, os.getcwd())
		self.search_path_b = Button(self.master)
		self.search_path_b.bind("<Button-1>", self.selectSearchPath)
		self.search_path_b["text"] = ">"

		self.search_query = Entry(self.master)
		self.search_query.bind('<Return>', self.startSearch)
		if len(sys.argv) >= 2:
			lang, enc = locale.getdefaultlocale()
			self.search_query.insert(END, ' '.join(sys.argv[2:]))
		self.search_query.focus()

		self.mode_button = Button(self.master)
		self.mode_button.bind("<Button-1>", self.popupMode)
		self.mode_button["text"] = _("AND")
		self.mode_menu = Menu(self.master, tearoff=0)
		self.mode_menu.add_command(label=_("AND"), command=self.setModeAND)
		self.mode_menu.add_command(label=_("OR"), command=self.setModeOR)
		self.mode_menu.add_command(label=_("Phrase"), command=self.setModePhrase)

		pad = 1
		self.ooo_path.grid(columnspan=2, row=0, column=1, sticky=E+W, pady=pad, padx=pad)
		self.ooo_path_b.grid(row=0, column=3, sticky=E+W, pady=pad, padx=pad)

		self.search_path.grid(columnspan=2, row=1, column=1, sticky=E+W, pady=pad, padx=pad)
		self.search_path_b.grid(row=1, column=3, sticky=E+W, pady=pad, padx=pad)
		self.search_query.grid(columnspan=3, row=2, column=1, sticky=E+W, pady=pad, padx=pad)
		self.mode_button.grid(columnspan=3, row=3, column=1, sticky=W, pady=pad, padx=pad)

		self.scrollbar = Scrollbar(self.master)
		self.scrollbar.grid(row=4, column=3, sticky=N+S, pady=pad, padx=pad)
		self.listbox = Listbox(self.master, yscrollcommand=self.scrollbar.set)
		self.listbox.bind('<Double-Button-1>', self.showDoc)
		self.listbox.grid(columnspan=2, row=4, column=1, sticky=E+W+S+N, pady=pad, padx=pad)
		self.scrollbar.config(command=self.listbox.yview)

		f = Frame(self.master)
		self.search = Button(f)
		self.search["text"] = _("Search")
		self.search["command"] = self.startSearch
		self.search.pack(side=LEFT)
		self.quit_button = Button(f)
		self.quit_button["text"] = _("Quit")
		self.quit_button["command"] = self.doQuit
		self.quit_button.pack(side=RIGHT)
		self.stop_button = Button(f)
		self.stop_button["text"] = _("Stop")
		self.stop_button["command"] = self.stop
		self.stop_button["state"] = DISABLED
		self.stop_button.pack(side=RIGHT)
		self.save_button = Button(f)
		self.save_button["text"] = _("Save")
		self.save_button["command"] = self.doSave
		self.save_button["state"] = DISABLED
		self.save_button.pack(side=LEFT)

		f.grid(row=5, column=2, sticky=E, pady=pad, padx=pad)
		self.status = Label(self.master, text="", bd=1, relief=SUNKEN, anchor=W)
		self.status.config(text=_("Ready."))
		self.status.grid(row=6, columnspan=4, column=0, sticky=E+W, pady=pad, padx=pad)
		return

	def doQuit(self):
		"""Save configuration, the quit."""
		self.saveConfig()
		self.master.quit()
		return

	def saveConfig(self):
		"""Save path settings in configuration file in the user's HOME."""
		if self.configfile:
			file = codecs.open(self.configfile, "w", "utf-8")
			if not self.config.has_section("General"):
				self.config.add_section("General")
			file.write("[General]\n")
			file.write("ooo_path=%s\n" % self.ooo_path.get())
			file.write("search_path=%s\n" % self.search_path.get())
			file.close()
		return

	def selectOOoPath(self, event):
		d = tkinter.filedialog.askopenfilename()
		self.ooo_path.delete(0, END)
		self.ooo_path.insert(END, d)
		return

	def selectSearchPath(self, event):
		d = tkinter.filedialog.askdirectory()
		self.search_path.delete(0, END)
		self.search_path.insert(END, d)
		return

	def setMode(self, mode):
		self.mode = mode
		self.mode_button["text"] = mode
		return

	def setModeAND(self):
		self.setMode(_("AND"))
		return

	def setModeOR(self):
		self.setMode(_("OR"))
		return

	def setModePhrase(self):
		self.setMode(_("Phrase"))
		return

	def popupMode(self, event):
		try:
			self.mode_menu.tk_popup(event.x_root, event.y_root, 0)
		finally:
			# make sure to release the grab (Tk 8.0a1 only)
			self.mode_menu.grab_release()
		return

	def stop(self):
		self.stopped = 1
		return

	def showDoc(self, event):
		"""Start OOo to view the file. This method lacks
		error handling (TODO)."""
		items = event.widget.curselection()
		filename = "%s%s" % (self.search_path.get(), event.widget.get(items[0]))
		filename = os.path.normpath(filename)
		prg = self.ooo_path.get()
		if not prg:
			tkinter.messagebox.showwarning(_('Error'), _('Set viewer first.'))
		else:
			filename = filename.replace('"', '\\" ')
			cmd = "\"%s\" \"%s\" &" % (prg, filename)
			self.status.config(text=_("Starting viewer..."))
			print(cmd)
			res = os.system(cmd)
			if res != 0:
				# don't show a dialog, this check might not be system-indepenent:
				print(_("Warning: Command returned code != 0: ") + cmd)
		return

	def removeXMLMarkup(self, s, replace_with_space):
		s = re.compile("<!--.*?-->", re.DOTALL).sub('', s)
		repl = ''
		if replace_with_space:
			repl = ' '
		s = re.compile("<[^>]*>", re.DOTALL).sub(repl, s)
		return s

	def match(self, query, docstring):
		mode = self.mode_button["text"]
		if mode == _("Phrase"):
			# match only documents that contain the phrase:
			regex = re.compile(re.escape(query.lower()), re.DOTALL)
			if regex.search(docstring):
				return 1
		else:
			parts = re.split("\s+", query.strip())
			if mode == _("AND"):
				# match only documents that contain all words:
				match = 1
				for part in parts:
					regex = re.compile(re.escape(part.lower()), re.DOTALL)
					if not regex.search(docstring):
						match = 0
						break
				return match
			elif mode == _("OR"):
				# match documents that contain at leats one word:
				match = 0
				for part in parts:
					regex = re.compile(re.escape(part.lower()), re.DOTALL)
					if regex.search(docstring):
						match = 1
						break
				return match
			else:
				print("Error: unknown search mode '%s'" % mode)
		return 0

	def processFile(self, filename, query):
		suffix = self.getSuffix(filename)
		# needed for error messages
		fsfilename =filename
		try:
			# Handle OpenOffice.org files:
			if suffix in ('sxw', 'stw',		# OOo   1.x swriter
					'sxc', 'stc',		# OOo   1.x scalc
					'sxi', 'sti',		# OOo   1.x simpress
					'sxg',				# OOo   1.x master document
					'sxm',				# OOo   1.x formula
					'sxd', 'std',		# OOo   1.x sdraw
					'odt', 'ott',		# OOo > 2.x swriter
					'odp', 'otp',		# OOo > 2.x simpress
					'odf',				# OOo > 2.x formula
					'odg', 'otg',		# OOo > 2.x sdraw
					'ods', 'ots',		# OOo > 2.x scalc
					):
				zip = zipfile.ZipFile(filename, "r")
				content = ""
				docinfo = ""
				try:
					# search embedded files:
					filelist = zip.namelist()
					for filename in filelist:
						if filename.endswith("content.xml"):
							content += str(zip.read(filename), 'utf-8')

						if filename.endswith("document.xml"):
							content += str(zip.read(filename), 'utf-8')

					content = self.removeXMLMarkup(content, replace_with_space=0)
					docinfo = str(zip.read("meta.xml"), 'utf-8')
					docinfo = self.removeXMLMarkup(docinfo, replace_with_space=0)
					self.ooo_count = self.ooo_count + 1
				except KeyError as err:
					print("Warning: %s not found in '%s'" % (err, filename))
					return None
				# Patch for encrypted files
				except UnicodeDecodeError:
					print("Warning: cannot open '%s'" % (fsfilename))
					return None
				title = ""
				title_match = re.compile("<dc:title>(.*?)</dc:title>", re.DOTALL|re.IGNORECASE).search(docinfo)
				if title_match:
					title = title_match.group(1)
				if self.match(query, "%s %s" % (content.lower(), docinfo.lower())):
					return (filename, title)

			# Handle MS-Office (>= 2007) files:
			if suffix in ('docx', 'dotx',		# MS-Word Documents >= 2007
				'xlsx', 'xltx',		# MS-Excel-Documents >= 2007
				):
				zip = zipfile.ZipFile(filename, "r")
				content = ""
				docinfo = ""
				try:
					# search embedded files:
					filelist = zip.namelist()
					for filename in filelist:

						if filename.endswith("document.xml"):
							content += str(zip.read(filename), 'utf-8')

						if filename.endswith("sharedStrings.xml"):
							content += str(zip.read(filename), 'utf-8')

					content = self.removeXMLMarkup(content, replace_with_space=0)
					docinfo = str(zip.read("docProps/core.xml"), 'utf-8')
					docinfo = self.removeXMLMarkup(docinfo, replace_with_space=0)
					self.ooo_count = self.ooo_count + 1
				except KeyError as err:
					print("Warning: %s not found in '%s'" % (err, filename))
					return None
				# Patch for encrypted files
				except UnicodeDecodeError:
					print("Warning: cannot open '%s'" % (fsfilename))
					return None
				title = ""
				title_match = re.compile("<dc:title>(.*?)</dc:title>", re.DOTALL|re.IGNORECASE).search(docinfo)
				if title_match:
					title = title_match.group(1)
				if self.match(query, "%s %s" % (content.lower(), docinfo.lower())):
					return (filename, title)
			# Handle MS-Office (>= 2007) MS-PowerPoint files:
			if suffix in ('pptx', 		# MS-PowerPoint-Documents >= 2007
				):
				zip = zipfile.ZipFile(filename, "r")
				content = ""
				docinfo = ""
				try:
					# search embedded files:
					filelist = zip.namelist()
					slideslist = []
					for element in filelist:
						if element[4:12]  == "slides/s":
							slideslist.append(element)
					content =""
					for element in slideslist:
						content += str(zip.read(element), 'utf-8')

					content = self.removeXMLMarkup(content, replace_with_space=0)
					docinfo = str(zip.read("docProps/core.xml"), 'utf-8')
					docinfo = self.removeXMLMarkup(docinfo, replace_with_space=0)
				except KeyError as err:
					print("Warning: %s not found in '%s'" % (err, filename))
					return None
				# Patch for encrypted files
				except UnicodeDecodeError:
					print("Warning: cannot open '%s'" % (fsfilename))
					return None
				title = ""
				title_match = re.compile("<dc:title>(.*?)</dc:title>", re.DOTALL|re.IGNORECASE).search(docinfo)
				if title_match:
					title = title_match.group(1)
				if self.match(query, "%s %s" % (content.lower(), docinfo.lower())):
					return (filename, title)
				self.ooo_count = self.ooo_count + 1

		except zipfile.BadZipfile as err:
			print(_("Warning: Supposed ZIP file ") + filename + _("could not be opened: ") + str(err))
		except IOError as err:
			print(_("Warning: File ") + filename + _("could not be opened: ") + str(err))
		return None

	def startSearch(self, event=None):
		self.stopped = 0
		self.last_update = 0
		self.match_count = 0
		self.ooo_count = 0
		self.savestring = ""
		self.listbox.delete(0, END)
		self.stop_button["state"] = NORMAL
		self.quit_button["state"] = DISABLED
		if not os.path.exists(self.search_path.get()):
			tkinter.messagebox.showwarning(_("Error"), _("Path ") + self.search_path.get() +_("doesn\'t exist."))
		else:
			#start_time = time.time()
			self.recursiveSearch(self.search_path.get())
			#duration = time.time() - start_time
			#print("time=%.2f" % duration)
			count_str = "%s %d %s" % (_("in"), self.ooo_count, _("files"))
			if self.stopped:
				self.status.config(text="%d %s %s %s" % (self.match_count, _("matches so far"), count_str, _("(search stopped)")))
				self.stopped = 0
			else:
				self.status.config(text="%d %s %s" % (self.match_count, _("matches"), count_str))
		self.stop_button["state"] = DISABLED
		self.quit_button["state"] = NORMAL
		return

	def getSuffix(self, filename):
		suffix_match = re.compile(".*\.(.*)").match(filename)
		if suffix_match:
			suffix = suffix_match.group(1).lower()
		else:
			suffix = None
		return suffix

	def recursiveSearch(self, directory):
		len_limit = 15		# avoid resizing window
		dir_part = os.path.split(directory)[1]
		if len(dir_part) > len_limit:
			dir_part = "%s..." % dir_part[0:len_limit]
		#print("'%s'" % dir_part)
		self.status.config(text=_("Searching in ") + "%s" % dir_part)
		try:
			dir_content = os.listdir(directory)
			dir_content.sort(key=str.lower)
		except OSError as err:
			print(_("Warning: ") + directory + (": ") + str(err))
			return
		except UnicodeError as err:
			print(_("Warning: Unicode problem with directory name..."))
			return
		enable_save = False
		for filename in dir_content:
			if self.stopped != 0:
				return
			filename = os.path.join(directory, filename)
			if os.path.isfile(filename):
				match = self.processFile(filename, self.search_query.get())
				update_interval = 0.05
				time_diff = time.time() - self.last_update
				if time_diff > update_interval:
					self.master.update()
					self.last_update = time.time()
				if match:
					title = match[1]
					if not title:
						title = "Untitled"
					display_filename = filename.replace(self.search_path.get(), '')
					self.listbox.insert('end', "%s" % display_filename)
					self.savestring = self.savestring + filename + "\n"
					if enable_save == False:
						self.save_button["state"] = NORMAL
						enable_save = True
					self.match_count = self.match_count + 1
			elif os.path.isdir(filename) and not os.path.islink(filename):
				self.recursiveSearch(filename)
		return

	def doSave(self):
		savestring = _("Search path:") + " " + self.search_path.get() + "\n"
		savestring = savestring + _("Search terms:") + " " + self.search_query.get() + "\n"
		savestring = savestring + _("Mode:") + " " + self.mode_button["text"] + "\n\n"
		savestring = savestring + _("Matches:") + "\n" + self.savestring
		fo = tkinter.filedialog.asksaveasfile()
		fo.write(savestring)
		self.stopped = 1
		self.save_button["state"] = DISABLED
		return

# Begin gettext integration
class TranslationIntegration(object):
	"""GNU-gettext for GNU/Linux"""

	def __init__(self, pname):

		self.moname = pname + ".mo"
		self.syslanguage()
		# check, whether it's a unix system installation
		_localepath = "/usr/share/locale/"

		_result = self.look4mo(_localepath)

		if _result == "xxx":
			self.syslanguage()
			_localepath = "locale/"
			_result = self.look4mo(_localepath)

		if not _result == "xxx":
			# chooses the right .mo file and installs translation
			trans = gettext.translation(pname, _localepath, [self.language])
			trans.install()
		else:
			# Translating the following strings is absurd!
			serror = "Bad package! No "+self.moname+" file found!"
			from tkinter import messagebox
			messagebox.showerror('Error!', serror)
			import sys; sys.exit(1)

	def syslanguage(self):
		"""which is the language of the system (GNU/Linux)?"""
		_loclang = locale.getdefaultlocale()
		self.language = _loclang[0]
		if self.language == None:
			self.language = "en"
		return self.language

	def look4mo(self, localepath):
		"""choose only a language which a .mo file exists for,
		else use english or return 'xxx' """
		_lpath = localepath + self.language + "/LC_MESSAGES/" + self.moname

		if not os.path.exists(_lpath):

			_newlang = self.dosplit(self.language, "_")
			_lpath = localepath + _newlang + "/LC_MESSAGES/"+self.moname

			if os.path.exists(_lpath):
				self.language = _newlang
			else:
				_newlang = self.dosplit(self.language, "@")
				_lpath = localepath + _newlang + "/LC_MESSAGES/" + self.moname

				if os.path.exists(_lpath):
					self.language = _newlang
				else:
					_lpath = localepath + "en/LC_MESSAGES/" + self.moname

					if os.path.exists(_lpath):
						self.language = "en"
					else:
						self.language = "xxx"

		return self.language
	def dosplit(self, language, splitter):

		"""
			splits language descriptor

			>>> gnugettext.dosplit(gnugettext, "de_DE", "_")
			'de'

			>>> gnugettext.dosplit(gnugettext, "be@latin", "@")
			'be'
		"""

		_newlang = language.split(splitter)
		_newlang = _newlang[0]

		return _newlang

# End gettext integration

if __name__ == "__main__":

	version = "0.9.0"
	loookversion = "loook"

	g = TranslationIntegration(loookversion)

	if len(sys.argv) >= 2 and (sys.argv[1] == '--help' or sys.argv[1] == '-h'):
		print(_("Usage:") +" loook [-h|--help] " + _("[search path] [search term]..."))
		sys.exit(1)
	root = Tk()
	root.minsize(380, 200)
	root.title("Loook " + version + " - " + _("OpenOffice.org File Finder"))
	root.columnconfigure(1, weight=1)
	root.rowconfigure(4, weight=1)
	root.columnconfigure(1, weight=1)
	root.rowconfigure(5, weight=0)

	app = Application(root)
	root.protocol("WM_DELETE_WINDOW", app.doQuit)
	root.mainloop()
