# -*- coding: utf-8 -*-
"""
$Id$

Copyright 2012 Lars Kruse <devel@sumpfralle.de>

This file is part of PyCAM.

PyCAM 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 3 of the License, or
(at your option) any later version.

PyCAM 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 PyCAM.  If not, see <http://www.gnu.org/licenses/>.
"""

import os
import sys
import code
# gtk is imported later
#import gtk
import StringIO

import pycam.Plugins
import pycam.Utils.log

_log = pycam.Utils.log.get_logger()


class GtkConsole(pycam.Plugins.PluginBase):

    UI_FILE = "gtk_console.ui"
    DEPENDS = ["Clipboard"]
    CATEGORIES = ["System"]

    # sys.ps1 and sys.ps2 don't seem to be available outside of the shell
    PROMPT_PS1 = ">>> "
    PROMPT_PS2 = "... "

    def setup(self):
        self._history = []
        self._history_position = None
        if self.gui:
            import gtk
            self._gtk = gtk
            self._console = code.InteractiveConsole(
                    locals=self.core.get_namespace(), filename="PyCAM")
            self._console_output = StringIO.StringIO()
            # TODO: clean up this stdin/stdout mess (maybe subclass "sys"?)
            code.sys.stdout = self._console_output
            code.sys.stdin = StringIO.StringIO()
            self._console_buffer = self.gui.get_object("ConsoleViewBuffer")
            def console_write(data):
                self._console_buffer.insert(
                        self._console_buffer.get_end_iter(), data)
                self._console_buffer.place_cursor(
                        self._console_buffer.get_end_iter())
            self._console.write  = console_write
            self._clear_console()
            console_action = self.gui.get_object("ToggleConsoleWindow")
            self.register_gtk_accelerator("console", console_action, None,
                    "ToggleConsoleWindow")
            self.core.register_ui("view_menu", "ToggleConsoleWindow",
                    console_action, 90)
            self._window = self.gui.get_object("ConsoleDialog")
            self._window_position = None
            self._gtk_handlers = []
            hide_window = lambda *args: self._toggle_window(value=False)
            for objname, signal, func in (
                    ("ConsoleExecuteButton", "clicked", self._execute_command),
                    ("CommandInput", "activate", self._execute_command),
                    ("CopyConsoleButton", "clicked", self._copy_to_clipboard),
                    ("WipeConsoleButton", "clicked", self._clear_console),
                    ("CommandInput", "key-press-event", self._scroll_history),
                    ("ToggleConsoleWindow", "toggled", self._toggle_window),
                    ("CloseConsoleButton", "clicked", hide_window),
                    ("ConsoleDialog", "delete-event", hide_window),
                    ("ConsoleDialog", "destroy", hide_window)):
                self._gtk_handlers.append((self.gui.get_object(objname),
                        signal, func))
            self.register_gtk_handlers(self._gtk_handlers)
        return True

    def teardown(self):
        if self.gui:
            self.unregister_gtk_handlers(self._gtk_handlers)

    def _hide_window(self, widget=None, event=None):
        self.gui.get_object("ConsoleDialog").hide()
        # don't close window (for "destroy" event)
        return True

    def _clear_console(self, widget=None):
        start, end = self._console_buffer.get_bounds()
        self._console_buffer.delete(start, end)
        self._console.write(self.PROMPT_PS1)

    def _execute_command(self, widget=None):
        input_control = self.gui.get_object("CommandInput")
        text = input_control.get_text()
        if not text:
            return
        input_control.set_text("")
        # add the command to the console window
        self._console.write(text + os.linesep)
        # execute command - check if it needs more input
        if not self._console.push(text):
            # append result to console view
            self._console_output.seek(0)
            for line in self._console_output.readlines():
                self._console.write(line)
            # scroll down console view to the end of the buffer
            view = self.gui.get_object("ConsoleView")
            view.scroll_mark_onscreen(self._console_buffer.get_insert())
            # clear the buffer
            self._console_output.truncate(0)
            # show the prompt again
            self._console.write(self.PROMPT_PS1)
        else:
            # show the "waiting for more" prompt
            self._console.write(self.PROMPT_PS2)
        # add to history
        if not self._history or (text != self._history[-1]):
            self._history.append(text)
        self._history_position = None

    def _copy_to_clipboard(self, widget=None):
        start, end = self._console_buffer.get_bounds()
        content = self._console_buffer.get_text(start, end)
        self.core.get("clipboard-set")(content)

    def _toggle_window(self, widget=None, value=None, action=None):
        toggle_checkbox = self.gui.get_object("ToggleConsoleWindow")
        checkbox_state = toggle_checkbox.get_active()
        if value is None:
            new_state = checkbox_state
        elif action is None:
            new_state = value
        else:
            new_state = action
        if new_state:
            if self._window_position:
                self._window.move(*self._window_position)
            self._window.show()
        else:
            self._window_position = self._window.get_position()
            self._window.hide()
        toggle_checkbox.set_active(new_state)
        return True

    def _scroll_history(self, widget=None, event=None):
        if event is None:
            return False
        try:
            keyval = getattr(event, "keyval")
            get_state = getattr(event, "get_state")
        except AttributeError:
            return False
        if get_state():
            # ignore, if any modifier is pressed
            return False
        input_control = self.gui.get_object("CommandInput")
        if (keyval == self._gtk.keysyms.Up):
            if self._history_position is None:
                # store the current (new) line for later
                self._history_lastline_backup = input_control.get_text()
                # start with the last item
                self._history_position = len(self._history) - 1
            elif self._history_position > 0:
                self._history_position -= 1
            else:
                # invalid -> no change
                return True
        elif (keyval == self._gtk.keysyms.Down):
            if self._history_position is None:
                return True
            self._history_position += 1
        else:
            # all other keys: ignore
            return False
        if self._history_position >= len(self._history):
            input_control.set_text(self._history_lastline_backup)
            # make sure that the backup can be stored again
            self._history_position = None
        else:
            input_control.set_text(self._history[self._history_position])
        # move the cursor to the end of the new text
        input_control.set_position(0)
        input_control.grab_focus()
        return True