#    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

import os
from gi.repository import Gio

from placeholder import *
from parser import Parser, Token
from helper import *

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" * ((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 = unicode(Gtk.accelerator_get_label(keyval, mod), 'utf-8')

                return accel or u''

        def display(self):
                nm = 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>' + markup_escape(unicode.join(u', ', 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 = unicode.join(u'\n' + self._indent, 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 = u''

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

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

                # Keep indentation
                return unicode.join(u'\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 = 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, basestring):
                                # 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 = self.placeholders.keys()
                k.sort(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=8:et:
