"""
@package gui_core.vselect

@brief wxGUI classes for interactive selection of vector
features. Allows creating a new vector map from selected vector
features or return their categories

Classes:
- vselect::VectorSelectList
- vselect::VectorSelectDialog
- vselect::VectorSelectBase
- vselect::VectorSelectHighlighter

(C) 2014-2015 by Matej Krejci, and 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 Matej Krejci <matejkrejci gmail.com> (mentor: Martin Landa)
"""

import string
import random

import wx
import wx.lib.mixins.listctrl as listmix

from core.utils import _
from core.gcmd import GMessage, GError, GWarning
from core.gcmd import RunCommand

import grass.script as grass
from grass.pydispatch.signal import Signal


class VectorSelectList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    """Widget for managing vector features selected from map display
    """

    def __init__(self, parent):
        wx.ListCtrl.__init__(
            self,
            parent=parent,
            id=wx.ID_ANY,
            style=wx.LC_REPORT | wx.BORDER_SUNKEN)
        listmix.ListCtrlAutoWidthMixin.__init__(self)

        self.InsertColumn(col=0, heading=_('category'))
        self.InsertColumn(col=1, heading=_('type'))
        self.SetColumnWidth(0, 100)
        self.SetColumnWidth(1, 100)

        self.index = 0
        self.dictIndex = {}

    def AddItem(self, item):
        if 'Category' not in item:
            return

        pos = self.InsertStringItem(0, str(item['Category']))
        self.SetStringItem(pos, 1, str(item['Type']))
        self.dictIndex[str(item['Category'])] = pos

    def RemoveItem(self, item):
        index = self.dictIndex.get(str(item['Category']), -1)
        if index > -1:
            self.DeleteItem(index)


class VectorSelectDialog(wx.Dialog):
    """Dialog for managing vector features selected from map display"""

    def __init__(self, parent, title=_("Select features"), size=(200, 300)):
        wx.Dialog.__init__(
            self,
            parent=parent,
            id=wx.ID_ANY,
            title=title,
            size=size,
            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)

        self._layout()

    def AddWidget(self, widget, proportion=1, flag=wx.EXPAND):
        self.mainSizer.Add(widget, proportion=proportion, flag=flag)
        self.Layout()

    def _layout(self):
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.mainSizer)

        self.Show()


