# Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
A class to hold topology geometries
"""
from typing import Any, Dict, Optional, Union

from here.content.content import Content
from here.content.hmc.baseattributes import BaseLayer
from here.platform.adapter import Adapter


class TopologyGeometry(BaseLayer):
    """
    A class to hold topology geometries
    """

    def __init__(self, content: Content, layer_id: str, adapter: Optional[Adapter] = None):
        """
        Instantiate a TopologyGeometry object.

        :param content: instance of the HMC root class
        :param layer_id: layer ID
        :param adapter: the ``Adapter`` to transform data between different representations,
            ``None`` to use the adapter defined in ``content``.
        """

        # call a parent constructor
        super().__init__(content, layer_id, adapter)

        # dictionary to hold segment objects, per partition
        self.segments: Dict[str, dict] = {}

        # dictionary to hold node objects, per partition
        self.nodes: Dict[str, dict] = {}

    def fill(self, pid):
        """
        Fetch tile objects for a given partition ID and fill segments and nodes dictionary
        if not already done

        :param pid: partition ID
        """
        # segments and nodes are filled at the same time
        # so checking segments should be sufficient
        if pid not in self.segments:
            # fetch partition
            partition = self.get_partition(pid)

            # fill segments dictionary
            self.segments[pid] = {}
            for seg in partition.segment:
                self.segments[pid][seg.identifier] = seg

            # fill nodes dictionary
            self.nodes[pid] = {}
            for node in partition.node:
                self.nodes[pid][node.identifier] = node

    def get_segments(self, pid, adapter: Optional[Adapter] = None) -> Union[dict, Any]:
        """
        Get segments for given partition ID and segment ID

        :param pid: partition ID
        :param adapter: the ``Adapter`` to transform data between different representations,
            ``None`` to use the adapter defined in the constructor or, if unset, in ``content``.
        :return: segment dict or an adapter-specific type
        """

        # fetch contents for this pid if not already done
        self.fill(pid)

        content_adapter = self._legacy_hmc_content_adapter(adapter)

        if pid not in self.segments:
            return None

        if content_adapter is None:
            return self.segments[pid]
        else:
            columns, data = self.get_tabular_data_segment(pid)
            return content_adapter.from_tabular(columns, data)

    def get_nodes(self, pid, adapter: Optional[Adapter] = None) -> Union[dict, Any]:
        """
        Get nodes for given partition ID and node ID

        :param pid: partition ID
        :param adapter: the ``Adapter`` to transform data between different representations,
            ``None`` to use the adapter defined in the constructor or, if unset, in ``content``.
        :return: node dict or an adapter-specific type
        """

        # fetch contents for this pid if not already done
        self.fill(pid)

        content_adapter = self._legacy_hmc_content_adapter(adapter)

        if pid not in self.nodes:
            return None

        if content_adapter is None:
            return self.nodes[pid]
        else:
            columns, data = self.get_tabular_data_node(pid)
            return content_adapter.from_tabular(columns, data)

    def get_tabular_data_segment(self, pid):
        """
        Get tabular data from segment dict
        :param pid: partition ID
        :return: columns, data
        """
        columns = (
            "partitionId",
            "identifier",
            "start_node_ref",
            "end_node_ref",
            "geometry",
            "length",
        )
        data = []
        if pid in self.segments:
            for sid, segment in self.segments[pid].items():
                data.append(
                    (
                        pid,
                        segment.identifier,
                        segment.start_node_ref,
                        segment.end_node_ref,
                        segment.geometry,
                        segment.length,
                    )
                )
        return columns, data

    def get_tabular_data_node(self, pid):
        """
        Get tabular data from segment dict
        :param pid: partition ID
        :return: columns, data
        """
        columns = ("partitionId", "identifier", "segment_ref", "geometry")
        data = []
        if pid in self.nodes:
            for nid, node in self.nodes[pid].items():
                data.append((pid, node.identifier, node.segment_ref, node.geometry))
        return columns, data
