#    Gedit snippets plugin
#    Copyright (C) 2005-2006  Jesse van den Kieboom <jesse@icecrew.nl>
#
#    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; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

from gi.repository import Gio, Gtk

import sys

from .placeholder import PlaceholderEnd, PlaceholderMirror, Placeholder, PlaceholderShell, PlaceholderEval, PlaceholderRegex, PlaceholderExpand
from .parser import Parser
from . import helper

class EvalUtilities:
    def __init__(self, view=None):
        self.view = view
        self._init_namespace()

    def _init_namespace(self):
        self.namespace = {
            '__builtins__': __builtins__,
            'align': self.util_align,
            'readfile': self.util_readfile,
            'filesize': self.util_filesize
        }

    def _real_len(self, s, tablen = 0):
        if tablen == 0:
            tablen = self.view.get_tab_width()

        return len(s.expandtabs(tablen))

    def _filename_to_uri(self, filename):
        gfile = Gio.file_new_for_path(filename)

        return gfile.get_uri()

    def util_readfile(self, filename):
        stream = Gio.file_new_for_path(filename).read()

        if not stream:
            return ''

        res = stream.read()
        stream.close()

        return res

    def util_filesize(self, filename):
        gfile = Gio.file_new_for_path(filename)
        info = gfile.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE)

        if not info:
            return 0

        return info.get_size()

    def util_align(self, items):
        maxlen = []
        tablen = self.view.get_tab_width()

        for row in range(0, len(items)):
            for col in range(0, len(items[row]) - 1):
                if row == 0:
                    maxlen.append(0)

                items[row][col] += "\t"
                rl = self._real_len(items[row][col], tablen)

                if (rl > maxlen[col]):
                    maxlen[col] = rl

        result = ''

        for row in range(0, len(items)):
            for col in range(0, len(items[row]) - 1):
                item = items[row][col]

                result += item + ("\t" * int(((maxlen[col] - \
                        self._real_len(item, tablen)) / tablen)))

            result += items[row][len(items[row]) - 1]

            if row != len(items) - 1:
                result += "\n"

        return result

