"""
@package web_services.cap_interface

@brief Provides common interface for GUI web_services.widgets to capabilities data of
       web services.

List of classes:
 - cap_interface::CapabilitiesBase
 - cap_interface::LayerBase
 - cap_interface::WMSCapabilities
 - cap_interface::WMSLayer
 - cap_interface::WMTSCapabilities
 - cap_interface::WMTSLayer
 - cap_interface::OnEarthCapabilities
 - cap_interface::OnEarthLayer

(C) 2012 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 Stepan Turek <stepan.turek seznam.cz> (Mentor: Martin Landa)
"""

import os
import sys

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

# Import only after the path has been set up.
from wms_cap_parsers import (  # noqa: E402
    WMSCapabilitiesTree,
    WMTSCapabilitiesTree,
    OnEarthCapabilitiesTree,
)


class CapabilitiesBase:
    def GetLayerByName(self, name):
        """Find layer by name"""
        for layer in self.layers_by_id:
            if name == layer.GetLayerData("name"):
                return layer
        return None

    def GetRootLayer(self):
        """Get children layers"""
        if self.layers_by_id:
            return self.layers_by_id[0]
        return None


class LayerBase:
    def GetId(self):
        """Get layer id"""
        return self.id

    def GetChildren(self):
        """Get children layers"""
        return self.child_layers

    def GetLayerNode(self):
        """Get layer node"""
        return self.layer_node

    def AddChildLayer(self, layer):
        """Add child layer"""
        self.child_layers.append(layer)


class WMSCapabilities(CapabilitiesBase, WMSCapabilitiesTree):
    def __init__(self, cap_file, force_version=None):
        """Create common interface for web_services.widgets to WMS
        capabilities data
        """
        # checks all elements needed for creation of GetMap requests
        # by r.in.wms/d.wms modules, invalid elements are removed
        WMSCapabilitiesTree.__init__(self, cap_file, force_version)

        self.cap_node = self.getroot().find(self.xml_ns.Ns("Capability"))
        self.root_layer = self.cap_node.find(self.xml_ns.Ns("Layer"))

        self.layers_by_id = {}
        self._initializeLayerTree(self.root_layer)

    def _initializeLayerTree(self, parent_layer, id=0):
        """Build tree, which represents layers"""
        if id == 0:
            parent_layer = WMSLayer(parent_layer, id, self)
            self.layers_by_id[id] = parent_layer
            id += 1

        layer_nodes = parent_layer.GetLayerNode().findall(self.xml_ns.Ns("Layer"))

        for node in layer_nodes:
            layer = WMSLayer(node, id, self)
            parent_layer.AddChildLayer(layer)
            self.layers_by_id[id] = layer
            id += 1
            id = self._initializeLayerTree(layer, id)

        return id

    def GetFormats(self):
        """Get supported formats"""
        request_node = self.cap_node.find(self.xml_ns.Ns("Request"))
        get_map_node = request_node.find(self.xml_ns.Ns("GetMap"))
        format_nodes = get_map_node.findall(self.xml_ns.Ns("Format"))

        return [node.text for node in format_nodes]


class WMSLayer(LayerBase):
    def __init__(self, layer_node, id, cap):
        """Common interface for web_services.widgets to WMS
        capabilities <Layer> element
        """
        self.id = id
        self.cap = cap
        self.child_layers = []
        self.layer_node = layer_node
        self.xml_ns = self.cap.getxmlnshandler()

    def GetLayerData(self, param):
        """Get layer data"""
        title = self.xml_ns.Ns("Title")
        name = self.xml_ns.Ns("Name")

        if param == "title":
            title_node = self.layer_node.find(title)
            if title_node is not None:
                return title_node.text
            return None

        if param == "name":
            name_node = self.layer_node.find(name)
            if name_node is not None:
                return name_node.text
            return None

        if param == "format":
            return self.cap.GetFormats()

        if param == "styles":
            styles = []
            style = self.xml_ns.Ns("Style")
            for style_node in self.layer_node.findall(style):
                style_name = ""
                style_title = ""

                if style_node.find(title) is not None:
                    style_title = style_node.find(title).text
                if style_node.find(name) is not None:
                    style_name = style_node.find(name).text

                styles.append(
                    {"title": style_title, "name": style_name, "isDefault": False}
                )
            return styles

        if param == "srs":
            projs_nodes = self.layer_node.findall(self.xml_ns.Ns(self.cap.getprojtag()))

            projs = []
            if projs_nodes is None:
                return projs
            for p in projs_nodes:
                projs.append(p.text.strip())
            return projs

    def IsRequestable(self):
        """Is it possible to use the layer for WMS GetMap request?"""
        name = self.xml_ns.Ns("Name")
        name_node = self.layer_node.find(name)

        return name_node is not None


class WMTSCapabilities(CapabilitiesBase, WMTSCapabilitiesTree):
    def __init__(self, cap_file):
        """Create common interface for web_services.widgets to WMTS
        capabilities data
        """
        # checks all elements needed for creation of GetTile requests
        # by r.in.wms/d.wms modules, invalid elements are removed
        WMTSCapabilitiesTree.__init__(self, cap_file)

        contents = self._find(self.getroot(), "Contents", self.xml_ns.NsWmts)
        layers = self._findall(contents, "Layer", self.xml_ns.NsWmts)

        self.layers_by_id = {}

        id = 0
        root_layer = WMTSLayer(None, id, self)
        self.layers_by_id[id] = root_layer

        for layer_node in layers:
            id += 1
            self.layers_by_id[id] = WMTSLayer(layer_node, id, self)
            root_layer.child_layers.append(self.layers_by_id[id])


