#----------------------------------------------------------------------------
# Name:         fmcustomizeddlg.py
# Purpose:
#
# Author:
#
# Created:
# Version:
# Date:
# Licence:      wxWindows license
# Tags:         phoenix-port, py3-port
#----------------------------------------------------------------------------
"""
This module contains a custom dialog class used to personalize the appearance of a
:class:`~wx.lib.agw.flatmenu.FlatMenu` on the fly, allowing also the user of your application to do the same.
"""

import wx
import six

if six.PY2:
    from UserDict import UserDict
else:
    from collections import UserDict

from .artmanager import ArtManager
from .fmresources import *
from .labelbook import LabelBook

_ = wx.GetTranslation

# ---------------------------------------------------------------------------- #
# Class OrderedDict
# ---------------------------------------------------------------------------- #

class OrderedDict(UserDict):
    """
    An ordered dictionary implementation.
    """

    def __init__(self, dict = None):
        self._keys = []
        UserDict.__init__(self, dict)

    def __delitem__(self, key):
        UserDict.__delitem__(self, key)
        self._keys.remove(key)

    def __setitem__(self, key, item):
        UserDict.__setitem__(self, key, item)
        if key not in self._keys: self._keys.append(key)

    def clear(self):
        UserDict.clear(self)
        self._keys = []

    def copy(self):
        dict = UserDict.copy(self)
        dict._keys = self._keys[:]
        return dict

    def items(self):
        return list(zip(self._keys, list(self.values())))

    def keys(self):
        return self._keys

    def popitem(self):
        try:
            key = self._keys[-1]
        except IndexError:
            raise KeyError('dictionary is empty')

        val = self[key]
        del self[key]

        return (key, val)

    def setdefault(self, key, failobj = None):
        UserDict.setdefault(self, key, failobj)
        if key not in self._keys: self._keys.append(key)

    def update(self, dict):
        UserDict.update(self, dict)
        for key in dict:
            if key not in self._keys: self._keys.append(key)

    def values(self):
        return list(map(self.get, self._keys))


# ---------------------------------------------------------------------------- #
# Class FMTitlePanel
# ---------------------------------------------------------------------------- #

