"""
@package web_services.widgets

@brief Widgets for web services (WMS, WMTS, NasaOnEarh)

List of classes:
 - widgets::WSPanel
 - widgets::LayersList

(C) 2012-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 Martin Landa <landa.martin gmail.com>
@author Stepan Turek <stepan.turek seznam.cz>
"""

import os
import sys
import shutil

from copy import deepcopy
try:
    from xml.etree.ElementTree import ParseError
except ImportError:  # < Python 2.7
    from xml.parsers.expat import ExpatError as ParseError

import wx
import wx.lib.flatnotebook as FN
import wx.lib.colourselect as csel
import wx.lib.mixins.listctrl as listmix
from wx.lib.newevent import NewEvent
from wx.gizmos import TreeListCtrl

from core import globalvar
from core.debug import Debug
from core.gcmd import GWarning, GMessage
from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
from core.utils import _

from web_services.cap_interface import WMSCapabilities, WMTSCapabilities, OnEarthCapabilities

from gui_core.widgets import GNotebook
from gui_core.widgets import ManageSettingsWidget
from gui_core.wrap import GSpinCtrl as SpinCtrl

import grass.script as grass

rinwms_path = os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms")
if rinwms_path not in sys.path:
    sys.path.append(rinwms_path)

from wms_base import WMSDriversInfo
from srs import Srs

from grass.pydispatch.signal import Signal