class WMTSLayer(LayerBase):
    def __init__(self, layer_node, id, cap):
        """Common interface for web_services.widgets to WMTS
        capabilities <Layer> element
        """
        self.id = id
        self.cap = cap
        self.child_layers = []
        self.layer_node = layer_node
        self.xml_ns = self.cap.getxmlnshandler()
        self.projs = self._getProjs()

    def GetLayerData(self, param):
        """Get layer data"""
        title = self.xml_ns.NsOws("Title")
        name = self.xml_ns.NsOws("Identifier")

        if self.layer_node is None and param in {"title", "name"}:
            return None
        if self.layer_node is None:
            return []

        if param == "title":
            title_node = self.layer_node.find(title)
            if title_node is not None:
                return title_node.text
            return None

        if param == "name":
            name_node = self.layer_node.find(name)
            if name_node is not None:
                return name_node.text
            return None

        if param == "styles":
            styles = []
            for style_node in self.layer_node.findall(self.xml_ns.NsWmts("Style")):
                style_name = ""
                style_title = ""

                if style_node.find(title) is not None:
                    style_title = style_node.find(title).text
                if style_node.find(name) is not None:
                    style_name = style_node.find(name).text

                is_def = False
                if (
                    "isDefault" in style_node.attrib
                    and style_node.attrib["isDefault"] == "true"
                ):
                    is_def = True

                styles.append(
                    {"title": style_title, "name": style_name, "isDefault": is_def}
                )

            return styles

        if param == "format":
            return [
                frmt.text.strip()
                for frmt in self.layer_node.findall(self.xml_ns.NsWmts("Format"))
            ]

        if param == "srs":
            return self.projs

    def _getProjs(self):
        """Get layer projections"""
        layer_projs = []
        if self.layer_node is None:
            return layer_projs

        mat_set_links = self.layer_node.findall(self.xml_ns.NsWmts("TileMatrixSetLink"))

        contents = self.cap.getroot().find(self.xml_ns.NsWmts("Contents"))
        tileMatrixSets = contents.findall(self.xml_ns.NsWmts("TileMatrixSet"))

        for link in mat_set_links:
            mat_set_link_id = link.find(self.xml_ns.NsWmts("TileMatrixSet")).text
            if not mat_set_link_id:
                continue

            for mat_set in tileMatrixSets:
                mat_set_id = mat_set.find(self.xml_ns.NsOws("Identifier")).text
                if mat_set_id and mat_set_id != mat_set_link_id:
                    continue
                mat_set_srs = mat_set.find(
                    self.xml_ns.NsOws("SupportedCRS")
                ).text.strip()
                layer_projs.append(mat_set_srs)
        return layer_projs

    def IsRequestable(self) -> bool:
        """Is it possible to use the layer for WMTS request?"""
        return self.layer_node is not None


class OnEarthCapabilities(CapabilitiesBase, OnEarthCapabilitiesTree):
    def __init__(self, cap_file):
        """Create Common interface for web_services.widgets to
        NASA OnEarth tile service data (equivalent to  WMS, WMTS
        capabilities data)
        """
        # checks all elements needed for creation of GetMap requests
        # by r.in.wms/d.wms modules, invalid elements are removed
        OnEarthCapabilitiesTree.__init__(self, cap_file)

        self.layers_by_id = {}
        self._initializeLayerTree(self.getroot())

    def _initializeLayerTree(self, parent_layer, id=0):
        """Build tree, which represents layers"""
        if id == 0:
            tiled_patterns = parent_layer.find("TiledPatterns")
            layer_nodes = tiled_patterns.findall("TiledGroup")
            layer_nodes += tiled_patterns.findall("TiledGroups")
            parent_layer = OnEarthLayer(None, None, id, self)
            self.layers_by_id[id] = parent_layer
            id += 1
        else:
            layer_nodes = parent_layer.layer_node.findall("TiledGroup")
            layer_nodes += parent_layer.layer_node.findall("TiledGroups")

        for layer_node in layer_nodes:
            layer = OnEarthLayer(layer_node, parent_layer, id, self)
            self.layers_by_id[id] = layer
            id += 1
            parent_layer.child_layers.append(layer)
            if layer_node.tag == "TiledGroups":
                id = self._initializeLayerTree(layer, id)

        return id


class OnEarthLayer(LayerBase):
    def __init__(self, layer_node, parent_layer, id, cap):
        """Common interface for web_services.widgets to NASA Earth
        capabilities <TiledGroup>/<TiledGroups> element
        (equivalent to  WMS, WMTS <Layer> element)
        """
        self.id = id
        self.cap = cap
        self.layer_node = layer_node
        self.child_layers = []
        self.parent_layer = parent_layer

    def IsRequestable(self) -> bool:
        """Is it possible to use the layer for NASA OnEarth GetMap request?"""
        return not (self.layer_node is None or self.layer_node.tag == "TiledGroups")

    def GetLayerData(self, param):
        """Get layer data"""
        if self.layer_node is None and param in {"title", "name"}:
            return None
        if self.layer_node is None:
            return []

        if param == "title":
            title_node = self.layer_node.find("Title")
            if title_node is not None:
                return title_node.text
            return None

        if param == "name":
            name_node = self.layer_node.find("Name")
            if name_node is not None:
                return name_node.text
            return None

        if param == "styles":
            return []

        if param == "format":
            return []

        if param == "srs":
            return []
