#!/usr/bin/env python

#   Copyright (c) 2017 Kurt Jacobson
#      <kurtcjacobson@gmail.com>
#
#   This file is part of Hazzy.
#
#   Hazzy 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.
#
#   Hazzy 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 Hazzy.  If not, see <http://www.gnu.org/licenses/>.

# Description:
#   The source view for use in the G-code editor or elsewhere.


import os
import gi

gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('GtkSource', '3.0')

from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import Pango
from gi.repository import GtkSource

# Set up paths
PYDIR = os.path.abspath(os.path.dirname(__file__))
LANGDIR = os.path.join(PYDIR, 'gcode_highlight', "language-specs")
STYLEDIR = os.path.join(PYDIR, 'gcode_highlight', "styles")

from utilities import logger
log = logger.get(__name__)


class GcodeMap(GtkSource.Map):
    def __init__(self):
        GtkSource.Map.__init__(self)

        self.set_vexpand(True)
        self.props.font_desc = Pango.FontDescription('1')


class GcodeView(GtkSource.View):

    def __init__(self):
        GtkSource.View.__init__(self)

        self.set_hexpand(True)
        self.set_vexpand(True)

        # create buffer
        self.buf = self.get_buffer()

        # setup style and lang managers
        self.lm = GtkSource.LanguageManager()
        self.sm = GtkSource.StyleSchemeManager()

        self.lm.set_search_path([LANGDIR])
        self.sm.set_search_path([STYLEDIR])

        self.buf.set_style_scheme(self.sm.get_scheme('gcode'))
        self.buf.set_language(self.lm.get_language('gcode'))

        self.buf.set_max_undo_levels(20)

        self.set_show_line_numbers(True)
        self.set_show_line_marks(False)
        self.set_highlight_current_line(False)

        self.connect('key-press-event', self.on_key_press)

        # Set line highlight styles
        self.add_mark_category('error', '#ff7373')
        self.add_mark_category('motion', '#c5c5c5')
        self.add_mark_category('selected', '#96fef6')

        self.mark = None
        self.current_file = None
        self.error_line = None

        self.show()

    def add_mark_category(self, category, bg_color):
        att = GtkSource.MarkAttributes()
        color = Gdk.RGBA()
        color.parse(bg_color)
        att.set_background(color)
        self.set_mark_attributes(category, att, 1)

    def load_file(self, fn=None):
        self.current_file = fn
        self.buf.begin_not_undoable_action()
        if not fn:
            self.buf.set_text('')
        else:
            with open(fn, 'r') as f:
                self.buf.set_text(f.read())
        self.buf.end_not_undoable_action()
        self.buf.set_modified(False)

        if self.error_line:
            self.highlight_line(self.error_line, 'error')
            self.error_line = None
        else:
            self.highlight_line(None)

    def highlight_line(self, lnum=None, style=None):
        style = style or 'none' # Must be a string
        if not lnum or lnum == -1:
            if self.mark:
                self.buf.delete_mark(self.mark)
                self.mark = None
            return
        iter = self.buf.get_iter_at_line(lnum-1)
        if not self.mark:
            self.mark = self.buf.create_source_mark(style, style, iter)
        elif self.mark != self.buf.get_mark(style):
            self.buf.delete_mark(self.mark)
            self.mark = self.buf.create_source_mark(style, style, iter)
        else:
            self.buf.move_mark(self.mark, iter)
        self.scroll_to_mark(self.mark, 0, True, 0, 0.5)

    # Since Gremlin3D reports any errors before GStat emits the 'file-loaded'
    # signal, we have to save the error line here and then do the actual 
    # highlighting after we have loaded the sourceview in 'self.load_file'
    def highlight_error_line(self, lnum):
        self.error_line = lnum

    def set_line_number(self, lnum):
        if lnum == 0: # 0 will scroll to end, use -1 for that!
            lnum = 1
        iter = self.buf.get_iter_at_line(lnum - 1)
        self.scroll_to_iter(iter, 0, True, 0, .5)

    def set_cursor(self, lnum):
        if lnum == 0: # 0 will scroll to end, use -1 for that!
            lnum = 1
        self.grab_focus()
        iter = self.buf.get_iter_at_line(lnum - 1)
        self.scroll_to_iter(iter, 0, True, 0, .5)
        self.buf.place_cursor(iter)

    def get_program_length(self):
        return self.buf.get_line_count()

    # If no "save as" file name specified save to the current file in preview
    def save(self, fn=None):
        if fn is None:
            fn = self.current_file
        text = self.buf.get_text(self.buf.get_start_iter(), self.buf.get_end_iter(), include_hidden_chars=True)

        with open(fn, "w") as openfile:
            openfile.write(text)

        self.buf.set_modified(False)
        log.info('Saved file as "{0}"'.format(fn))

    # ctrl+s to save the file
    def on_key_press(self, widget, event):
        kv = event.keyval
        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if kv == Gdk.KEY_s:
                self.save()


def demo():
    view = GcodeView()
    buf = view.get_buffer()
    buf.set_text('''(TEST OF G-CODE HIGHLIGHTING)\n\nG1 X1.2454 Y2.3446 Z-10.2342 I0 J0 K0\n\nM3''')
    view.highlight_line(3, None)

    scrolled = Gtk.ScrolledWindow()
    scrolled.set_hexpand(True)
    scrolled.add(view)

    view_map = GcodeMap()
    view_map.set_view(view)

    box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
    box.pack_start(scrolled, True, True, 0)
    box.pack_start(Gtk.Separator(), False, False, 0)
    box.pack_start(view_map, False, False, 0)

    win = Gtk.Window()
    win.set_default_size(400, 300)
    win.add(box)

    win.connect('destroy', Gtk.main_quit)

    win.show_all()
    Gtk.main()

if __name__ == '__main__':
    demo()
