# -*- coding: utf-8 -*-
"""
GUI frame template:
- auto-accelerated control shortcuts, "&OK" will turn Alt-O into shortcut
- Python console window, initially hidden,
  with auto-saved command history kept in conf.ConsoleHistoryCommands
- wx widget inspector window, initially hidden
- option for log panel, handles logging messages via wx events
 
@author      Erki Suurjaak
@created     03.04.2012
@modified    27.04.2014
"""
import os
import wx
import wx.lib.inspection
import wx.lib.newevent
import wx.py
 
import conf
import wx_accel
 
 
"""Custom application event for adding to log."""
LogEvent,    EVT_LOG =    wx.lib.newevent.NewEvent()
"""Custom application event for setting main window status."""
StatusEvent, EVT_STATUS = wx.lib.newevent.NewEvent()
 
 
class TemplateFrameMixIn(wx_accel.AutoAcceleratorMixIn):
    """Application main window."""
 
    def __init__(self):
        wx_accel.AutoAcceleratorMixIn.__init__(self)
 
        conf.load()
 
        self.Bind(EVT_LOG,      self.on_log_message)
        self.Bind(EVT_STATUS,   self.on_set_status)
        self.Bind(wx.EVT_CLOSE, self.on_exit)
 
        self.console_commands = set() # Commands from run_console()
        self.frame_console = wx.py.shell.ShellFrame(parent=self,
            title=u"%s Console" % conf.Title, size=conf.ConsoleSize)
        self.frame_console.Bind(wx.EVT_CLOSE, self.on_showhide_console)
        self.frame_console_shown = False # Init flag
        console = self.console = self.frame_console.shell
        if not isinstance(conf.ConsoleHistoryCommands, list):
            conf.ConsoleHistoryCommands = [] 
        for cmd in conf.ConsoleHistoryCommands:
            console.addHistory(cmd)
        console.Bind(wx.EVT_KEY_DOWN, self.on_keydown_console)
        self.widget_inspector = wx.lib.inspection.InspectionTool()
 
        self.CreateStatusBar()
 
 
    def create_log_panel(self, parent):
        """Creates and returns the log output panel."""
        panel = wx.Panel(parent)
        sizer = panel.Sizer = wx.BoxSizer(wx.VERTICAL)
 
        button_clear = wx.Button(parent=panel, label="C&lear log",
                                 size=(100, -1))
        button_clear.Bind(wx.EVT_BUTTON, lambda event: self.log.Clear())
        edit_log = self.log = wx.TextCtrl(panel, style=wx.TE_MULTILINE)
        edit_log.SetEditable(False)
        # Read-only controls tend to be made grey by default
        getcolour = wx.SystemSettings.GetColour
        edit_log.BackgroundColour = getcolour(wx.SYS_COLOUR_WINDOW)
        edit_log.ForegroundColour = getcolour(wx.SYS_COLOUR_GRAYTEXT)
 
        sizer.Add(button_clear, border=5, flag=wx.ALIGN_RIGHT | wx.TOP | 
                  wx.RIGHT)
        sizer.Add(edit_log, border=5, proportion=1, flag=wx.GROW | wx.ALL)
        return panel
 
 
    def create_menu(self):
        """Creates the program menu."""
        menu = wx.MenuBar()
        menu_file = wx.Menu()
        menu.Insert(0, menu_file, "&File")
        menu_recent = self.menu_recent = wx.Menu()
        menu_file.AppendMenu(id=wx.NewId(), text="&Recent files",
            submenu=menu_recent, help="Recently opened files.")
        menu_file.AppendSeparator()
        menu_console = self.menu_console = menu_file.Append(
            id=wx.NewId(), kind=wx.ITEM_CHECK, text="Show &console\tCtrl-E",
            help="Show/hide a Python shell environment window")
        menu_inspect = self.menu_inspect = menu_file.Append(
            id=wx.NewId(), kind=wx.ITEM_CHECK, text="Show &widget inspector",
            help="Show/hide the widget inspector")
 
        self.file_history = wx.FileHistory(conf.MaxRecentFiles)
        self.file_history.UseMenu(menu_recent)
        for f in conf.RecentFiles[::-1]: # Backwards - FileHistory is a stack
            os.path.exists(f) and self.file_history.AddFileToHistory(f)
        wx.EVT_MENU_RANGE(self, wx.ID_FILE1, wx.ID_FILE9, self.on_recent_file)
        menu_file.AppendSeparator()
        m_exit = menu_file.Append(-1, "E&xit\tAlt-X", "Exit")
 
        self.Bind(wx.EVT_MENU, self.on_showhide_console, menu_console)
        self.Bind(wx.EVT_MENU, self.on_open_widget_inspector, menu_inspect)
        self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
        self.SetMenuBar(menu)
 
 
    def on_exit(self, event):
        """Handler on application exit, saves configuration."""
        do_exit = True
        if do_exit:
            conf.save()
            self.Destroy()
 
 
    def on_keydown_console(self, event):
        """Handler for keydown in console, saves entered command in history."""
        event.Skip()
        if (wx.WXK_RETURN == event.KeyCode and not event.ShiftDown()
        and self.console.history):
            # Defer saving until command is inserted into console history
            wx.CallAfter(self.save_last_command)
 
 
    def run_console(self, command):
        """
        Runs the command in the Python console. Will not be saved to console
        commands history.
        """
        self.console.run(command)
        self.console_commands.add(command)
 
 
    def save_last_command(self):
        """
        Saves the last console command in conf, minus the commands given via
        run_console().
        """
        h = [x for x in self.console.history if x not in self.console_commands]
        history = h[:conf.MaxConsoleHistory][::-1]
        if history != conf.ConsoleHistoryCommands:
            conf.ConsoleHistoryCommands[:] = history
            conf.save()
 
 
    def on_set_status(self, event):
        """Event handler for adding a message to the log control."""
        self.SetStatusText(event.text)
 
 
    def on_log_message(self, event):
        """Event handler for adding a message to the log control."""
        if hasattr(self, "log") and getattr(conf, "LogEnabled", False):
            text = event.text
            try:
                self.log.AppendText(text + "\n")
            except Exception:
                try:
                    self.log.AppendText(text.decode("utf-8", "replace") + "\n")
                except Exception as e:
                    print("Exception %s: %s in on_log_message" %
                          (e.__class__.__name__, e))
 
 
    def on_showhide_console(self, event):
        """Toggles the console shown/hidden."""
        show = not self.frame_console.IsShown()
        if show:
            if not self.frame_console_shown:
                # First showing of console, set height to a fraction of main
                # form, and position it immediately under the main form, or
                # covering its bottom if no room.
                self.frame_console_shown = True
                size = wx.Size(self.Size.width, max(200, self.Size.height / 3))
                self.frame_console.Size = size
                display = wx.GetDisplaySize()
                y = 0
                min_bottom_space = 130 # Leave space for autocomplete dropdown
                if size.height > display.height - self.Size.height \
                - self.Position.y - min_bottom_space:
                    y = display.height - self.Size.height - self.Position.y \
                        - size.height - min_bottom_space
                self.frame_console.Position = (
                    self.Position.x, self.Position.y + self.Size.height + y
                )
            # Scroll to the last line
            self.console.ScrollToLine(self.console.LineCount + 3 - (
                self.console.Size.height / self.console.GetTextExtent(" ")[1]
            ))
        self.frame_console.Show(show)
        if hasattr(self, "menu_console"):
            self.menu_console.Check(show)
 
 
    def on_open_widget_inspector(self, event):
        """Toggles the widget inspection tool shown/hidden."""
        visible = not (self.widget_inspector.initialized
                       and self.widget_inspector._frame)
        if visible:
            self.widget_inspector.Init()
            self.widget_inspector.Show(selectObj=self, refreshTree=True)
            self.widget_inspector._frame.Bind(wx.EVT_CLOSE, lambda e: e.Skip())
        else:
            self.widget_inspector._frame.Close()
        if hasattr(self, "menu_inspect"):
            self.menu_inspect.Check(visible)
 
 
    def on_recent_file(self, event):
        """Handler for clicking an entry in Recent Files menu."""
        filename = self.file_history.GetHistoryFile(event.GetId() - wx.ID_FILE1)
        self.open_file(filename)