#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-14  Stephane Galland <galland@arakhne.org>
#
# 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
#---------------------------------

# Standard libraries
import re
# Include the Gtk libraries
from gi.repository import GObject, GdkPixbuf, Gdk, Gio, Gtk
# AutoLaTeX includes
from autolatex.utils import latex_log_parser as log_parser

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

import gettext
_T = gettext.gettext

#---------------------------------
# CLASS ConsoleMode
#---------------------------------
class ConsoleMode:
  HIDE = 0
  OPTIONAL = 1
  SHOW = 2

#---------------------------------
# CLASS Panel
#---------------------------------

#
# Gtk panel that is managing the configuration of the plugin.
#
class Console(Gtk.ScrolledWindow):
  __gtype_name__ = "AutoLaTeXLaTeXConsole"

  # Constructor.
  # @param plugin - reference to the plugin. The plugin must provide
  #                 the attribute "window" that contains the
  #                 reference to the Gtk window that launched this,
  #                 and the function "do_update_state" that will be
  #                 invoked for notifying a state change.
  def __init__(self, plugin):
    Gtk.ScrolledWindow.__init__(self)
    self.window = plugin.window
    self.plugin = plugin
    self._re_file_match = re.compile("^(.+):([0-9]+):\\s*(.*?)\\s*$", re.DOTALL)
    self._document_directory = None
    self._latex_parser = None
    self._current_error = -1
    # Create the store
    self._messages = Gtk.ListStore(str, str, str, int, str)
    # Create the list
    self._message_widget = Gtk.TreeView()
    self._message_widget.set_size_request(200, 150)
    self._message_widget.set_model(self._messages)
    column = Gtk.TreeViewColumn("Level", Gtk.CellRendererPixbuf(), stock_id=0)
    self._message_widget.append_column(column)
    column = Gtk.TreeViewColumn("Text", Gtk.CellRendererText(), text=1)
    self._message_widget.append_column(column)
    self._message_widget.set_headers_clickable(False)
    self._message_widget.set_headers_visible(False)
    # Init the scroll
    self.add(self._message_widget)
    self.set_size_request(200, 150)
    self.set_policy(
      Gtk.PolicyType.AUTOMATIC,
      Gtk.PolicyType.AUTOMATIC)
    self.set_shadow_type(Gtk.ShadowType.IN)
    self.set_property('hexpand', True)
    self.set_property('vexpand', True)
    #
    self.show_all()
    #
    self._message_widget.connect(
      'button-press-event', self.on_list_click_action);

  # Set the text of the log.
  # @param log - message to output, if None the second parameter will be used.
  # @param latex_warnings - list of LaTeX warnings, if None the first parameter
  #                         will be used.
  # @param document_directory - name of the document's directory.
  def set_log(self, log, latex_warnings, document_directory):
    # Reset attributes
    self._latex_parser = None
    self._current_error = -1
    if (document_directory is None):
      self._document_directory = None;
    else:
      self._document_directory = Gio.File.new_for_path(document_directory)
    # Clear the list
    self._messages.clear()
    # Add the errors in the log
    if log:
      mo = re.match(self._re_file_match, log)
      if mo:
        filename = mo.group(1)
        linenumber = int(mo.group(2))
        message = mo.group(3)
      else:
        filename = ''
        linenumber = int(0)
        message = log
      ui_icon = Gtk.STOCK_DIALOG_ERROR
      if filename:
        m = filename
        if linenumber>0:
          m = m + ":" + str(linenumber)
        self._messages.append(
          [ ui_icon, m+"\n"+message, filename, linenumber, None ])
      else:
        self._messages.append(
          [ ui_icon, message, filename, linenumber, None ])
    elif latex_warnings:
      # Add the warnings
      for latex_warning in latex_warnings:
        ui_icon = Gtk.STOCK_JUMP_TO if latex_warning[2] else Gtk.STOCK_DIALOG_WARNING
        self._messages.append(
          [ ui_icon, latex_warning[0], latex_warning[1], int(0), latex_warning[2] ])
    if log:
      return ConsoleMode.SHOW
    if latex_warnings:
      return ConsoleMode.OPTIONAL
    return ConsoleMode.HIDE

  # Display the next error.
  def show_next_error(self):
    length = len(self._messages)
    if length>0 and self._current_error < (len(self._messages)-1):
      self._current_error = self._current_error + 1
      path = Gtk.TreePath(self._current_error)
      is_expanded = self._do_click_on_list(path)
      while is_expanded:
        is_expanded = self._do_click_on_list(path)
      self._message_widget.get_selection().select_path(path)
      self._message_widget.scroll_to_cell(
        path,
        self._message_widget.get_column(0),
        True, 0, 0)
      return ConsoleMode.SHOW
    return ConsoleMode.HIDE

  # Indicates if there is a "next" error.
  def has_next_error(self):
    length = len(self._messages)
    return length>0 and self._current_error < (length-1)

  # Display the previous error.
  def show_previous_error(self):
    if self._current_error > 0:
      self._current_error = self._current_error - 1
      path = Gtk.TreePath(self._current_error)
      is_expanded = self._do_click_on_list(path)
      while is_expanded:
        is_expanded = self._do_click_on_list(path)
      self._message_widget.get_selection().select_path(path)
      self._message_widget.scroll_to_cell(
        path,
        self._message_widget.get_column(0),
        True, 0, 0)
      return ConsoleMode.SHOW
    return ConsoleMode.HIDE

  # Replies if there is a "previous" error.
  def has_previous_error(self):
    return self._current_error > 0

  # Replace a "generic" error message for "undefined references" by
  # the detailled list of the undefined references.
  # @param list_iter - list of the undefined references.
  # @param log_file - source of the error messages.
  def _replace_by_undefined_reference_warnings(self, list_iter, log_file):
    if not self._latex_parser:
      self._latex_parser = log_parser.Parser(log_file)
    warnings = self._latex_parser.get_undefined_reference_warnings()
    if warnings:
      ui_icon = Gtk.STOCK_DIALOG_WARNING
      for warning in reversed(warnings):
        self._messages.insert_before(list_iter,
          [ ui_icon,
            warning.get_message(),
            warning.get_filename(),
            int(warning.get_line_number()),
            None ])
    else:
      # Issue 53: sometimes the LaTeX tool is saying
      # "There were undefined references" for a citation.
      warnings = self._latex_parser.get_undefined_citation_warnings()
      if warnings:
        ui_icon = Gtk.STOCK_DIALOG_WARNING
        for warning in reversed(warnings):
          self._messages.insert_before(list_iter,
            [ ui_icon,
              warning.get_message(),
              warning.get_filename(),
              int(warning.get_line_number()),
              None ])
      else:
        ui_icon = Gtk.STOCK_DIALOG_ERROR
        self._messages.insert_before(list_iter,
          [ ui_icon,
            _T("Internal Error: Unable to retreive the undefined references from the LaTeX log"),
            None,
            int(0),
            None ])
    self._messages.remove(list_iter)

  # Replace a "generic" error message for "multidefined labels" by
  # the detailled list of the multidefined labels.
  # @param list_iter - list of the multidefined labels.
  # @param log_file - source of the error messages.
  def _replace_by_multidefined_label_warnings(self, list_iter, log_file):
    if not self._latex_parser:
      self._latex_parser = log_parser.Parser(log_file)
    warnings = self._latex_parser.get_multidefined_label_warnings()
    if warnings:
      ui_icon = Gtk.STOCK_DIALOG_WARNING
      for warning in reversed(warnings):
        self._messages.insert_before(list_iter,
          [ ui_icon,
            warning.get_message(),
            warning.get_filename(),
            int(warning.get_line_number()),
            None ])
    else:
      ui_icon = Gtk.STOCK_DIALOG_ERROR
      self._messages.insert_before(list_iter,
        [ ui_icon,
          _T("Internal Error: Unable to retreive the multidefined references from the LaTeX log"),
          None,
          int(0),
          None ])
    self._messages.remove(list_iter)

  # Replace a "generic" error message for "undefined citations" by
  # the detailled list of the undefined citations.
  # @param list_iter - list of the undefined citations.
  # @param log_file - source of the error messages.
  def _replace_by_undefined_citation_warnings(self, list_iter, log_file):
    if not self._latex_parser:
      self._latex_parser = log_parser.Parser(log_file)
    warnings = self._latex_parser.get_undefined_citation_warnings()
    if warnings:
      ui_icon = Gtk.STOCK_DIALOG_WARNING
      for warning in reversed(warnings):
        self._messages.insert_before(list_iter,
          [ ui_icon,
            warning.get_message(),
            warning.get_filename(),
            int(warning.get_line_number()),
            None ])
    else:
      ui_icon = Gtk.STOCK_DIALOG_ERROR
      self._messages.insert_before(list_iter,
        [ ui_icon,
          _T("Internal Error: Unable to retreive the undefined citations from the LaTeX log"),
          None,
          int(0),
          None ])
    self._messages.remove(list_iter)

  # Software execution of a "click" on the error message pointed
  # by the given path.
  def _do_click_on_list(self, path):
    if path:
      list_iter = self._messages.get_iter(path)
      row = self._messages[list_iter]
      # Get the filename and the line number
      filename = row[2]
      wcode = row[4]
      if wcode:
        if wcode == 'W1':
          self._replace_by_multidefined_label_warnings(list_iter, filename)
          return True
        elif wcode == 'W2':
          self._replace_by_undefined_reference_warnings(list_iter, filename)
          return True
        elif wcode == 'W3':
          self._replace_by_undefined_citation_warnings(list_iter, filename)
          return True
      elif filename and self._document_directory:
        linenumber = row[3]
        filename = self._document_directory.resolve_relative_path(filename)
        linenumber = linenumber if linenumber>=1 else 1
        # Open the file or select the tab
        tab = self.window.get_tab_from_location(filename)
        if tab:
          # Switch to the tab and show the line
          self.window.set_active_tab(tab)
          document = tab.get_document()
          view = tab.get_view()
          line_iter = document.get_iter_at_line(linenumber - 1)
          view.scroll_to_iter(
            line_iter,
            0,
            True,
            0, 0.5)
          view.grab_focus()
        else:
          # Open a new tab at the line
          tab = self.window.create_tab_from_location(
            filename,
            None, # encoding
            linenumber, # row
            0, # column
            False, # Do not create an empty file
            True) # Switch to the tab
          view = tab.get_view()
          view.grab_focus()
        GObject.idle_add(self._on_differed_selection, tab, linenumber)
    return False

  # Private handler for the asynchronous highlight of the error's line
  # in the text editor.
  def _on_differed_selection(self, tab, linenumber):
    if tab:
      document = tab.get_document()
      line_iter = document.get_iter_at_line(linenumber - 1)
      end_line_iter = line_iter.copy()
      end_line_iter.forward_to_line_end()
      document.select_range(line_iter, end_line_iter)    

  # Handle the click on the Gtk list
  def on_list_click_action(self, widget, event, data=None):
    if (event.button==1 and (event.type==Gdk.EventType._2BUTTON_PRESS or event.type==Gdk.EventType._3BUTTON_PRESS)) or event.button==2:
      x, y = event.get_coords()
      path, column, cell_x, cell_y = widget.get_path_at_pos(x,y)
      if path:
        self._current_error = (int)(path.get_indices()[0])
      else:
        self._current_error = -1
      self._do_click_on_list(path)
      self.plugin.do_update_state()