class FMTitlePanel(wx.Panel):
    """
    Helper class to draw gradient shadings on the dialog.
    """

    def __init__(self, parent, title):
        """
        Default class constructor.

        :param `parent`: the :class:`FMTitlePanel` parent;
        :param `title`: the string to use as a dialog title.
        """

        wx.Panel.__init__(self, parent)
        self._title = title

        # Set the panel size
        dc = wx.MemoryDC()
        dc.SelectObject(wx.Bitmap(1, 1))
        dc.SetFont(wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ))

        ww, hh = dc.GetTextExtent("Tp")
        dc.SelectObject(wx.NullBitmap)

        # Set minimum panel size
        if ww < 250:
            ww = 250

        self.SetSize(wx.Size(ww, hh + 10))

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)


    def OnEraseBackground(self, event):
        """
        Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`FMTitlePanel`.

        :param `event`: a :class:`EraseEvent` event to be processed.

        :note: This method is intentionally empty to reduce flicker.
        """

        pass


    def OnPaint(self, event):
        """
        Handles the ``wx.EVT_PAINT`` event for :class:`FMTitlePanel`.

        :param `event`: a :class:`PaintEvent` event to be processed.
        """

        dc = wx.BufferedPaintDC(self)

        # Draw the background
        colour1 = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
        colour2 = ArtManager.Get().LightColour(colour1, 70)
        ArtManager.Get().PaintStraightGradientBox(dc, self.GetClientRect(), colour1, colour2, False)

        # Draw the text
        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        font.SetWeight(wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        dc.SetTextForeground(wx.BLACK)
        dc.DrawText(self._title, 5, 5)


# ---------------------------------------------------------------------------- #
# Class FMCustomizeDlg
# ---------------------------------------------------------------------------- #

class FMCustomizeDlg(wx.Dialog):
    """
    Class used to customize the appearance of :class:`~wx.lib.agw.flatmenu.FlatMenu`
    and :class:`~wx.lib.agw.flatmenu.FlatMenuBar`.
    """

    def __init__(self, parent=None):
        """
        Default class constructor.

        :param `parent`: the :class:`FMCustomizeDlg` parent window.
        """

        self._book = None

        if not parent:
            wx.Dialog.__init__(self)
            return

        wx.Dialog.__init__(self, parent, wx.ID_ANY, _("Customize"), wx.DefaultPosition,
                           wx.DefaultSize, wx.DEFAULT_DIALOG_STYLE)

        self._visibleMenus = OrderedDict()
        self._hiddenMenus = OrderedDict()

        self.CreateDialog()
        self.ConnectEvents()
        self.GetSizer().Fit(self)
        self.GetSizer().SetSizeHints(self)
        self.GetSizer().Layout()
        self.Centre()


    def CreateDialog(self):
        """ Actually creates the dialog. """

        sz = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(sz)

        # Create the main book and add some pages into it
        style = INB_NO_RESIZE | INB_LEFT | INB_DRAW_SHADOW | INB_BORDER
        self._book = LabelBook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style)
        sz.Add(self._book, 1, wx.EXPAND)

        self._book.SetColour(INB_TAB_AREA_BACKGROUND_COLOUR, ArtManager.Get().GetMenuFaceColour())

        colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
        self._book.SetColour(INB_ACTIVE_TAB_COLOUR, colour)

        self.created = False
        self.Initialise()
        hsizer = wx.BoxSizer(wx.HORIZONTAL)

        # add a separator between the book & the buttons area
        hsizer.Add(wx.Button(self, wx.ID_OK, _("&Close")), 0, wx.EXPAND | wx.ALIGN_RIGHT)
        sz.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
        sz.Add(hsizer, 0, wx.ALIGN_RIGHT | wx.ALL, 2)


    def Initialise(self):
        """ Initialzes the :class:`~wx.lib.agw.labelbook.LabelBook` pages. """

        self._book.DeleteAllPages()
        self._book.AddPage(self.CreateMenusPage(), _("Menus"), True)
        self._book.AddPage(self.CreateOptionsPage(), _("Options"), False)


    def CloseDialog(self):
        """ Closes the dialog. """

        self.EndModal(wx.ID_OK)


    def ConnectEvents(self):
        """ Does nothing at the moment. """

        pass


    def CreateMenusPage(self):
        """ Creates the :class:`~wx.lib.agw.labelbook.LabelBook` pages with :class:`~wx.lib.agw.flatmenu.FlatMenu` information. """

        menus = wx.Panel(self._book, wx.ID_ANY, wx.DefaultPosition, wx.Size(300, 300))
        sz = wx.BoxSizer(wx.VERTICAL)
        menus.SetSizer(sz)

        choices = []

        mb = self.GetParent()

        if not self.created:
            self.order = []

        # Add all the menu items that are currently visible to the list
        for i in range(len(mb._items)):

            dummy, lableOnly = ArtManager.Get().GetAccelIndex(mb._items[i].GetTitle())
            choices.append(lableOnly)

            # Add the menu to the visible menus map
            self._visibleMenus.update({lableOnly: mb._items[i].GetMenu()})
            if not self.created:
                self.order.append(lableOnly)

        # Add all hidden menus to the menu bar

        for key in self._hiddenMenus:
            choices.append(key)

        if self.created:
            visible = OrderedDict()
            hidden = OrderedDict()
            for items in self.order:
                if items in self._visibleMenus:
                    visible[items] = self._visibleMenus[items]
                elif items in self._hiddenMenus:
                    hidden[items] = self._hiddenMenus[items]

            self._visibleMenus = visible
            self._hiddenMenus = hidden

        self._menuListId = wx.NewIdRef()
        self._checkListMenus = wx.CheckListBox(menus, self._menuListId, pos=wx.DefaultPosition, size=wx.Size(250, 250),
                                               choices=self.order, style=wx.BORDER_SIMPLE)
        self._checkListMenus.Bind(wx.EVT_CHECKLISTBOX, self.OnMenuChecked)

        # check all visible items
        for indx, item in enumerate(self.order):
            if item in self._visibleMenus:
                self._checkListMenus.Check(indx)

        # Add title panel
        title = FMTitlePanel(menus, _("Select Menu To Add/Remove:"))
        sz.Add(title, 0, wx.EXPAND | wx.ALL, 2)
        sz.Add(self._checkListMenus, 1, wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, 2)

        self.created = True

        return menus


    def CreateShortcutsPage(self):
        """ Creates the :class:`~wx.lib.agw.labelbook.LabelBook` shorcuts page. """

        shorcuts = wx.Panel(self._book, wx.ID_ANY, wx.DefaultPosition, wx.Size(300, 300))
        return shorcuts


    def CreateOptionsPage(self):
        """
        Creates the :class:`~wx.lib.agw.labelbook.LabelBook` option page which holds the
        :class:`~wx.lib.agw.flatmenu.FlatMenu` styles.
        """

        options = wx.Panel(self._book, wx.ID_ANY, wx.DefaultPosition, wx.Size(300, 300))

        # Create some options here
        vsizer = wx.BoxSizer(wx.VERTICAL)
        options.SetSizer(vsizer)

        #-----------------------------------------------------------
        # options page layout
        # - Menu Style: Default or 2007 (radio group)
        #
        # - Default Style Settings:     (static box)
        #     + Draw vertical gradient  (check box)
        #     + Draw border             (check box)
        #     + Drop toolbar shadow     (check box)
        #
        # - Colour Scheme                   (static box)
        #     + Menu bar background colour  (combo button)
        #-----------------------------------------------------------

        self._menuStyleID = wx.NewIdRef()
        choices =  [_("Default Style"), _("Metallic")]
        self._menuStyle = wx.RadioBox(options, self._menuStyleID, _("Menu bar style"),
                                      wx.DefaultPosition, wx.DefaultSize, choices)

        # update the selection
        theme = ArtManager.Get().GetMenuTheme()

        if theme == Style2007:
            self._menuStyle.SetSelection(1)
        else:
            self._menuStyle.SetSelection(0)

        # connect event to the control
        self._menuStyle.Bind(wx.EVT_RADIOBOX, self.OnChangeStyle)

        vsizer.Add(self._menuStyle, 0, wx.EXPAND | wx.ALL, 5)

        self._sbStyle = wx.StaticBoxSizer(wx.StaticBox(options, -1, _("Default style settings")), wx.VERTICAL)
        self._drawVertGradID = wx.NewIdRef()
        self._verticalGradient = wx.CheckBox(options, self._drawVertGradID, _("Draw vertical gradient"))
        self._verticalGradient.Bind(wx.EVT_CHECKBOX, self.OnChangeStyle)
        self._sbStyle.Add(self._verticalGradient, 0, wx.EXPAND | wx.ALL, 3)
        self._verticalGradient.SetValue(ArtManager.Get().GetMBVerticalGradient())

        self._drawBorderID = wx.NewIdRef()
        self._drawBorder = wx.CheckBox(options, self._drawBorderID, _("Draw border around menu bar"))
        self._drawBorder.Bind(wx.EVT_CHECKBOX, self.OnChangeStyle)
        self._sbStyle.Add(self._drawBorder, 0, wx.EXPAND | wx.ALL, 3)
        self._drawBorder.SetValue(ArtManager.Get().GetMenuBarBorder())

        self._shadowUnderTBID = wx.NewIdRef()
        self._shadowUnderTB =  wx.CheckBox(options, self._shadowUnderTBID, _("Toolbar float over menu bar"))
        self._shadowUnderTB.Bind(wx.EVT_CHECKBOX, self.OnChangeStyle)
        self._sbStyle.Add(self._shadowUnderTB, 0, wx.EXPAND | wx.ALL, 3)
        self._shadowUnderTB.SetValue(ArtManager.Get().GetRaiseToolbar())

        vsizer.Add(self._sbStyle, 0, wx.EXPAND | wx.ALL, 5)

        # Misc
        sb = wx.StaticBoxSizer(wx.StaticBox(options, -1, _("Colour Scheme")), wx.VERTICAL)
        self._colourID = wx.NewIdRef()

        colourChoices = ArtManager.Get().GetColourSchemes()
        colourChoices.sort()

        self._colour = wx.ComboBox(options, self._colourID, ArtManager.Get().GetMenuBarColourScheme(), choices=colourChoices,
                                   style=wx.CB_DROPDOWN | wx.CB_READONLY)
        sb.Add(self._colour, 0, wx.EXPAND)
        vsizer.Add(sb, 0, wx.EXPAND | wx.ALL, 5)
        self._colour.Bind(wx.EVT_COMBOBOX, self.OnChangeStyle)

        # update the dialog by sending all possible events to us
        event = wx.CommandEvent(wx.wxEVT_COMMAND_RADIOBOX_SELECTED, self._menuStyleID)
        event.SetEventObject(self)
        event.SetInt(self._menuStyle.GetSelection())
        self._menuStyle.ProcessEvent(event)

        event.SetEventType(wx.wxEVT_COMMAND_CHECKBOX_CLICKED)
        event.SetId(self._drawVertGradID)
        event.SetInt(ArtManager.Get().GetMBVerticalGradient())
        self._verticalGradient.ProcessEvent(event)

        event.SetEventType(wx.wxEVT_COMMAND_CHECKBOX_CLICKED)
        event.SetId(self._shadowUnderTBID)
        event.SetInt(ArtManager.Get().GetRaiseToolbar())
        self._shadowUnderTB.ProcessEvent(event)

        event.SetEventType(wx.wxEVT_COMMAND_CHECKBOX_CLICKED)
        event.SetId(self._drawBorderID)
        event.SetInt(ArtManager.Get().GetMenuBarBorder())
        self._drawBorder.ProcessEvent(event)

        event.SetEventType(wx.wxEVT_COMMAND_COMBOBOX_SELECTED)
        event.SetId(self._colourID)
        self._colour.ProcessEvent(event)

        return options


    def OnMenuChecked(self, event):
        """
        Handles the ``wx.EVT_CHECKBOX`` event for :class:`FMCustomizeDlg`.

        :param `event`: a :class:`CommandEvent` event to be processed.

        :note: This method handles the :class:`~wx.lib.agw.flatmenu.FlatMenu` menus visibility.
        """

        id = event.GetInt()
        checked = self._checkListMenus.IsChecked(id)
        menuName = self._checkListMenus.GetString(id)
        menu = None
        mb = self.GetParent()

        if checked:

            # remove the item from the hidden map
            if menuName in self._hiddenMenus:
                menu = self._hiddenMenus.pop(menuName)

            # add it to the visible map
            if menu:
                self._visibleMenus.update({menuName: menu})

            indx = self._checkListMenus.GetItems().index(menuName)
            # update the menubar
            mb.Insert(indx, menu, menu._menuBarFullTitle)
            mb.Refresh()

        else:

            # remove the item from the visible items
            if menuName in self._visibleMenus:
                menu = self._visibleMenus.pop(menuName)

            # add it to the hidden map
            if menu:
                self._hiddenMenus.update({menuName: menu})

            # update the menubar
            pos = mb.FindMenu(menuName)
            if pos != wx.NOT_FOUND:
                mb.Remove(pos)

            mb.Refresh()

        if self.created:
            visible = OrderedDict()
            hidden = OrderedDict()
            for items in self.order:
                if items in self._visibleMenus:
                    visible[items] = self._visibleMenus[items]
                elif items in self._hiddenMenus:
                    hidden[items] = self._hiddenMenus[items]

            self._visibleMenus = visible
            self._hiddenMenus = hidden


    def OnChangeStyle(self, event):
        """
        Handles the ``wx.EVT_CHECKBOX`` event for :class:`FMCustomizeDlg`.

        :param `event`: a :class:`CommandEvent` event to be processed.

        :note: This method handles the :class:`~wx.lib.agw.flatmenu.FlatMenu` styles.
        """

        mb = self.GetParent()

        if event.GetId() == self._menuStyleID:

            if event.GetSelection() == 0:

                # Default style
                ArtManager.Get().SetMenuTheme(StyleXP)
                self._drawBorder.Enable()
                self._verticalGradient.Enable()
                mb.Refresh()

            else:

                ArtManager.Get().SetMenuTheme(Style2007)
                self._drawBorder.Enable(False)
                self._verticalGradient.Enable(False)
                mb.Refresh()

            return

        if event.GetId() == self._drawBorderID:

            ArtManager.Get().DrawMenuBarBorder(event.IsChecked())
            mb.Refresh()
            return

        if event.GetId() == self._drawVertGradID:

            ArtManager.Get().SetMBVerticalGradient(event.IsChecked())
            mb.Refresh()
            return

        if event.GetId() == self._colourID:

            selection = _("Default")
            sel = self._colour.GetSelection()
            if sel != wx.NOT_FOUND:
                # select new colour scheme
                selection = self._colour.GetStringSelection()

            ArtManager.Get().SetMenuBarColour(selection)
            mb.Refresh()
            return

        if event.GetId() == self._shadowUnderTBID:
            ArtManager.Get().SetRaiseToolbar(event.IsChecked())
            mb.Refresh()
            return