class VectorSelectBase():
    """@brief Main class of vector selection function

    It allows selecting vector features from map display and to export
    them as a new vector map. Current version allows selecting
    features one-by-one by single click in map display.

    This class can be initialized with (see CreateDialog()) or without
    (see gselect) dialog (see VectorSelectDialog).
    """

    def __init__(self, parent, giface):
        self.parent = parent
        self._giface = giface
        self.register = False
        self.mapWin = self._giface.GetMapWindow()
        self.mapDisp = giface.GetMapDisplay()
        self.RegisterMapEvtHandler()

        self.selectedFeatures = []
        self.mapName = None  # chosen map for selecting features

        self._dialog = None
        self.onCloseDialog = None

        self.updateLayer = Signal('VectorSelectBase.updateLayer')

        self.painter = VectorSelectHighlighter(self.mapDisp, giface)

    def CreateDialog(self, createButton=True):
        """Create dialog

        :param createButton: True to add 'create new map' button
        """
        if self._dialog:
            return

        self._dialog = VectorSelectDialog(parent=self.parent)
        self._dialog.Bind(wx.EVT_CLOSE, self.OnCloseDialog)
        if createButton:
            createMap = wx.Button(
                self._dialog, wx.ID_ANY, _("Create a new map"))
            createMap.Bind(wx.EVT_BUTTON, self.OnExportMap)
            self._dialog.AddWidget(createMap, proportion=0.1)
        self.slist = VectorSelectList(self._dialog)
        self.slist.Bind(wx.EVT_LIST_KEY_DOWN, self.OnDelete)
        self.slist.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnDeleteRow)
        self._dialog.AddWidget(self.slist)

        self.onCloseDialog = Signal('VectorSelectBase.onCloseDialog')

    def OnDeleteRow(self, event=None):
        """Delete row in widget
        """
        index = self.slist.GetFocusedItem()
        category = self.slist.GetItemText(index)
        for item in self.selectedFeatures:
            if int(item['Category']) == int(category):
                self.selectedFeatures.remove(item)
                break
        self.slist.DeleteItem(index)
        self._draw()

    def OnDelete(self, event):
        """Delete row in widget by press key(delete)
        """
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_DELETE:
            self.OnDeleteRow()

    def RegisterMapEvtHandler(self):
        if not self.register:
            self.mapWin.RegisterMouseEventHandler(wx.EVT_LEFT_DOWN,
                                                  self._onMapClickHandler,
                                                  'cross')
        self.register = True

    def UnregisterMapEvtHandler(self):
        """Unregistrates _onMapClickHandler from mapWin"""
        if self.register:
            self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN,
                                                    self._onMapClickHandler)
        self.register = False

    def OnClose(self):
        self.selectedFeatures = []
        self._draw()
        self.UnregisterMapEvtHandler()

    def OnCloseDialog(self, evt=None):
        if not self.onCloseDialog:
            return

        self.onCloseDialog.emit()
        self.selectedFeatures = []
        self.painter.Clear()
        self._dialog.Destroy()
        self.UnregisterMapEvtHandler()

    def Reset(self):
        """Remove items from dialog list"""
        self.selectedFeatures = []
        if self._dialog:
            self.slist.DeleteAllItems()
            self._dialog.Raise()
        self.RegisterMapEvtHandler()

    def _onMapClickHandler(self, event):
        """Registred handler for clicking on grass disp
        """
        if event == "unregistered":
            return
        vWhatDic = self.QuerySelectedMap()
        if 'Category' in vWhatDic:
            self.AddVecInfo(vWhatDic)
            self._draw()
            if self._dialog:
                self._dialog.Raise()

    def AddVecInfo(self, vInfoDictTMP):
        """Update vector in list

        Note: click on features add category
              second click on the same vector remove category from list
        """
        if len(self.selectedFeatures) > 0:
            for sel in self.selectedFeatures:
                if sel['Category'] == vInfoDictTMP[
                        'Category']:  # features is selected=> remove features
                    self.selectedFeatures.remove(sel)
                    if self._dialog:  # if dialog initilized->update dialog
                        self.slist.RemoveItem(vInfoDictTMP)
                    return True

            self.selectedFeatures.append(vInfoDictTMP)
            if self._dialog:
                self.slist.AddItem(vInfoDictTMP)
        else:  # only one is selected
            self.selectedFeatures.append(vInfoDictTMP)
            if self._dialog:
                self.slist.AddItem(vInfoDictTMP)

        if len(self.selectedFeatures) == 0:
            return False

        return True

    def _draw(self):
        """Call class 'VectorSelectHighlighter' to draw selected features"""
        self.updateLayer.emit()
        if len(self.selectedFeatures) > 0:
            self.painter.SetLayer(self.selectedFeatures[0]['Layer'])
            self.painter.SetMap(self.selectedFeatures[0]['Map'])
            tmp = list()
            for i in self.selectedFeatures:
                tmp.append(i['Category'])

            self.painter.SetCats(tmp)
            self.painter.DrawSelected()
        else:
            self.painter.Clear()

    def GetSelectedMap(self):
        """Return name of selected map in layer tree"""
        layerList = self._giface.GetLayerList()
        layerSelected = layerList.GetSelectedLayer()
        if layerSelected is None:
            return None

        if not layerSelected.maplayer.IsActive():
            GWarning(_("Selected map <%s> has been disabled for rendering. "
                       "Operation canceled.") % str(layerSelected), parent=self.mapWin)
            return None

        if layerSelected:
            mapName = str(layerSelected)
            if self.mapName is not None:
                if self.mapName != mapName:
                    self.Reset()
        else:
            mapName = None
            self.UnregisterMapEvtHandler()
            GError(_("No map layer selected. Operation canceled."))
        return mapName

    def QuerySelectedMap(self):
        """Return w.what info from last clicked coords on display

        """
        self.mapName = self.GetSelectedMap()
        if not self.mapName:
            return {}

        mapInfo = self.mapWin.GetMap()
        threshold = 10.0 * (
            (mapInfo.region['e'] - mapInfo.region['w']) / mapInfo.width)
        try:
            query = grass.vector_what(map=[self.mapName],
                                      coord=self.mapWin.GetLastEN(),
                                      distance=threshold, skip_attributes=True)
        except grass.ScriptError:
            GError(parent=self,
                   message=_("Failed to query vector map(s) <%s>.") % self.map)
            return None

        return query[0]

    def GetLineStringSelectedCats(self):
        """Return line of categories separated by comma"""
        strTMP = ''
        for cat in self.selectedFeatures:
            strTMP += str(cat['Category']) + ','
        return strTMP[:-1]

    def _id_generator(self, size=6,
                      chars=string.ascii_uppercase + string.digits):
        return ''.join(random.choice(chars) for _ in range(size))

    def OnExportMap(self, event):
        """Export selected features to a new map

        Add new map layer to layer tree and checked it

        @todo: set color of map to higlight color
        """

        if len(self.selectedFeatures) == 0:
            GMessage(_('No features selected'))
            return
        lst = ''
        for cat in self.selectedFeatures:  # build text string of categories for v.extract input
            lst += str(cat['Category']) + ','
        lst = lst[:-1]
        outMap = str(self.selectedFeatures[0][
                     'Map']) + '_selection' + str(self._id_generator(3))
        ret, err = RunCommand('v.extract',
                              input=self.selectedFeatures[0]['Map'],
                              layer=self.selectedFeatures[0]['Layer'],
                              output=outMap,
                              cats=lst,
                              getErrorMsg=True)
        if ret == 0:
            tree = self._giface.GetLayerTree()
            if tree:
                tree.AddLayer(ltype='vector', lname=outMap,
                              lcmd=['d.vect', 'map=%s' % outMap],
                              lchecked=True)

                # TODO colorize new map
                self.Reset()
            else:
                GMessage(_('Vector map <%s> was created') % outMap)
                self.Reset()
        else:
            GError(_("Unable to create a new vector map.\n\nReason: %s") % err)

    """
    def SetSelectedCat(self, cats):
        # allows setting selected vector categories by list of cats (per line)
        info = self.QuerySelectedMap()
        if 'Category' not in info:
            return

        for cat in cats.splitlines():
            tmpDict = {}
            tmpDict['Category'] = cat
            tmpDict['Map'] = info['Map']
            tmpDict['Layer'] = info['Layer']
            tmpDict['Type'] = '-'
            self.AddVecInfo(tmpDict)

        self._draw()
    """