class Snippet:
    def __init__(self, data, environ = {}):
        self.data = data
        self.environ = environ

    def __getitem__(self, prop):
        return self.data[prop]

    def __setitem__(self, prop, value):
        self.data[prop] = value

    def accelerator_display(self):
        accel = self['accelerator']

        if accel:
            keyval, mod = Gtk.accelerator_parse(accel)
            accel = Gtk.accelerator_get_label(keyval, mod)

        return accel or ''

    def display(self):
        nm = helper.markup_escape(self['description'])

        tag = self['tag']
        accel = self.accelerator_display()
        detail = []

        if tag and tag != '':
            detail.append(tag)

        if accel and accel != '':
            detail.append(accel)

        if not detail:
            return nm
        else:
            return nm + ' (<b>' + helper.markup_escape(', '.join(detail)) + \
                    '</b>)'

    def _add_placeholder(self, placeholder):
        if placeholder.tabstop in self.placeholders:
            if placeholder.tabstop == -1:
                self.placeholders[-1].append(placeholder)
                self.plugin_data.ordered_placeholders.append(placeholder)
        elif placeholder.tabstop == -1:
            self.placeholders[-1] = [placeholder]
            self.plugin_data.ordered_placeholders.append(placeholder)
        else:
            self.placeholders[placeholder.tabstop] = placeholder
            self.plugin_data.ordered_placeholders.append(placeholder)

    def _insert_text(self, text):
        # Insert text keeping indentation in mind
        indented = str.join('\n' + self._indent, helper.spaces_instead_of_tabs(self._view, text).split('\n'))
        self._view.get_buffer().insert(self._insert_iter(), indented)

    def _insert_iter(self):
        return self._view.get_buffer().get_iter_at_mark(self._insert_mark)

    def _create_environment(self, data):
        if data in self.environ['utf8']:
            val = self.environ['utf8'][data]
        else:
            val = ''

        # Get all the current indentation
        all_indent = helper.compute_indentation(self._view, self._insert_iter())

        # Substract initial indentation to get the snippet indentation
        indent = all_indent[len(self._indent):]

        # Keep indentation
        return str.join('\n' + indent, val.split('\n'))

    def _create_placeholder(self, data):
        tabstop = data['tabstop']
        begin = self._insert_iter()

        if tabstop == 0:
            # End placeholder
            return PlaceholderEnd(self._view, self.environ, begin, data['default'])
        elif tabstop in self.placeholders:
            # Mirror placeholder
            return PlaceholderMirror(self._view, tabstop, self.environ, begin)
        else:
            # Default placeholder
            return Placeholder(self._view, tabstop, self.environ, data['default'], begin)

    def _create_shell(self, data):
        begin = self._insert_iter()
        return PlaceholderShell(self._view, data['tabstop'], self.environ, begin, data['contents'])

    def _create_eval(self, data):
        begin = self._insert_iter()
        return PlaceholderEval(self._view, data['tabstop'], self.environ, data['dependencies'], begin, data['contents'], self._utils.namespace)

    def _create_regex(self, data):
        begin = self._insert_iter()
        return PlaceholderRegex(self._view, data['tabstop'], self.environ, begin, data['input'], data['pattern'], data['substitution'], data['modifiers'])

    def _create_text(self, data):
        return data

    def _invalid_placeholder(self, placeholder, remove):
        buf = self._view.get_buffer()

        # Remove the text because this placeholder is invalid
        if placeholder.default and remove:
            buf.delete(placeholder.begin_iter(), placeholder.end_iter())

        placeholder.remove()

        if placeholder.tabstop == -1:
            index = self.placeholders[-1].index(placeholder)
            del self.placeholders[-1][index]
        else:
            del self.placeholders[placeholder.tabstop]

        self.plugin_data.ordered_placeholders.remove(placeholder)

    def _parse(self, plugin_data):
        # Initialize current variables
        self._view = plugin_data.view
        self._indent = helper.compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark))
        self._utils = EvalUtilities(self._view)
        self.placeholders = {}
        self._insert_mark = self.end_mark
        self.plugin_data = plugin_data

        # Create parser
        parser = Parser(data=self['text'])

        # Parse tokens
        while (True):
            token = parser.token()

            if not token:
                break

            try:
                val = {'environment': self._create_environment,
                       'placeholder': self._create_placeholder,
                       'shell': self._create_shell,
                       'eval': self._create_eval,
                       'regex': self._create_regex,
                       'text': self._create_text}[token.klass](token.data)
            except KeyError:
                sys.stderr.write('Token class not supported: %s (%s)\n' % token.klass)
                continue

            if isinstance(val, str):
                # Insert text
                self._insert_text(val)
            else:
                # Insert placeholder
                self._add_placeholder(val)

        # Create end placeholder if there isn't one yet
        if 0 not in self.placeholders:
            self.placeholders[0] = PlaceholderEnd(self._view, self.environ, self.end_iter(), None)
            self.plugin_data.ordered_placeholders.append(self.placeholders[0])

        # Make sure run_last is ran for all placeholders and remove any
        # non `ok` placeholders
        for tabstop in self.placeholders.copy():
            ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]]

            for placeholder in ph:
                placeholder.run_last(self.placeholders)

                if not placeholder.ok or placeholder.done:
                    self._invalid_placeholder(placeholder, not placeholder.ok)

        # Remove all the Expand placeholders which have a tabstop because
        # they can be used to mirror, but they shouldn't be real tabstops
        # (if they have mirrors installed). This is problably a bit of
        # a dirty hack :)
        if -1 not in self.placeholders:
            self.placeholders[-1] = []

        for tabstop in self.placeholders.copy():
            placeholder = self.placeholders[tabstop]

            if tabstop != -1:
                if isinstance(placeholder, PlaceholderExpand) and \
                   placeholder.has_references:
                    # Add to anonymous placeholders
                    self.placeholders[-1].append(placeholder)

                    # Remove placeholder
                    del self.placeholders[tabstop]

        self.plugin_data = None

    def insert_into(self, plugin_data, insert):
        buf = plugin_data.view.get_buffer()
        last_index = 0

        # Find closest mark at current insertion, so that we may insert
        # our marks in the correct order
        (current, next) = plugin_data.next_placeholder()

        if current:
            # Insert AFTER current
            last_index = plugin_data.placeholders.index(current) + 1
        elif next:
            # Insert BEFORE next
            last_index = plugin_data.placeholders.index(next)
        else:
            # Insert at first position
            last_index = 0

        # lastIndex now contains the position of the last mark
        # Create snippet bounding marks
        self.begin_mark = buf.create_mark(None, insert, True)
        self.end_mark = buf.create_mark(None, insert, False)

        # Now parse the contents of this snippet, create Placeholders
        # and insert the placholder marks in the marks array of plugin_data
        self._parse(plugin_data)

        # So now all of the snippet is in the buffer, we have all our
        # placeholders right here, what's next, put all marks in the
        # plugin_data.marks
        k = sorted(self.placeholders.keys(), reverse=True)

        plugin_data.placeholders.insert(last_index, self.placeholders[0])
        last_iter = self.placeholders[0].end_iter()

        for tabstop in k:
            if tabstop != -1 and tabstop != 0:
                placeholder = self.placeholders[tabstop]
                end_iter = placeholder.end_iter()

                if last_iter.compare(end_iter) < 0:
                    last_iter = end_iter

                # Inserting placeholder
                plugin_data.placeholders.insert(last_index, placeholder)

        # Move end mark to last placeholder
        buf.move_mark(self.end_mark, last_iter)

        return self

    def deactivate(self):
        buf = self.begin_mark.get_buffer()

        buf.delete_mark(self.begin_mark)
        buf.delete_mark(self.end_mark)

        self.placeholders = {}

    def begin_iter(self):
        return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark)

    def end_iter(self):
        return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark)

# ex:ts=4:et:
