#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-14  Stephane Galland <[email protected]>
#
# 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
# (at your option) 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; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

#---------------------------------
# IMPORTS
#---------------------------------

# Import standard python libs
import os
import tempfile
import re
import gettext
# Include the Glib, Gtk and Gedit libraries
from gi.repository import GObject, Gtk, Gio, GdkPixbuf, Gedit, PeasGtk

# AutoLaTeX shared libs

from autolatex.utils import utils as autolatex_utils
from autolatex.utils import gsettings as autolatex_gsettings
from autolatex.config import window as cli_config

# AutoLaTeX-Gedit internal libs

from .utils import gedit_runner
from .config import main_panel as plugin_config
from .widgets import latex_console

#---------------------------------
# PLUGIN CONFIGURATION
#---------------------------------

autolatex_utils.init_plugin_configuration(__file__, 'autolatex-gedit3')

#---------------------------------
# INTERNATIONALIZATION
#---------------------------------

_T = gettext.gettext

#---------------------------------
# CLASS AutoLaTeXPlugin
#---------------------------------

# Plugin for Gedit
class AutoLaTeXPlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable):
 __gtype_name__ = "AutoLaTeXPlugin"
 window = GObject.property(type=Gedit.Window)

 #Constructor
 def __init__(self):
   GObject.Object.__init__(self)
   self._status_bar_context_id = None
   self._compilation_under_progress = False # Indicate if the compilation is under progress
   self._console_icon = None # Icon of the error console
   self._gsettings = autolatex_gsettings.Manager()
   self._syntex_regex = re.compile('\%.*mainfile:\s*(.*)$')

 # Invoked when the configuration window is open
 def do_create_configure_widget(self):
   return plugin_config.Panel(self._gsettings, self.window)

 # Invoked when the plugin is activated
 def do_activate(self):
   self._console_icon = self._get_icon('console')
   self._latex_console = latex_console.Console(self) # Current instance of the error console
   if not self._gsettings:
     self._gsettings = autolatex_gsettings.Manager()
   self._add_ui()
   self._check_autolatex_binaries()

 # Invoke when the plugin is desactivated
 def do_deactivate(self):
   gedit_runner.kill_all_runners()
   self._remove_ui()
   self._gsettings.unbind()
   self._gsettings = None

 # Check if the AutoLaTeX binaries were found
 def _check_autolatex_binaries(self):
   if not autolatex_utils.AUTOLATEX_BINARY and not autolatex_utils.AUTOLATEX_BACKEND_BINARY:
     dialog = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _T("The programs 'autolatex' and 'autolatex-backend'\nwere not found.\nPlease fix the configuration of the AutoLaTeX plugin."))
     answer = dialog.run()
     dialog.destroy()
   elif not autolatex_utils.AUTOLATEX_BINARY:
     dialog = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _T("The program 'autolatex' was not found.\nPlease fix the configuration of the AutoLaTeX plugin."))
     answer = dialog.run()
     dialog.destroy()
   elif not autolatex_utils.AUTOLATEX_BACKEND_BINARY:
     dialog = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _T("The program 'autolatex-backend' was not found.\nPlease fix the configuration of the AutoLaTeX plugin."))
     answer = dialog.run()
     dialog.destroy()

 # Invoke when the UI is updated
 def do_update_state(self):
   directory = self._find_AutoLaTeX_dir()
   hasTeXDocument = self._is_TeX_document()
   hasAutoLaTeXDocument = (directory is not None)
   isInTeXContext = (hasTeXDocument or hasAutoLaTeXDocument)
   # Display or hide the menus
   self._menu.set_visible(isInTeXContext)
   self._document_actions.set_visible(isInTeXContext)
   self._texsensitive_actions.set_visible(isInTeXContext)
   self._general_actions.set_visible(isInTeXContext)

   if directory:
     cfgFile = autolatex_utils.get_autolatex_document_config_file(directory)
     hasDocConfFile = os.path.exists(cfgFile)
   else:
     hasDocConfFile = False
   hasUserConfFile = os.path.exists(autolatex_utils.get_autolatex_user_config_file())
   # Change the sensitivity
   if self._document_actions:
     self._document_actions.set_sensitive(hasAutoLaTeXDocument and not self._compilation_under_progress)
   if self._texsensitive_actions:
     self._texsensitive_actions.set_sensitive(hasTeXDocument and not self._compilation_under_progress)
   if self._docconfsensitive_actions:
     self._docconfsensitive_actions.set_sensitive(hasDocConfFile and not self._compilation_under_progress)
   if self._userconfsensitive_actions:
     self._userconfsensitive_actions.set_sensitive(hasUserConfFile and not self._compilation_under_progress)
   action = self._document_actions.get_action('AutoLaTeXNextError')
   assert action is not None
   action.set_sensitive(self._latex_console.has_next_error())
   action = self._document_actions.get_action('AutoLaTeXPreviousError')
   assert action is not None
   action.set_sensitive(self._latex_console.has_previous_error())

 def _open_latex_console(self, set_visible=True):
   bottom_panel = self.window.get_bottom_panel()
   console_parent = self._latex_console.get_parent()
   if (console_parent is None):
     bottom_panel.add_item(self._latex_console,
         "autolatex-console-panel",
         _T("AutoLaTeX Console"),
         Gtk.Image.new_from_pixbuf(self._console_icon))
   bottom_panel.activate_item(self._latex_console)
   if set_visible and bottom_panel.get_property("visible") == False:
     bottom_panel.set_property("visible", True)

 # Update the UI according to the flag "compilation under progress"
 # and to compilation outputs
 def _update_action_validity(self, valid, console_content, latex_warnings):
   bottom_panel = self.window.get_bottom_panel()
   statusbar = self.window.get_statusbar()
   statusbar.remove_all(self._statusbar_id)
   # Display or hide the error console if an error message is given or not
   show_console = self._latex_console.set_log(
           console_content,
           latex_warnings,
           self._find_AutoLaTeX_dir())
   if show_console != latex_console.ConsoleMode.HIDE:
     self._open_latex_console(show_console == latex_console.ConsoleMode.SHOW)
   # Update the status bar
   if show_console == latex_console.ConsoleMode.OPTIONAL and bottom_panel.get_property("visible") == False:
     statusbar.push(self._statusbar_id,
         _T("LaTeX warnings were found. Please open the bottom panel to see them."))
   # Update the sensitivities of the Widgets
   self._compilation_under_progress = not valid
   GObject.idle_add(self.do_update_state)

 # Load an icon from the AutoLaTeX package
 def _get_icon(self, icon):
   return GdkPixbuf.Pixbuf.new_from_file(autolatex_utils.make_toolbar_icon_path('autolatex-'+icon+'.png'))


 # Add any contribution to the Gtk UI
 def _add_ui(self):
   # Get status bar id
   self._statusbar_id = self.window.get_statusbar().get_context_id('gedit-autolatex-plugin')
   # Get the UI manager
   manager = self.window.get_ui_manager()
   # Create the Top menu for AutoLaTeX
   self._menu = Gtk.ActionGroup("AutoLaTeXMenu")
   self._menu.add_actions([
     ('AutoLaTeXMenu', None, _T("AutoLaTeX"),
     None, _T("AutoLaTeX"),
     None),
   ])
   manager.insert_action_group(self._menu)
   # Create the menu for SyncTeX
   self._synctex_menu = Gtk.ActionGroup("AutoLaTeXSyncTeXMenu")
   self._synctex_menu.add_actions([
     ('AutoLaTeXSyncTeXMenu', Gtk.STOCK_DISCONNECT, _T("SyncTeX"),
     None, _T("SyncTeX"),
     None),
   ])
   manager.insert_action_group(self._synctex_menu)
   # Create the group of actions that are needing an AutoLaTeX document
   self._document_actions = Gtk.ActionGroup("AutoLaTeXDocumentActions")
   self._document_actions.add_actions([
     ('AutoLaTeXGenerateImageAction', None, _T("Generate images"),
     None, _T("Generate the images with AutoLaTeX"),
     self.on_generateimage_action_activate),
     ('AutoLaTeXCompileAction', None, _T("Compile"),
     '<ctrl>B', _T("Compile with AutoLaTeX"),
     self.on_compile_action_activate),
     ('AutoLaTeXCleanAction', None, _T("Remove temporary files"),
     None, _T("Clean with AutoLaTeX"),
     self.on_clean_action_activate),
     ('AutoLaTeXCleanallAction', None, _T("Clean all"),
     None, _T("Clean all with AutoLaTeX"),
     self.on_cleanall_action_activate),
     ('AutoLaTeXViewAction', None, _T("View the PDF"),
     None, _T("Open the PDF viewer"),
     self.on_view_action_activate),
     ('AutoLaTeXMakeFlatAction', None, _T("Create flat version of the TeX document"),
     None, _T("Create a flat version of the document, to be submitted to on-line publication systems (Elsevier...)"),
     self.on_makeflat_action_activate),
     ('AutoLaTeXNextError', None, _T("Show next error/warning"),
     'F4', _T("Show the next error or warning from the LaTeX console"),
     self.on_autolatex_next_error_action),
     ('AutoLaTeXPreviousError', None, _T("Show previous error/warning"),
     '<shift>F4', _T("Show the previous error or warning from the LaTeX console"),
     self.on_autolatex_previous_error_action),
   ])
   manager.insert_action_group(self._document_actions)
   # Create the group of actions that are needing an TeX document
   self._texsensitive_actions = Gtk.ActionGroup("AutoLaTeXTeXSensitiveActions")
   self._texsensitive_actions.add_actions([
     ('AutoLaTeXDocumentConfAction', None, _T("Document configuration"),
     None, _T("Change the configuration for the document"),
     self.on_document_configuration_action_activate),
   ])
   manager.insert_action_group(self._texsensitive_actions)
   # Create the group of actions that are needing the configuration file of a document
   self._docconfsensitive_actions = Gtk.ActionGroup("AutoLaTeXDocConfSensitiveActions")
   self._docconfsensitive_actions.add_actions([
     ('AutoLaTeXRemoveDocumentConfAction', None, _T("Delete document configuration"),
     None, _T("Delete the configuration for the document"),
     self.on_delete_document_configuration_action_activate),
   ])
   manager.insert_action_group(self._docconfsensitive_actions)
   # Create the group of actions that are needing the configuration file of the user
   self._userconfsensitive_actions = Gtk.ActionGroup("AutoLaTeXUserConfSensitiveActions")
   self._userconfsensitive_actions.add_actions([
     ('AutoLaTeXRemoveUserConfAction', None, _T("Delete user configuration"),
     None, _T("Delete the configuration for the user"),
     self.on_delete_user_configuration_action_activate),
   ])
   manager.insert_action_group(self._userconfsensitive_actions)
   # Create the group of actions that are not needing any special document
   self._general_actions = Gtk.ActionGroup("AutoLaTeXGeneralActions")
   self._general_actions.add_toggle_actions([
     ('AutoLaTeXEnableSyncTeXAction', None, _T("Force the use of SyncTeX"),
     None, _T("Use SyncTeX even if the document and user configurations say 'no'"),
     self.on_enable_synctex_action_activate),
   ])
   self._general_actions.add_actions([
     ('AutoLaTeXUpdateForSyncTeXAction', None, _T("Update TeX file with SyncTeX reference"),
     None, _T("Update the text of the TeX file to add the reference to the main document"),
     self.on_update_for_synctex_action_activate),
     ('AutoLaTeXUserConfAction', None, _T("User configuration"),
     None, _T("Change the configuration for the user"),
     self.on_user_configuration_action_activate),
   ])
   manager.insert_action_group(self._general_actions)
   # Put the icons into the actions
   for definition in [    (self._document_actions, 'AutoLaTeXGenerateImageAction', 'images'),
           (self._document_actions, 'AutoLaTeXCompileAction', 'compile'),
           (self._document_actions, 'AutoLaTeXCleanAction', 'clean'),
           (self._document_actions, 'AutoLaTeXCleanallAction', 'cleanall'),
           (self._document_actions, 'AutoLaTeXViewAction', 'view'),
           (self._texsensitive_actions, 'AutoLaTeXDocumentConfAction', 'preferences'),
           (self._general_actions, 'AutoLaTeXUserConfAction', 'preferences')
         ]:
     action = definition[0].get_action(definition[1])
     action.set_gicon(self._get_icon(definition[2]))
   # Add the Gtk contributions
   ui_path = os.path.join(autolatex_utils.AUTOLATEX_APP_PATH, 'ui')
   self._ui_merge_ids = []
   for ui_file in [ 'menu.ui', 'toolbar.ui' ]:
     self._ui_merge_ids.append(manager.add_ui_from_file(os.path.join(ui_path, ui_file)))
   manager.ensure_update()
   # Change the state of the check-boxes
   checkbox = self._general_actions.get_action('AutoLaTeXEnableSyncTeXAction')
   checkbox.set_active(self._gsettings.get_force_synctex())
   # Connect from gsettings
   self._gsettings.connect('force-synctex', self.on_gsettings_changed)
   self._gsettings.connect('save-before-run-autolatex', self.on_gsettings_changed)

 # Remove all contributions to the Gtk UI
 def _remove_ui(self):
   # Disconnect from gsettings
   self._gsettings.disconnect('force-synctex')
   self._gsettings.disconnect('save-before-run-autolatex')
   # Remove the error console
   if self._latex_console:
     if self._latex_console.get_parent() is not None:
       panel = self.window.get_bottom_panel()
       panel.remove_item(self._latex_console)
     self._latex_console = None
   # Remove the Gtk Widgets
   manager = self.window.get_ui_manager()
   manager.remove_action_group(self._document_actions)
   manager.remove_action_group(self._texsensitive_actions)
   manager.remove_action_group(self._docconfsensitive_actions)
   manager.remove_action_group(self._userconfsensitive_actions)
   manager.remove_action_group(self._general_actions)
   manager.remove_action_group(self._synctex_menu)
   manager.remove_action_group(self._menu)
   for merge_id in self._ui_merge_ids:
     manager.remove_ui(merge_id)
   manager.ensure_update()

 # Replies if the active document is a TeX document
 def _is_TeX_document(self):
   doc = self.window.get_active_document()
   if doc:
     doc = Gedit.Document.get_location(doc)
     if doc:
       return autolatex_utils.is_TeX_document(doc.get_path())
   return False

 # Try to find the directory where an AutoLaTeX configuration file is
 # located. The search is traversing the parent directory from the current
 # document.
 def _find_AutoLaTeX_dir(self):
   adir = None
   doc = self.window.get_active_document()
   if doc:
     doc = Gedit.Document.get_location(doc)
     if doc:
       return autolatex_utils.find_AutoLaTeX_directory(doc.get_path())
   return adir

 def _save_documents(self):
   for document in self.window.get_unsaved_documents():
     is_untitled = document.is_untitled()
     is_deleted = document.get_deleted()
     is_readonly = document.get_readonly()
     if not is_untitled and not is_deleted and not is_readonly :
       document.save(Gedit.DocumentSaveFlags.IGNORE_MTIME)

 def _apply_general_autolatex_cli_options(self, params):
   if self._gsettings.get_force_synctex():
     params = [ '--synctex' ] + params
   params = [ autolatex_utils.DEFAULT_LOG_LEVEL ] + params
   return params

 def on_clean_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Removing the generated files (except the figures)"),
         'clean', self._apply_general_autolatex_cli_options(
         [ '--noview' ]),
         False)

 def on_cleanall_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Removing the generated files and figures"),
         'cleanall', self._apply_general_autolatex_cli_options(
         [ '--noview' ]),
         False)

 def on_compile_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Generating the document"),
         'all', self._apply_general_autolatex_cli_options(
         [ '--noview' ]),
         True)

 def on_generateimage_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Generating the figures with the translators"),
         'images', self._apply_general_autolatex_cli_options(
         [ '--noview' ]),
         False)

 def on_view_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Launching the viewer"),
         'view', self._apply_general_autolatex_cli_options(
         [ '--asyncview' ]),
         True)

 def on_document_configuration_action_activate(self, action, data=None):
   directory = self._find_AutoLaTeX_dir()
   if directory:
     cli_config.open_configuration_dialog(self.window, True, directory)

 def on_delete_document_configuration_action_activate(self, action, data=None):
   directory = self._find_AutoLaTeX_dir()
   if directory:
     cfgFile = autolatex_utils.get_autolatex_document_config_file(directory)
     if os.path.exists(cfgFile):
       dialog = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _T("Do you want to delete the document configuration?"))
       answer = dialog.run()
       dialog.destroy()
       if answer == Gtk.ResponseType.YES:
         os.unlink(cfgFile)
         self.do_update_state()

 def on_user_configuration_action_activate(self, action, data=None):
   directory = self._find_AutoLaTeX_dir()
   if directory:
     cli_config.open_configuration_dialog(self.window, False, directory)

 def on_delete_user_configuration_action_activate(self, action, data=None):
   cfgFile = autolatex_utils.get_autolatex_user_config_file()
   if os.path.exists(cfgFile):
     dialog = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _T("Do you want to delete the user configuration?"))
     answer = dialog.run()
     dialog.destroy()
     if answer == Gtk.ResponseType.YES:
       os.unlink(cfgFile)
       self.do_update_state()

 def on_enable_synctex_action_activate(self, action, data=None):
   checkbox = self._general_actions.get_action('AutoLaTeXEnableSyncTeXAction')
   self._gsettings.set_force_synctex(checkbox.get_active())

 def on_update_for_synctex_action_activate(self, action, data=None):
   if self._is_TeX_document():
     directory = self._find_AutoLaTeX_dir()
     view = self.window.get_active_view()
     text_buffer = view.get_buffer()
     found = None
     # Search in the first tree lines
     if text_buffer.get_line_count() > 0:
       found = self.__search_for_synctex_flag(text_buffer, 0)
     # Search in the last tree lines
     if not found and text_buffer.get_line_count() > 3:
       found = self.__search_for_synctex_flag(text_buffer,
               max(3,text_buffer.get_line_count()-3))
     # Add the SyncTeX flag
     if not found:
       private_config = autolatex_utils.backend_get_configuration(
                 directory,
                 'all', '__private__');
       main_file = private_config.get('input', 'latex file', '');
       current_dir = Gio.File.new_for_path(os.getcwd())
       main_file = current_dir.resolve_relative_path(main_file).get_path()
       current_document = self.window.get_active_document()
       document_file = Gedit.Document.get_location(current_document)
       if document_file:
         document_filename = current_dir.resolve_relative_path(document_file.get_path())
         document_filename = document_filename.get_path()
         if main_file != document_filename:
           document_dir = document_file.get_parent()
           rel_path = os.path.relpath(main_file, document_dir.get_path())
           text_buffer.insert_interactive(
                 text_buffer.get_iter_at_line(0),
                 unicode("% mainfile: "+rel_path+"\n"),
                 -1,
                 view.get_editable())

 def __search_for_synctex_flag(self, text_buffer, line_number):
   found = None
   i = 0
   text_iter1 = text_buffer.get_iter_at_line(line_number)
   while text_iter1 and i<3 and not found:
     text_iter2 = text_iter1.copy();
     text_iter2.forward_to_line_end()
     line = text_iter1.get_visible_text(text_iter2)
     mo = re.match(self._syntex_regex, line)
     if mo:
       found = mo.group(1)
     text_iter1 = text_iter2
     if not text_iter1.forward_line():
       i = 4
     else:
       i = i + 1
   return found

 def on_gsettings_changed(self, settings, key, data=None):
   if key == 'force-synctex':
     checkbox = self._general_actions.get_action('AutoLaTeXEnableSyncTeXAction')
     checkbox.set_active(self._gsettings.get_force_synctex())
   elif key == 'save-before-run-autolatex':
     pass

 def on_enable_synctex_action_activate(self, action, data=None):
   checkbox = self._general_actions.get_action('AutoLaTeXEnableSyncTeXAction')
   self._gsettings.set_force_synctex(checkbox.get_active())

 def on_makeflat_action_activate(self, action, data=None):
   self._launch_AutoLaTeX(
         _T("Making the \"flat\" version of the document"),
         'makeflat', self._apply_general_autolatex_cli_options(
         [ '--noview' ],
         True))

 def on_autolatex_next_error_action(self, action, data=None):
   show_console = self._latex_console.show_next_error()
   if show_console != latex_console.ConsoleMode.HIDE:
     self._open_latex_console(show_console == latex_console.ConsoleMode.SHOW)
     self.do_update_state()

 def on_autolatex_previous_error_action(self, action, data=None):
   show_console = self._latex_console.show_previous_error()
   if show_console != latex_console.ConsoleMode.HIDE:
     self._open_latex_console(show_console == latex_console.ConsoleMode.SHOW)
     self.do_update_state()

 def _launch_AutoLaTeX(self, label, directive, params, enable_saving):
   directory = self._find_AutoLaTeX_dir()
   if directory:
     GObject.idle_add(self._update_action_validity, False, None, None)

     # Save the documents if necessary
     if enable_saving and self._gsettings.get_save_before_run_autolatex():
       self._save_documents()

     thread = gedit_runner.Runner(
           self,
           label,
           self._gsettings.get_progress_info_visibility(),
           directory,
           directive,
           params)
     thread.start()