class WSPanel(wx.Panel):

    def __init__(self, parent, web_service, **kwargs):
        """Show data from capabilities file.

        Signal: capParsed - this signal is emitted when capabilities file is downloaded
                            (after ConnectToServer method was called)

        :param parent:  parent widget
        :param web_service:  web service to be panel generated for
        """
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)

        self.parent = parent
        self.ws = web_service

        self.capParsed = Signal('WSPanel.capParsed')

        # stores widgets, which represents parameters/flags of d.wms
        self.params = {}
        self.flags = {}

        self.o_layer_name = ''

        # stores err output from r.in.wms during getting capabilities
        self.cmd_err_str = ''

        # stores selected layer from layer list
        self.sel_layers = []

        # downloaded and parsed data from server successfully?
        self.is_connected = False

        # common part of command for r.in.wms -c and d.wms
        self.ws_cmdl = None

        # provides information about driver parameters
        self.drv_info = WMSDriversInfo()
        self.drv_props = self.drv_info.GetDrvProperties(self.ws)

        self.ws_drvs = {
            'WMS_1.1.1': {
                'cmd': [
                    'wms_version=1.1.1',
                    'driver=WMS_GRASS'],
                'cap_parser': lambda temp_file: WMSCapabilities(
                    temp_file,
                    '1.1.1'),
            },
            'WMS_1.3.0': {
                'cmd': [
                    'wms_version=1.3.0',
                    'driver=WMS_GRASS'],
                'cap_parser': lambda temp_file: WMSCapabilities(
                    temp_file,
                    '1.3.0'),
            },
            'WMTS': {
                'cmd': ['driver=WMTS_GRASS'],
                'cap_parser': WMTSCapabilities,
            },
            'OnEarth': {
                'cmd': ['driver=OnEarth_GRASS'],
                'cap_parser': OnEarthCapabilities,
            }}

        self.cmdStdErr = GStderr(self)
        self.cmd_thread = CmdThread(self)
        self.cap_file = grass.tempfile()

        reqDataBox = wx.StaticBox(
            parent=self, label=_(" Requested data settings "))
        self._nb_sizer = wx.StaticBoxSizer(reqDataBox, wx.VERTICAL)
        self.notebook = GNotebook(parent=self,
                                  style=FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON)

        self._requestPage()
        self._advancedSettsPage()

        self._layout()

        self.layerSelected = self.list.layerSelected

        self.Bind(EVT_CMD_DONE, self.OnCapDownloadDone)
        self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)

    def __del__(self):
        self.cmd_thread.abort(abortall=True)
        grass.try_remove(self.cap_file)

    def _layout(self):
        self._nb_sizer.Add(item=self.notebook, proportion=1, flag=wx.EXPAND)
        self.SetSizer(self._nb_sizer)

    def _requestPage(self):
        """Create request page"""
        self.req_page_panel = wx.Panel(parent=self, id=wx.ID_ANY)
        self.notebook.AddPage(page=self.req_page_panel,
                              text=_('Request'),
                              name='request')

        # list of layers
        self.layersBox = wx.StaticBox(parent=self.req_page_panel, id=wx.ID_ANY,
                                      label=_("List of layers "))

        style = wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT
        if self.drv_props['req_multiple_layers']:
            style = style | wx.TR_MULTIPLE
        if 'WMS' not in self.ws:
            style = style | wx.TR_HIDE_ROOT

        self.list = LayersList(parent=self.req_page_panel,
                               web_service=self.ws,
                               style=style)

        self.params['format'] = None

        self.params['srs'] = None
        if 'srs' not in self.drv_props['ignored_params']:
            projText = wx.StaticText(
                parent=self.req_page_panel, id=wx.ID_ANY,
                label=_("Source projection:"))
            self.params['srs'] = wx.Choice(
                parent=self.req_page_panel, id=wx.ID_ANY)

        self.list.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnListSelChanged)

        # layout
        self.req_page_sizer = wx.BoxSizer(wx.VERTICAL)

        layersSizer = wx.StaticBoxSizer(self.layersBox, wx.HORIZONTAL)

        layersSizer.Add(
            item=self.list,
            proportion=1,
            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
            border=5)

        self.req_page_sizer.Add(
            item=layersSizer,
            proportion=1,
            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
            border=5)

        self.source_sizer = wx.BoxSizer(wx.HORIZONTAL)

        if self.params['format'] is not None:
            self.source_sizer.Add(
                item=self.params['format'],
                flag=wx.LEFT | wx.RIGHT | wx.BOTTOM,
                border=5)

        if self.params['srs'] is not None:
            self.source_sizer.Add(
                item=projText,
                flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL,
                border=5)
            self.source_sizer.Add(
                item=self.params['srs'],
                flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.TOP | wx.BOTTOM,
                border=5)

        self.req_page_sizer.Add(
            item=self.source_sizer,
            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
            border=5)

        self.req_page_panel.SetSizer(self.req_page_sizer)

    def enableButtons(self, enable=True):
        """Enable/disable up, down, buttons
        """
        self.btnUp.Enable(enable)
        self.btnDown.Enable(enable)

    def _advancedSettsPage(self):
        """Create advanced settings page
        """
        # TODO parse maxcol, maxrow, settings from d.wms module?
        # TODO OnEarth driver - add selection of time
        adv_setts_panel = wx.Panel(parent=self, id=wx.ID_ANY)
        self.notebook.AddPage(page=adv_setts_panel,
                              text=_('Advanced request settings'),
                              name='adv_req_setts')

        labels = {}
        self.l_odrder_list = None
        if 'WMS' in self.ws:
            labels['l_order'] = wx.StaticBox(
                parent=adv_setts_panel, id=wx.ID_ANY,
                label=_("Order of layers in raster"))
            self.l_odrder_list = wx.ListBox(
                adv_setts_panel, id=wx.ID_ANY, choices=[],
                style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
            self.btnUp = wx.Button(
                adv_setts_panel, id=wx.ID_ANY, label=_("Up"))
            self.btnDown = wx.Button(
                adv_setts_panel, id=wx.ID_ANY, label=_("Down"))

            self.btnUp.Bind(wx.EVT_BUTTON, self.OnUp)
            self.btnDown.Bind(wx.EVT_BUTTON, self.OnDown)

        labels['method'] = wx.StaticText(parent=adv_setts_panel, id=wx.ID_ANY,
                                         label=_("Reprojection method:"))

        self.reproj_methods = ['nearest', 'linear', 'cubic', 'cubicspline']
        self.params['method'] = wx.Choice(
            parent=adv_setts_panel,
            id=wx.ID_ANY,
            choices=[
                _('Nearest neighbor'),
                _('Linear interpolation'),
                _('Cubic interpolation'),
                _('Cubic spline interpolation')])

        labels['maxcols'] = wx.StaticText(
            parent=adv_setts_panel, id=wx.ID_ANY,
            label=_("Maximum columns to request from server at time:"))
        self.params['maxcols'] = SpinCtrl(
            parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1))

        labels['maxrows'] = wx.StaticText(
            parent=adv_setts_panel, id=wx.ID_ANY,
            label=_("Maximum rows to request from server at time:"))
        self.params['maxrows'] = SpinCtrl(
            parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1))

        min = 100
        max = 10000
        self.params['maxcols'].SetRange(min, max)
        self.params['maxrows'].SetRange(min, max)

        val = 500
        self.params['maxcols'].SetValue(val)
        self.params['maxrows'].SetValue(val)

        self.flags['o'] = self.params['bgcolor'] = None
        if not 'o' in self.drv_props['ignored_flags']:
            self.flags['o'] = wx.CheckBox(
                parent=adv_setts_panel, id=wx.ID_ANY,
                label=_("Do not request transparent data"))

            self.flags['o'].Bind(wx.EVT_CHECKBOX, self.OnTransparent)
            labels['bgcolor'] = wx.StaticText(
                parent=adv_setts_panel, id=wx.ID_ANY,
                label=_("Background color:"))
            self.params['bgcolor'] = csel.ColourSelect(
                parent=adv_setts_panel, id=wx.ID_ANY, colour=(
                    255, 255, 255), size=globalvar.DIALOG_COLOR_SIZE)
            self.params['bgcolor'].Enable(False)

        self.params['urlparams'] = None
        if self.params['urlparams'] not in self.drv_props['ignored_params']:
            labels['urlparams'] = wx.StaticText(
                parent=adv_setts_panel, id=wx.ID_ANY,
                label=_("Additional query parameters for server:"))
            self.params['urlparams'] = wx.TextCtrl(
                parent=adv_setts_panel, id=wx.ID_ANY)

        # layout

        border = wx.BoxSizer(wx.VERTICAL)

        if 'WMS' in self.ws:

            boxSizer = wx.StaticBoxSizer(labels['l_order'], wx.VERTICAL)
            gridSizer = wx.GridBagSizer(hgap=3, vgap=3)

            gridSizer.Add(self.l_odrder_list,
                          pos=(0, 0),
                          span=(4, 1),
                          flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
                          border=0)

            gridSizer.Add(self.btnUp,
                          pos=(0, 1),
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          border=0)

            gridSizer.Add(self.btnDown,
                          pos=(1, 1),
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          border=0)

            gridSizer.AddGrowableCol(0)
            boxSizer.Add(gridSizer,
                         flag=wx.EXPAND | wx.ALL,
                         border=5)

            border.Add(item=boxSizer,
                       flag=wx.LEFT | wx.RIGHT | wx.UP | wx.EXPAND,
                       border=5)

        gridSizer = wx.GridBagSizer(hgap=3, vgap=3)

        row = 0
        for k in ['method', 'maxcols', 'maxrows', 'o', 'bgcolor']:

            if k in self.params:
                param = self.params[k]
            elif k in self.flags:
                param = self.flags[k]

            if param is None:
                continue

            if k in labels or k == 'o':
                if k != 'o':
                    label = labels[k]
                else:
                    label = param

                gridSizer.Add(label,
                              flag=wx.ALIGN_LEFT |
                              wx.ALIGN_CENTER_VERTICAL,
                              pos=(row, 0))

            if k != 'o':
                gridSizer.Add(item=param,
                              flag=wx.ALIGN_RIGHT |
                              wx.ALIGN_CENTER_VERTICAL,
                              pos=(row, 1))
            row += 1

        gridSizer.AddGrowableCol(0)
        border.Add(item=gridSizer,
                   flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND,
                   border=5)

        if self.params['urlparams']:
            gridSizer = wx.GridBagSizer(hgap=3, vgap=3)

            row = 0
            gridSizer.Add(labels['urlparams'],
                          flag=wx.ALIGN_LEFT |
                          wx.ALIGN_CENTER_VERTICAL,
                          pos=(row, 0))

            gridSizer.Add(item=self.params['urlparams'],
                          flag=wx.ALIGN_RIGHT |
                          wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
                          pos=(row, 1))

            gridSizer.AddGrowableCol(1)

            border.Add(item=gridSizer,
                       flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND,
                       border=5)

        adv_setts_panel.SetSizer(border)

    def OnUp(self, event):
        """Move selected layer up
        """
        if self.l_odrder_list.GetSelections():
            pos = self.l_odrder_list.GetSelection()
            if pos:
                self.sel_layers.insert(pos - 1, self.sel_layers.pop(pos))
            if pos > 0:
                self._updateLayerOrderList(selected=(pos - 1))
            else:
                self._updateLayerOrderList(selected=0)

    def OnDown(self, event):
        """Move selected to down
        """
        if self.l_odrder_list.GetSelections():
            pos = self.l_odrder_list.GetSelection()
            if pos != len(self.sel_layers) - 1:
                self.sel_layers.insert(pos + 1, self.sel_layers.pop(pos))
            if pos < len(self.sel_layers) - 1:
                self._updateLayerOrderList(selected=(pos + 1))
            else:
                self._updateLayerOrderList(selected=len(self.sel_layers) - 1)

    def _updateLayerOrderList(self, selected=None):
        """Update order in list.
        """
        def getlayercaption(layer):
            if l['title']:
                cap = (l['title'])
            else:
                cap = (l['name'])

            if l['style']:
                if l['style']['title']:
                    cap += ' / ' + l['style']['title']
                else:
                    cap += ' / ' + l['style']['name']
            return cap

        layer_capts = [getlayercaption(l) for l in self.sel_layers]
        self.l_odrder_list.Set(layer_capts)
        if self.l_odrder_list.IsEmpty():
            self.enableButtons(False)
        else:
            self.enableButtons(True)
            if selected is not None:
                self.l_odrder_list.SetSelection(selected)
                self.l_odrder_list.EnsureVisible(selected)

    def OnTransparent(self, event):
        checked = event.IsChecked()
        if checked:
            self.params['bgcolor'].Enable(True)
        else:
            self.params['bgcolor'].Enable(False)

    def ConnectToServer(self, url, username, password):
        """Download and parse data from capabilities file.

        :param url: server url
        :type url: str
        :param username: username for connection
        :type username: str
        :param password: password for connection
        :type password: str
        """
        self._prepareForNewConn(url, username, password)
        cap_cmd = [
            'r.in.wms',
            '-c',
            ('capfile_output=%s' % self.cap_file),
            '--overwrite'] + self.ws_cmdl

        self.currentPid = self.cmd_thread.GetId()
        self.cmd_thread.RunCmd(cap_cmd, stderr=self.cmdStdErr)

    def OnCmdOutput(self, event):
        """Manage cmd output.
        """
        if Debug.GetLevel() != 0:
            Debug.msg(1, event.text)
        elif event.type != 'message' and event.type != 'warning':
            self.cmd_err_str += event.text + os.linesep

    def _prepareForNewConn(self, url, username, password):
        """Prepare panel for new connection
        """
        self.is_connected = False

        self.sel_layers = []
        self.formats_list = []
        self.projs_list = []

        self.conn = {
            'url': url,
            'password': password,
            'username': username
        }

        conn_cmd = []
        for k, v in self.conn.iteritems():
            if v:
                conn_cmd.append("%s=%s" % (k, v))

        self.ws_cmdl = self.ws_drvs[self.ws]['cmd'] + conn_cmd

    def OnCapDownloadDone(self, event):
        """Process donwloaded capabilities file and emits capParsed
        signal (see class constructor).
        """
        if event.pid != self.currentPid:
            return

        if event.returncode != 0:
            if self.cmd_err_str:
                self.cmd_err_str = _(
                    "Unable to download %s capabilities file\nfrom <%s>:\n" %
                    (self.ws.replace('_', ' '),
                     self.conn['url'])) + self.cmd_err_str
            self._postCapParsedEvt(error_msg=self.cmd_err_str)
            self.cmd_err_str = ''
            return

        self._parseCapFile(self.cap_file)

    def _parseCapFile(self, cap_file):
        """Parse capabilities data and emits capParsed signal
        (see class constructor).
        """
        try:
            self.cap = self.ws_drvs[self.ws]['cap_parser'](cap_file)
        except (IOError, ParseError) as error:
            error_msg = _(
                "%s web service was not found in fetched capabilities file from <%s>:\n%s\n" %
                (self.ws, self.conn['url'], str(error)))
            if Debug.GetLevel() != 0:
                Debug.msg(1, error_msg)
                self._postCapParsedEvt(None)
            else:
                self._postCapParsedEvt(error_msg=error_msg)
            return

        self.is_connected = True

        # WMS standard has formats defined for all layers
        if 'WMS' in self.ws:
            self.formats_list = sorted(self._getFormats())
            self._updateFormatRadioBox(self.formats_list)
            self._setDefaultFormatVal()

        self.list.LoadData(self.cap)
        self.OnListSelChanged(event=None)

        self._postCapParsedEvt(None)

    def ParseCapFile(self, url, username, password, cap_file=None,):
        """Parse capabilities data and emits capParsed signal
        (see class constructor).
        """
        self._prepareForNewConn(url, username, password)

        if cap_file is None or not url:
            self._postCapParsedEvt(None)
            return

        shutil.copyfile(cap_file, self.cap_file)

        self._parseCapFile(self.cap_file)

    def UpdateWidgetsByCmd(self, cmd):
        """Update panel widgets accordnig to passed cmd tuple

        :param cmd: cmd in tuple
        """

        dcmd = cmd[1]

        layers = []

        if 'layers' in dcmd:
            layers = dcmd['layers']

        styles = []
        if 'styles' in dcmd:
            styles = dcmd['styles']

        if 'WMS' in self.ws:
            layers = layers.split(',')
            styles = styles.split(',')
        else:
            layers = [layers]
            styles = [styles]

        if len(layers) != len(styles):
            styles = [''] * len(layers)

        l_st_list = []
        for i in range(len(layers)):
            l_st_list.append({'style': styles[i],
                              'layer': layers[i]})

        # WMS standard - first layer in params is most bottom...
        # therefore layers order need to be reversed
        l_st_list = [l for l in reversed(l_st_list)]
        self.list.SelectLayers(l_st_list)

        params = {}
        if 'format' in dcmd:
            params['format'] = dcmd['format']
        if 'srs' in dcmd:
            params['srs'] = 'EPSG:' + dcmd['srs']
        if 'method' in dcmd:
            params['method'] = dcmd['method']

        for p, v in params.iteritems():
            if self.params[p]:
                self.params[p].SetStringSelection(v)

        for p, conv_f in [
                ('urlparams', None),
                ('maxcols', int),
                ('maxrows', int)]:
            if p in dcmd:
                v = dcmd[p]
                if conv_f:
                    v = conv_f(v)
                self.params[p].SetValue(v)

        if 'flags' in dcmd and \
           'o' in dcmd['flags']:
            self.flags['o'].SetValue(1)
            self.params['bgcolor'].Enable(True)

        if 'bgcolor' in dcmd and \
           self.params['bgcolor']:
            bgcolor = dcmd['bgcolor'].strip().lower()
            if len(bgcolor) == 8 and \
               '0x' == bgcolor[:2]:

                colour = '#' + bgcolor[2:]
                self.params['bgcolor'].SetColour(colour)

    def IsConnected(self):
        """Was successful in downloading and parsing capabilities data?
        """
        return self.is_connected

    def _postCapParsedEvt(self, error_msg):
        """Helper function
        """
        self.capParsed.emit(error_msg=error_msg)

    def CreateCmd(self):
        """Create d.wms cmd from values of panels widgets

        :return: cmd list
        :return: None if required widgets do not have selected/filled values.
        """

        # check required widgets
        if not self._checkImportValues():
            return None

        # create d.wms command
        lcmd = self.ws_cmdl
        lcmd = ['d.wms'] + lcmd

        layers = "layers="
        styles = 'styles='
        first = True

        # WMS standard - first layer in params is most bottom...
        # therefore layers order need to be reversed
        for layer in reversed(self.sel_layers):
            if not first:
                layers += ','
                styles += ','
            first = False
            layers += layer['name']
            if layer['style'] is not None:
                styles += layer['style']['name']

        lcmd.append(layers)
        lcmd.append(styles)

        if 'format' not in self.drv_props['ignored_params']:
            i_format = self.params['format'].GetSelection()
            lcmd.append("format=%s" % self.formats_list[i_format])

        if 'srs' not in self.drv_props['ignored_params']:
            i_srs = self.params['srs'].GetSelection()
            epsg_num = int(self.projs_list[i_srs].split(':')[-1])
            lcmd.append("srs=%s" % epsg_num)

        for k in ['maxcols', 'maxrows', 'urlparams']:
            lcmd.append(k + '=' + str(self.params[k].GetValue()))

        i_method = self.params['method'].GetSelection()
        lcmd.append('method=' + self.reproj_methods[i_method])

        if not 'o' in self.drv_props['ignored_flags'] and \
                self.flags['o'].IsChecked():
            lcmd.append('-o')

            c = self.params['bgcolor'].GetColour()
            hex_color = wx.Colour(
                c[0],
                c[1],
                c[2]).GetAsString(
                wx.C2S_HTML_SYNTAX)
            lcmd.append("bgcolor=" + '0x' + hex_color[1:])

        lcmd.append("map=" + self.o_layer_name)

        return lcmd

    def OnListSelChanged(self, event):
        """Update widgets according to selected layer in list.
        """
        curr_sel_ls = self.list.GetSelectedLayers()
        # update self.sel_layers (selected layer list)
        if 'WMS' in self.ws:
            for sel_l in self.sel_layers[:]:
                if sel_l not in curr_sel_ls:
                    self.sel_layers.remove(sel_l)

            for l in curr_sel_ls:
                if l not in self.sel_layers:
                    self.sel_layers.append(l)

            self._updateLayerOrderList()
        else:
            self.sel_layers = curr_sel_ls

        # update projection

        self.projs_list = []
        projs_list = []

        intersect_proj = []
        first = True
        for l in curr_sel_ls:
            layer_projs = l['cap_intf_l'].GetLayerData('srs')
            if first:
                projs_list = layer_projs
                first = False
                continue

            projs_list = set(projs_list).intersection(layer_projs)

        if 'srs' not in self.drv_props['ignored_params']:

            for proj in projs_list:
                proj_code = Srs(proj.strip()).getcode()
                proj_spl = proj_code.split(':')
                if proj_spl[0].strip().lower() in self.drv_info.GetSrs():
                    try:
                        int(proj_spl[1])
                        self.projs_list.append(proj_code)
                    except ValueError as IndexError:
                        continue

            cur_sel = self.params['srs'].GetStringSelection()

            self.projs_list = sorted(self.projs_list)
            self.params['srs'].SetItems(self.projs_list)

            if cur_sel:
                self.params['srs'].SetStringSelection(cur_sel)
            else:
                try:
                    i = self.projs_list.index('EPSG:4326')
                    self.params['srs'].SetSelection(i)
                except ValueError:
                    if len(self.projs_list) > 0:
                        self.params['srs'].SetSelection(0)

        # update format

        if 'WMS' not in self.ws and \
           'format' not in self.drv_props['ignored_params']:
            self.formats_list = []
            cur_sel = None

            if self.params['format'] is not None:
                cur_sel = self.params['format'].GetStringSelection()

            if len(curr_sel_ls) > 0:
                self.formats_list = sorted(
                    self._getFormats(
                        curr_sel_ls[0]['cap_intf_l']))
                self._updateFormatRadioBox(self.formats_list)

                if cur_sel:
                    self.params['format'].SetStringSelection(cur_sel)
                else:
                    self._setDefaultFormatVal()

        self.Layout()

    def _setDefaultFormatVal(self):
        """Set default format value.
        """
        try:
            i = self.formats_list.index('png')
            self.params['format'].SetSelection(i)
        except ValueError:
            pass

    def _updateFormatRadioBox(self, formats_list):
        """Helper function
        """
        if self.params['format'] is not None:
            self.req_page_sizer.Detach(self.params['format'])
            self.params['format'].Destroy()
        if len(self.formats_list) > 0:
            self.params['format'] = wx.RadioBox(
                parent=self.req_page_panel,
                id=wx.ID_ANY,
                label=_("Source image format"),
                pos=wx.DefaultPosition,
                choices=formats_list,
                majorDimension=4,
                style=wx.RA_SPECIFY_COLS)
            self.source_sizer.Insert(item=self.params['format'], before=2,
                                     flag=wx.LEFT | wx.RIGHT | wx.BOTTOM,
                                     border=5)

    def _getFormats(self, layer=None):
        """Get formats

        WMS has formats defined generally for whole cap.
        In WMTS and NASA OnEarh formats are defined for layer.
        """
        formats_label = []
        if layer is None:
            formats_list = self.cap.GetFormats()
        else:
            formats_list = layer.GetLayerData('format')

        for frmt in formats_list:
            frmt = frmt.strip()
            label = self.drv_info.GetFormatLabel(frmt)

            if label:
                formats_label.append(label)

        return formats_label

    def _checkImportValues(self,):
        """Check if required widgets are selected/filled
        """
        warning_str = ""
        show_war = False
        if not self.list or not self.list.GetSelectedLayers():
            warning_str += _("Select layer in layer list.\n")
            show_war = True

        if self.params['format'] is not None and \
           self.params['format'].GetSelection() == -1:
            warning_str += _("Select source image format.\n")
            show_war = True

        if self.params['srs'] is not None and \
           self.params['srs'].GetSelection() == -1:
            warning_str += _("Select source projection.\n")
            show_war = True

        if not self.o_layer_name:
            warning_str += _("Choose output layer name.\n")
            show_war = True

        if show_war:
            GMessage(parent=self.parent, message=warning_str)
            return False

        return True

    def SetOutputLayerName(self, name):
        """Set name of layer to be added to layer tree
        """
        self.o_layer_name = name

    def GetOutputLayerName(self):
        return self.o_layer_name

    def GetCapFile(self):
        """Get path to file where capabilities are saved
        """
        return self.cap_file

    def GetWebService(self):
        """Get web service
        """
        return self.ws


