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

# This file is part of the Gedit LaTeX Plugin
#
# Copyright (C) 2010 Michael Zeising
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public Licence as published by the Free Software
# Foundation; either version 2 of the Licence, 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 Licence for more
# details.
#
# You should have received a copy of the GNU General Public Licence along with
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# Street, Fifth Floor, Boston, MA  02110-1301, USA

"""
latex.editor
"""

BENCHMARK = True

import logging
if BENCHMARK: import time

from gi.repository import Gtk, GObject
from gi.repository import Gdk

from ..base.editor import Editor
from ..base.file import File
from ..issues import Issue, IIssueHandler
from ..util import verbose, open_error

from parser import LaTeXParser
from expander import LaTeXReferenceExpander
from outline import LaTeXOutlineGenerator
from validator import LaTeXValidator
from completion import LaTeXCompletionHandler

from dialogs import ChooseMasterDialog

from . import LaTeXSource
from ..preferences import Preferences, DocumentPreferences

LOG = logging.getLogger(__name__)

class LaTeXEditor(Editor, IIssueHandler):

    extensions = Preferences().get("latex-extensions").split(",")

    dnd_extensions = [".png", ".pdf", ".bib", ".tex"]

    @property
    def completion_handlers(self):
        self.__latex_completion_handler = LaTeXCompletionHandler()
        return [ self.__latex_completion_handler ]

    def init(self, file, context):
        """
        @param file: base.File
        @param context: base.WindowContext
        """
        LOG.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = DocumentPreferences(self._file)
        self._preferences.connect("preferences-changed", self._on_preferences_changed)

        self.register_marker_type("latex-error", self._preferences.get("error-background-color"))
        self.register_marker_type("latex-warning", self._preferences.get("warning-background-color"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        GObject.idle_add(self.__parse)
        self.__update_neighbors()

    def _on_preferences_changed(self, prefs, key, new_value):
        if key in ["outline-show-labels", "outline-show-tables", "outline-show-graphics"]:
            # regenerate outline model
            if self._document_is_master:
                self._outline = self._outline_generator.generate(self._document, self)
                self._outline_view.set_outline(self._outline)
            else:
                # FIXME: self._document contains the full model of child and master
                # so we may not use it for regenerating the outline here
                self.__parse()

    def drag_drop_received(self, files):
        # see base.Editor.drag_drop_received

        # TODO: we need to insert the source at the drop location - so pass it here

        LOG.debug("drag_drop: %s" % files)

#        if len(files) == 1:
#            file = files[0]
#            self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
#            if file.extension == ".png":
#                self._log.debug("PNG image - including...")
#                source = "\\includegraphics{%s}" % file.path
#                self.insert(source)

    def insert(self, source):
        # see base.Editor.insert

        if type(source) is LaTeXSource:
            if source.packages and len(source.packages) > 0:
                self.ensure_packages(source.packages)

            Editor.insert(self, source.source)
        else:
            Editor.insert(self, source)

    POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2

    def insert_at_position(self, source, position):
        """
        Insert source at a certain position in the LaTeX document:

         * POSITION_PACKAGES: after the last \usepackage statement
         * POSITION_BIBLIOGRAPHY: before \end{document}

        @param source: a LaTeXSource object
        @param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
        """

        if position == self.POSITION_BIBLIOGRAPHY:
            offset = self._document.end_of_document
            Editor.insert_at_offset(self, offset, source, True)
        elif position == self.POSITION_PACKAGES:
            offset = self._document.end_of_packages
            Editor.insert_at_offset(self, offset, source, False)
        else:
            raise NotImplementedError

    def ensure_packages(self, packages):
        """
        Ensure that certain packages are included

        @param packages: a list of package names
        """
        self.__parse()    # ensure up-to-date document model

        if not self._document_is_master:
            LOG.debug("ensure_packages: document is not a master")

            # find the packages that haven't already been mentioned
            info_packages = [p for p in packages if not p in self._ensured_packages]

            if len(info_packages) > 0:
                # generate markup
                li_tags = "\n".join([" • <tt>%s</tt>" % p for p in info_packages])

                from ..util import open_info
                open_info("LaTeX Package Required",
                        "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s" % li_tags)

                # extend the already mentioned packages
                self._ensured_packages.extend(info_packages)

            return

        # find missing packages
        present_package_names = [p.value for p in self._outline.packages]
        package_names = [p for p in packages if not p in present_package_names]

        # insert the necessary \usepackage commands
        if len(package_names) > 0:
            source = "\n" + "\n".join(["\\usepackage{%s}" % n for n in package_names])
            self.insert_at_position(source, self.POSITION_PACKAGES)

    def on_save(self):
        """
        The file has been saved

        Update models
        """

#        from multiprocessing import Process
#
#        p_parse = Process(target=self.__parse)
#        p_parse.start()

        self.__parse()

    def __update_neighbors(self):
        """
        Find all files in the working directory that are relevant for LaTeX, e.g.
        other *.tex files or images.
        """

        # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
        # know the edited file and the Editor should call an update() method of the handler
        # when the file is saved.

        tex_files = self._file.find_neighbors(".tex")
        bib_files = self._file.find_neighbors(".bib")

        graphic_files = []
        for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
            graphic_files.extend(self._file.find_neighbors(extension))

        self.__latex_completion_handler.set_neighbors(tex_files, bib_files, graphic_files)

    #@verbose
    def __parse(self):
        """
        Ensure that the document model is up-to-date
        """
        if self.content_changed(self._change_reference):
            # content has changed so document model may be dirty
            self._change_reference = self.current_timestamp

            LOG.debug("Parsing document...")

            # reset highlight
            self.remove_markers("latex-error")
            self.remove_markers("latex-warning")

            # reset issues
            self._issue_view.clear()

            if BENCHMARK: t = time.clock()

            # parse document
            if self._document != None:
                self._document.destroy()
                del self._document
            self._document = self._parser.parse(self.content, self._file, self)

            # update document preferences
            self._preferences.parse_content(self.content)

            if BENCHMARK: LOG.info("LaTeXParser.parse: %f" % (time.clock() - t))

            LOG.debug("Parsed %s bytes of content" % len(self.content))

            # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!

            if self._document.is_master:

                self._context.set_action_enabled("LaTeXChooseMasterAction", False)
                self._document_is_master = True

                # expand child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, self._file, self, self.charset)

                # generate outline from the expanded model
                self._outline = self._outline_generator.generate(self._document, self)

                # pass to view
                self._outline_view.set_outline(self._outline)

                # validate
                self._validator.validate(self._document, self._outline, self, self._preferences)
            else:
                LOG.debug("Document is not a master")

                self._context.set_action_enabled("LaTeXChooseMasterAction", True)
                self._document_is_master = False

                # the outline used by the outline view has to be created only from the child model
                # otherwise we see the outline of the master and get wrong offsets
                self._outline = self._outline_generator.generate(self._document, self)
                self._outline_view.set_outline(self._outline)

                # find master
                master_file = self.__master_file

                if master_file is None:
                    return

                # parse master
                master_content = open(master_file.path).read()
                self._document = self._parser.parse(master_content, master_file, self)

                # expand its child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, master_file, self, self.charset)

                # create another outline of the expanded master model to make elements
                # from the master available (labels, colors, BibTeX files etc.)
                self._outline = self._outline_generator.generate(self._document, self)

                # validate
                prefs = DocumentPreferences(master_file)
                prefs.parse_content(master_content)
                self._validator.validate(self._document, self._outline, self, prefs)

            # pass outline to completion
            self.__latex_completion_handler.set_outline(self._outline)

            # pass neighbor files to completion
            self.__update_neighbors()

            LOG.debug("Parsing finished")

    def choose_master_file(self):
        master_filename = ChooseMasterDialog().run(self._file.dirname)
        if master_filename:
            # relativize the master filename
            master_filename = File(master_filename).relativize(self._file.dirname, True)
            self._preferences.set("master-filename", master_filename)
        return master_filename

    @property
    def __master_file(self):
        """
        Find the LaTeX master of this child

        @return: base.File
        """
        path = self._preferences.get("master-filename")
        if path != None:
            if File.is_absolute(path):
                LOG.debug("master path is absolute")
                return File(path)
            else:
                LOG.debug("master path is relative")
                return File.create_from_relative_path(path, self._file.dirname)
        else:
            # master filename not found, ask user
            master_filename = self.choose_master_file()
            if master_filename:
                return File.create_from_relative_path(master_filename, self._file.dirname)
            else:
                return None

    def issue(self, issue):
        # see IIssueHandler.issue

        local = (issue.file == self._file)

        self._issue_view.append_issue(issue, local)

        if issue.file == self._file:
            if issue.severity == Issue.SEVERITY_ERROR:
                self.create_marker("latex-error", issue.start, issue.end)
            elif issue.severity == Issue.SEVERITY_WARNING:
                self.create_marker("latex-warning", issue.start, issue.end)

    def on_cursor_moved(self, offset):
        """
        The cursor has moved
        """
        if self._preferences.get("outline-connect-to-editor"):
            self._outline_view.select_path_by_offset(offset)

    @property
    def file(self):
        # overrides Editor.file

        # we may not call self._document.is_master because _document is always
        # replaced by the master model
        if self._document_is_master:
            return self._file
        else:
            return self.__master_file

    @property
    def edited_file(self):
        """
        Always returns the really edited file instead of the master

        This is called by the outline view to identify foreign nodes
        """
        return self._file

    def destroy(self):
        # unreference the window context
        del self._context

        # destroy the cached document
        self._document.destroy()
        del self._document

        Editor.destroy(self)

# ex:ts=4:et:
