# -*- coding: utf-8 -*-
"""
@package gui_core.simplelmgr

@brief GUI class for simple layer management.

Classes:
 - simplelmgr::SimpleLayerManager
 - simplelmgr::SimpleLmgrToolbar

(C) 2013 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Anna Petrasova (kratochanna gmail.com)
"""
import os

import wx
import wx.aui

from grass.pydispatch.signal import Signal

# needed just for testing
if __name__ == '__main__':
    from grass.script.setup import set_gui_path
    set_gui_path()

from gui_core.toolbars import BaseToolbar, BaseIcons
from icons.icon import MetaIcon
from gui_core.forms import GUI
from gui_core.dialogs import SetOpacityDialog
from gui_core.wrap import CheckListBox, Menu
from core.utils import GetLayerNameFromCmd
from core.gcmd import GError
from core.layerlist import LayerList

SIMPLE_LMGR_RASTER = 1
SIMPLE_LMGR_VECTOR = 2
SIMPLE_LMGR_RASTER3D = 4
SIMPLE_LMGR_RGB = 8

SIMPLE_LMGR_TB_TOP = 16
SIMPLE_LMGR_TB_BOTTOM = 32
SIMPLE_LMGR_TB_LEFT = 64
SIMPLE_LMGR_TB_RIGHT = 128


class SimpleLayerManager(wx.Panel):
    """Simple layer manager class provides similar functionality to
    Layertree, but it's just list, not tree."""

    def __init__(
            self, parent, layerList, lmgrStyle=SIMPLE_LMGR_RASTER |
            SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT, toolbarCls=None,
            modal=False):
        wx.Panel.__init__(self, parent=parent, name='SimpleLayerManager')

        self._style = lmgrStyle
        self._layerList = layerList
        self._checkList = CheckListBox(self, style=wx.LB_EXTENDED)
        if not toolbarCls:
            toolbarCls = SimpleLmgrToolbar
        self._toolbar = toolbarCls(self, lmgrStyle=self._style)

        self._auimgr = wx.aui.AuiManager(self)

        self._modal = modal
        # d.* dialogs are recreated each time, attempt to hide it resulted
        # in completely mysterious memory corruption and crash when opening
        # any dialog with stock labels (wx.ID_OK and so on)

        # needed in order not to change selection when moving layers
        self._blockSelectionChanged = False

        self._checkList.Bind(
            wx.EVT_LISTBOX,
            lambda evt: self._selectionChanged())
        self._checkList.Bind(
            wx.EVT_LISTBOX_DCLICK,
            self.OnLayerChangeProperties)
        self._checkList.Bind(wx.EVT_CHECKLISTBOX, self.OnLayerChecked)
        self._checkList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)

        # signal emitted when somethin in layer list changes
        self.opacityChanged = Signal('SimpleLayerManager.opacityChanged')
        self.cmdChanged = Signal('SimpleLayerManager.cmdChanged')
        self.layerAdded = Signal('SimpleLayerManager.layerAdded')
        self.layerRemoved = Signal('SimpleLayerManager.layerRemoved')
        self.layerActivated = Signal('SimpleLayerManager.layerActivated')
        self.layerMovedUp = Signal('SimpleLayerManager.layerMovedUp')
        self.layerMovedDown = Signal('SimpleLayerManager.layerMovedDown')
        # emitted by any change (e.g. for rerendering)
        self.anyChange = Signal('SimpleLayerManager.layerChange')

        self._layout()
        self.SetMinSize((200, -1))
        self._update()

    def _layout(self):
        self._auimgr.AddPane(self._checkList,
                             wx.aui.AuiPaneInfo().
                             Name("checklist").
                             CenterPane().
                             CloseButton(False).
                             BestSize((self._checkList.GetBestSize())))
        paneInfo = wx.aui.AuiPaneInfo(). \
            Name("toolbar").Caption(_("Toolbar")).ToolbarPane(). \
            CloseButton(False).Layer(1).Gripper(False). \
            BestSize((self._toolbar.GetBestSize()))
        if self._style & SIMPLE_LMGR_TB_LEFT:
            paneInfo.Left()
        elif self._style & SIMPLE_LMGR_TB_RIGHT:
            paneInfo.Right()
        elif self._style & SIMPLE_LMGR_TB_TOP:
            paneInfo.Top()
        else:
            paneInfo.Bottom()

        self._auimgr.AddPane(self._toolbar, paneInfo)
        self._auimgr.Update()

    def _selectionChanged(self):
        """Selection was changed externally,
        updates selection info in layers."""
        if self._blockSelectionChanged:
            return
        selected = self._checkList.GetSelections()
        for i, layer in enumerate(self._layerList):
            layer.Select(i in selected)

    def UnInit(self):
        """Needs to be called before destroying this window"""
        self._auimgr.UnInit()

    def OnContextMenu(self, event):
        """Show context menu.

        So far offers only copying layer list to clipboard
        """
        if len(self._layerList) < 1:
            event.Skip()
            return

        menu = Menu()
        llist = [layer.name for layer in self._layerList]
        texts = [','.join(llist), ','.join(reversed(llist))]
        labels = [_("Copy map names to clipboard (top to bottom)"),
                  _("Copy map names to clipboard (bottom to top)")]
        for label, text in zip(labels, texts):
            id = wx.NewId()
            self.Bind(
                wx.EVT_MENU,
                lambda evt,
                t=text,
                id=id: self._copyText(t),
                id=id)

            menu.Append(id, label)

        # show the popup menu
        self.PopupMenu(menu)
        menu.Destroy()
        event.Skip()

    def _copyText(self, text):
        """Helper function for copying

        TODO: move to utils?
        """
        if wx.TheClipboard.Open():
            do = wx.TextDataObject()
            do.SetText(text)
            wx.TheClipboard.SetData(do)
            wx.TheClipboard.Close()

    def OnLayerChecked(self, event):
        """Layer was (un)checked, update layer's info."""
        checkedIdxs = self._checkList.GetChecked()
        for i, layer in enumerate(self._layerList):
            if i in checkedIdxs and not layer.IsActive():
                layer.Activate()
                self.layerActivated.emit(index=i, layer=layer)
            elif i not in checkedIdxs and layer.IsActive():
                layer.Activate(False)
                self.layerActivated.emit(index=i, layer=layer)
        self.anyChange.emit()
        event.Skip()

    def OnAddRaster(self, event):
        """Opens d.rast dialog and adds layer.
        Dummy layer is added first."""
        cmd = ['d.rast']
        layer = self.AddRaster(name='', cmd=cmd, hidden=True, dialog=None)
        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
            cmd=cmd, completed=(self.GetOptData, layer, ''))
        event.Skip()

    def OnAddVector(self, event):
        """Opens d.vect dialog and adds layer.
        Dummy layer is added first."""
        cmd = ['d.vect']

        layer = self.AddVector(name='', cmd=cmd, hidden=True, dialog=None)
        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
            cmd=cmd, completed=(self.GetOptData, layer, ''))
        event.Skip()

    def OnAddRast3d(self, event):
        """Opens d.rast3d dialog and adds layer.
        Dummy layer is added first."""
        cmd = ['d.rast3d']
        layer = self.AddRast3d(name='', cmd=cmd, hidden=True, dialog=None)
        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
            cmd=cmd, completed=(self.GetOptData, layer, ''))
        event.Skip()

    def OnAddRGB(self, event):
        """Opens d.rgb dialog and adds layer.
        Dummy layer is added first."""
        cmd = ['d.rgb']
        layer = self.AddRGB(name='', cmd=cmd, hidden=True, dialog=None)
        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
            cmd=cmd, completed=(self.GetOptData, layer, ''))
        event.Skip()

    def OnRemove(self, event):
        """Removes selected layers from list."""
        layers = self._layerList.GetSelectedLayers(activeOnly=False)
        for layer in layers:
            self.layerRemoved.emit(
                index=self._layerList.GetLayerIndex(layer), layer=layer)
            self._layerList.RemoveLayer(layer)
        self._update()
        self.anyChange.emit()
        event.Skip()

    def OnLayerUp(self, event):
        """Moves selected layers one step up.

        Note: not completely correct for multiple layers."""
        layers = self._layerList.GetSelectedLayers()
        self._blockSelectionChanged = True
        for layer in layers:
            idx = self._layerList.GetLayerIndex(layer)
            if idx > 0:
                self.layerMovedUp.emit(index=idx, layer=layer)
                self._layerList.MoveLayerUp(layer)
        self._update()
        self._blockSelectionChanged = False
        self.anyChange.emit()
        event.Skip()

    def OnLayerDown(self, event):
        """Moves selected layers one step down.

        Note: not completely correct for multiple layers."""
        layers = self._layerList.GetSelectedLayers()
        self._blockSelectionChanged = True
        for layer in layers:
            idx = self._layerList.GetLayerIndex(layer)
            if idx < len(self._layerList) - 1:
                self.layerMovedDown.emit(
                    index=self._layerList.GetLayerIndex(layer), layer=layer)
                self._layerList.MoveLayerDown(layer)
        self._update()
        self._blockSelectionChanged = False
        self.anyChange.emit()
        event.Skip()

    def OnLayerChangeProperties(self, event):
        """Opens module dialog to edit layer command."""
        layers = self._layerList.GetSelectedLayers()
        if not layers or len(layers) > 1:
            return
        self._layerChangeProperties(layers[0])
        event.Skip()

    def _layerChangeProperties(self, layer):
        """Opens new module dialog or recycles it."""
        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
            cmd=layer.cmd, completed=(self.GetOptData, layer, ''))

    def OnLayerChangeOpacity(self, event):
        """Opacity of a layer is changing."""
        layers = self._layerList.GetSelectedLayers()
        if not layers or len(layers) > 1:
            return
        layer = layers[0]
        dlg = SetOpacityDialog(self, opacity=layer.opacity,
                               title=_("Set opacity of <%s>") % layer.name)
        dlg.applyOpacity.connect(lambda value:
                                 self._setLayerOpacity(layer, value))
        dlg.CentreOnParent()

        if dlg.ShowModal() == wx.ID_OK:
            self._setLayerOpacity(layer, dlg.GetOpacity())
        dlg.Destroy()
        event.Skip()

    def _setLayerOpacity(self, layer, value):
        """Sets layer's opacity.'"""
        layer.opacity = value
        self._update()
        self.opacityChanged.emit(
            index=self._layerList.GetLayerIndex(layer),
            layer=layer)
        self.anyChange.emit()

    def _update(self):
        """Updates checklistbox according to layerList structure."""
        items = []
        active = []
        selected = []

        # remove hidden (temporary) layers first
        for layer in reversed(self._layerList):
            if layer.hidden:
                self._layerList.RemoveLayer(layer)

        for layer in self._layerList:
            if layer.opacity < 1:
                items.append(
                    "{name} (opacity {opacity}%)".format(
                        name=layer.name, opacity=int(
                            layer.opacity * 100)))
            else:
                items.append(layer.name)
            active.append(layer.IsActive())
            selected.append(layer.IsSelected())

        self._checkList.SetItems(items)
        for i, check in enumerate(active):
            self._checkList.Check(i, check)

        for i, layer in enumerate(self._layerList):
            if selected[i]:
                self._checkList.Select(i)
            else:
                self._checkList.Deselect(i)

    def GetOptData(self, dcmd, layer, params, propwin):
        """Handler for module dialogs."""
        if dcmd:
            layer.cmd = dcmd
            layer.selected = True
            mapName, found = GetLayerNameFromCmd(dcmd)
            if found:
                try:
                    if layer.hidden:
                        layer.hidden = False
                        signal = self.layerAdded
                    else:
                        signal = self.cmdChanged

                    layer.name = mapName
                    signal.emit(
                        index=self._layerList.GetLayerIndex(layer),
                        layer=layer)
                except ValueError as e:
                    self._layerList.RemoveLayer(layer)
                    GError(parent=self,
                           message=str(e),
                           showTraceback=False)

            self._update()
            self.anyChange.emit()

    def AddRaster(self, name, cmd, hidden, dialog):
        """Ads new raster layer."""
        layer = self._layerList.AddNewLayer(name=name, mapType='raster',
                                            active=True,
                                            cmd=cmd, hidden=hidden)
        return layer

    def AddRast3d(self, name, cmd, hidden, dialog):
        """Ads new raster3d layer."""
        layer = self._layerList.AddNewLayer(name=name, mapType='raster_3d',
                                            active=True,
                                            cmd=cmd, hidden=hidden)
        return layer

    def AddVector(self, name, cmd, hidden, dialog):
        """Ads new vector layer."""
        layer = self._layerList.AddNewLayer(name=name, mapType='vector',
                                            active=True,
                                            cmd=cmd, hidden=hidden)
        return layer

    def AddRGB(self, name, cmd, hidden, dialog):
        """Ads new vector layer."""
        layer = self._layerList.AddNewLayer(name=name, mapType='rgb',
                                            active=True,
                                            cmd=cmd, hidden=hidden)
        return layer

    def GetLayerInfo(self, layer, key):
        """Just for compatibility, should be removed in the future"""
        value = getattr(layer, key)
        # hack to return empty list, required in OnCancel in forms
        # not sure why it should be empty
        if key == 'cmd' and len(value) == 1:
            return []
        return value

    def Delete(self, layer):
        """Just for compatibility, should be removed in the future"""
        self._layerList.RemoveLayer(layer)


class SimpleLmgrToolbar(BaseToolbar):
    """Toolbar of simple layer manager.

    Style of the toolbar can be changed (horizontal,
    vertical, which map types to include).
    """

    def __init__(self, parent, lmgrStyle):
        """Toolbar constructor
        """
        self._style = lmgrStyle
        if lmgrStyle & (SIMPLE_LMGR_TB_LEFT | SIMPLE_LMGR_TB_RIGHT):
            direction = wx.TB_VERTICAL
        else:
            direction = wx.TB_HORIZONTAL
        BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction)

        self.InitToolbar(self._getToolbarData(self._toolbarData()))

        # realize the toolbar
        self.Realize()

    def _toolbarData(self):
        """Toolbar data"""
        data = [('edit', icons['edit'],
                 self.parent.OnLayerChangeProperties),
                ('remove', icons['remove'],
                 self.parent.OnRemove),
                (None, ),
                ('up', icons['up'],
                 self.parent.OnLayerUp),
                ('down', icons['down'],
                 self.parent.OnLayerDown),
                (None, ),
                ('opacity', icons['opacity'],
                 self.parent.OnLayerChangeOpacity),
                ]
        if self._style & SIMPLE_LMGR_RASTER3D:
            data.insert(0, ('addRaster3d', icons['addRast3d'],
                            self.parent.OnAddRast3d))
        if self._style & SIMPLE_LMGR_RGB:
            data.insert(0, ('addRGB', icons['addRGB'],
                            self.parent.OnAddRGB))
        if self._style & SIMPLE_LMGR_VECTOR:
            data.insert(0, ('addVector', BaseIcons['addVect'],
                            self.parent.OnAddVector))
        if self._style & SIMPLE_LMGR_RASTER:
            data.insert(0, ('addRaster', BaseIcons['addRast'],
                            self.parent.OnAddRaster))

        return data


icons = {
    'remove': MetaIcon(img='layer-remove',
                       label=_("Remove"),
                       desc=_("Remove selected map(s) from list")),
    'up': MetaIcon(img='layer-up',
                   label=_("Layer up"),
                   desc=_("Move selected layer(s) up")),
    'down': MetaIcon(img='layer-down',
                     label=_("Layer down"),
                     desc=_("Move selected layer(s) down")),
    'edit': MetaIcon(img='layer-edit',
                     label=_("Edit layer properties"),
                     desc=_("Edit layer properties")),
    'opacity': MetaIcon(img='layer-opacity',
                        label=_("Change opacity"),
                        desc=_("Change layer opacity")),
    'addRast3d': MetaIcon(img='layer-raster3d-add',
                          label=_("Add 3D raster map layer"),
                          desc=_("Add 3D raster map layer")),
    'addRGB': MetaIcon(img='layer-rgb-add', label=_('Add RGB map layer'))
}


class TestFrame(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent=parent,
                          title="Simple layer manager test")
        SimpleLayerManager(parent=self, layerList=LayerList())


def test():
    app = wx.App()
    frame = TestFrame(None)
    frame.Show()
    app.MainLoop()

if __name__ == '__main__':
    test()