class VectorSelectHighlighter():
    """Class for highlighting selected features on display

    :param mapdisp: Map display frame
    """

    def __init__(self, mapdisp, giface):
        self.qlayer = None
        self.mapdisp = mapdisp
        self.giface = giface
        self.layerCat = {}
        self.data = {}
        self.data['Category'] = list()
        self.data['Map'] = None
        self.data['Layer'] = None

    def SetMap(self, map):
        self.data['Map'] = map

    def SetLayer(self, layer):
        self.data['Layer'] = layer

    def SetCats(self, cats):
        self.data['Category'] = cats

    def Clear(self):
        self.data['Category'] = list()
        self.data['Map'] = None
        self.data['Layer'] = None
        self.mapdisp.RemoveQueryLayer()
        self.giface.GetMapWindow().UpdateMap(render=False)

    def DrawSelected(self):
        """Highlight selected features"""
        self.layerCat[int(self.data['Layer'])] = self.data['Category']

        # add map layer with higlighted vector features
        self.AddQueryMapLayer()  # -> self.qlayer
        self.qlayer.SetOpacity(0.7)
        self.giface.updateMap.emit(render=True, renderVector=True)

    def AddQueryMapLayer(self):
        """Redraw a map

        :return: True if map has been redrawn, False if no map is given
        """
        if self.mapdisp.GetMap().GetLayerIndex(self.qlayer) < 0:
            self.qlayer = None

        if self.qlayer:
            self.qlayer.SetCmd(
                self.mapdisp.AddTmpVectorMapLayer(
                    self.data['Map'],
                    self.layerCat,
                    addLayer=False))
        else:
            self.qlayer = self.mapdisp.AddTmpVectorMapLayer(
                self.data['Map'], self.layerCat)

        return self.qlayer