class LayersList(TreeListCtrl, listmix.ListCtrlAutoWidthMixin):

    def __init__(self, parent, web_service, style, pos=wx.DefaultPosition):
        """List of layers and styles available in capabilities file
        """
        self.parent = parent
        self.ws = web_service

        TreeListCtrl.__init__(self, parent=parent, id=wx.ID_ANY, style=style)

        # setup mixins
        listmix.ListCtrlAutoWidthMixin.__init__(self)
        if self.ws != 'OnEarth':
            self.AddColumn(_('Name'))
            self.AddColumn(_('Type'))
        else:
            self.AddColumn(_('Layer name'))

        self.SetMainColumn(0)  # column with the tree
        self.setResizeColumn(0)

        self.root = None
        self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnListSelChanging)

        self.layerSelected = Signal('LayersList.layerSelected')

    def LoadData(self, cap=None):
        """Load data into list
        """
        # detete first all items
        self.DeleteAllItems()

        if not cap:
            return

        def AddLayerChildrenToTree(parent_layer, parent_item):
            """Recursive function which adds all capabilities
            layers/styles to the LayersList.
            """
            def gettitle(layer):
                """Helper function"""
                if layer.GetLayerData('title') is not None:
                    layer_title = layer.GetLayerData('title')
                elif layer.GetLayerData('name') is not None:
                    layer_title = layer.GetLayerData('name')
                else:
                    layer_title = str(layer.GetId())

                return layer_title

            def addlayer(layer, item):

                if self.ws != 'OnEarth':
                    self.SetItemText(item, _('layer'), 1)

                styles = layer.GetLayerData('styles')

                def_st = None
                for st in styles:

                    if st['name']:
                        style_name = st['name']
                    else:
                        continue

                    if st['title']:
                        style_name = st['title']

                    if st['isDefault']:
                        def_st = st

                    style_item = self.AppendItem(item, style_name)
                    if self.ws != 'OnEarth':
                        self.SetItemText(style_item, _('style'), 1)

                    self.SetPyData(style_item, {'type': 'style',
                                                'layer': layer,  # it is parent layer of style
                                                'style': st})

                self.SetPyData(item, {'type': 'layer',  # is it layer or style?
                                      'layer': layer,  # Layer instance from web_services.cap_interface
                                      'style': def_st})  # layer can have assigned default style

            if parent_layer is None:
                parent_layer = cap.GetRootLayer()
                layer_title = gettitle(parent_layer)
                parent_item = self.AddRoot(layer_title)
                addlayer(parent_layer, parent_item)

            for layer in parent_layer.GetChildren():
                item = self.AppendItem(parent_item, gettitle(layer))
                addlayer(layer, item)
                AddLayerChildrenToTree(layer, item)

        AddLayerChildrenToTree(None, None)
        # self.ExpandAll(self.GetRootItem())

    def GetSelectedLayers(self):
        """Get selected layers/styles in LayersList

        :return: dict with these items:
                 * 'name'  : layer name used for request
                   if it is style, it is name of parent layer
                 * 'title' : layer title
                 * 'style' : {'name' : 'style name', title : 'style title'}
                 * 'cap_intf_l' : \*Layer instance from web_services.cap_interface
        """
        sel_layers = self.GetSelections()
        sel_layers_dict = []
        for s in sel_layers:
            try:
                layer = self.GetPyData(s)['layer']
            except ValueError:
                continue
            sel_layers_dict.append({
                'name': layer.GetLayerData('name'),
                'title': layer.GetLayerData('title'),
                'style': self.GetPyData(s)['style'],
                'cap_intf_l': layer
            })
        return sel_layers_dict

    def OnListSelChanging(self, event):
        """Do not allow selecting items, which cannot be requested from server.
        """
        def _emitSelected(layer):
            title = layer.GetLayerData('title')
            self.layerSelected.emit(title=title)

        def _selectRequestableChildren(item, list_to_check, items_to_sel):

            self.Expand(item)
            child_item, cookie = self.GetFirstChild(item)
            while child_item.IsOk():
                if  self.GetPyData(child_item)['layer'].IsRequestable() \
                        and not self.IsSelected(child_item):
                    items_to_sel.append(child_item)
                elif not self.GetPyData(child_item)['layer'].IsRequestable():
                    list_to_check.append(child_item)

                child_item, cookie = self.GetNextChild(item, cookie)

        cur_item = event.GetItem()
        if not self.GetPyData(cur_item)['layer'].IsRequestable():
            event.Veto()

            if not self.HasFlag(wx.TR_MULTIPLE):
                return

            _emitSelected(self.GetPyData(cur_item)['layer'])

            items_to_chck = []
            items_to_sel = []
            chck_item = cur_item

            while True:
                _selectRequestableChildren(
                    chck_item, items_to_chck, items_to_sel)
                if items_to_chck:
                    chck_item = items_to_chck.pop()
                else:
                    break

            while items_to_sel:
                self.SelectItem(items_to_sel.pop(), unselect_others=False)
        else:
            _emitSelected(self.GetPyData(cur_item)['layer'])

    def GetItemCount(self):
        """Required for listmix.ListCtrlAutoWidthMixin
        """
        return 0

    def GetCountPerPage(self):
        """Required for listmix.ListCtrlAutoWidthMixin
        """
        return 0

    def SelectLayers(self, l_st_list):
        """Select layers/styles in LayersList

        :param l_st_list: [{style : 'style_name', layer : 'layer_name'}, ...]
        :return: items from l_st_list which were not found
        """
        def checknext(item, l_st_list, items_to_sel):
            def compare(item, l_name, st_name):
                it_l_name = self.GetPyData(item)['layer'].GetLayerData('name')
                it_st = self.GetPyData(item)['style']
                it_type = self.GetPyData(item)['type']

                if it_l_name == l_name and ((not it_st and not st_name) or (
                        it_st and it_st['name'] == st_name and it_type == 'style')):

                    return True

                return False

            for i, l_st in enumerate(l_st_list):
                l_name = l_st['layer']
                st_name = l_st['style']

                if compare(item, l_name, st_name):
                    items_to_sel[i] = [item, l_st]
                    break

            if len(items_to_sel) == len(l_st_list):
                item = self.GetNext(item)
                if not item.IsOk():
                    return
                checknext(item, l_st_list, items_to_sel)

        self.UnselectAll()

        l_st_list = deepcopy(l_st_list)
        root_item = self.GetRootItem()

        items_to_sel = [None] * len(l_st_list)
        checknext(root_item, l_st_list, items_to_sel)

        # items are selected according to position in l_st_list
        # to be added to Layers order list in right order
        for i in items_to_sel:
            if not i:
                continue

            item, l_st = i
            un_o = True
            if self.HasFlag(wx.TR_MULTIPLE):
                un_o = False

            self.SelectItem(item, unselect_others=un_o)
            l_st_list.remove(l_st)

        return l_st_list


class WSManageSettingsWidget(ManageSettingsWidget):

    def __init__(self, parent, settingsFile, default_servers):

        ManageSettingsWidget.__init__(self, parent, settingsFile)
        self.default_servers = default_servers

    def _layout(self):

        self.btnAddDefaultServers = wx.Button(parent=self, id=wx.ID_ANY,
                                              label=_("Add default"))
        self.btnAddDefaultServers.Bind(wx.EVT_BUTTON, self.OnAddDefaultServers)

        ManageSettingsWidget._layout(self)
        self.settingsSizer.Add(item=self.btnAddDefaultServers,
                               flag=wx.RIGHT,
                               border=5)

    def OnAddDefaultServers(self, event):

        setts = self.GetSettings()
        self.servers_to_add = {}
        for k, v in self.default_servers.iteritems():
            if k not in setts.iterkeys():
                self.servers_to_add[k] = v
            elif v != setts[k]:
                GMessage(parent=self,
                         message=_("User defined server with same name "
                                   "as default server <%s> already exists.\n"
                                   "Keeping user defined server") % (k))

        if self.servers_to_add:
            self.AddSettings(self.servers_to_add)
