# Copyright (C) 2022-2023 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.

"""
Bindings for the Associated Node and Segments from Topology geometry and ADAS attribute Layers.
"""
from dataclasses import dataclass
from typing import Dict, List, Mapping, Optional

from google.protobuf.message import Message
from here.content.base import ContentIndexer, ContentPartition, SingleLayerBinding
from here.content.content import Content as ParentContent
from here.content.content import hmc_schema_version
from here.content.hmc2.base_attributes import BaseAttributesMultiLayerBinding
from here.content.hmc2.topology_geometry import Node, TopologyGeometry
from here.content.utils.proto import decode_enum, decode_message
from here.platform.adapter import Adapter, DecodedMessage, Partition


@dataclass
class SegmentAnchor:
    """Segment Ref"""

    partition_id: Partition
    oriented_segment_ref: List[DecodedMessage]
    first_segment_start_offset: DecodedMessage
    last_segment_end_offset: DecodedMessage
    attribute_orientation: str
    indexer = ContentIndexer(partition="partition_id")


@dataclass
class CurvatureHeading:
    """Curvature Heading"""

    partition_id: Partition
    segment_anchors: List[DecodedMessage]
    curvature: int
    heading: int
    indexer = ContentIndexer(partition="partition_id")


@dataclass
class Elevation:
    """Elevation"""

    partition_id: Partition
    segment_anchors: List[DecodedMessage]
    elevation: int
    accuracy: str
    signed_elevation: int
    node_anchors: Optional[List[DecodedMessage]] = None
    indexer = ContentIndexer(partition="partition_id")


@dataclass
class Slope:
    """slope"""

    partition_id: Partition
    segment_anchors: List[DecodedMessage]
    slope: int
    accuracy: str
    indexer = ContentIndexer(partition="partition_id")


@dataclass
class LinkAccuracy:
    """Link accuracy"""

    partition_id: Partition
    segment_anchors: List[DecodedMessage]
    link_accuracy: int
    indexer = ContentIndexer(partition="partition_id")


@dataclass
class BuiltupAreaRoad:
    """Builtup Area Road"""

    partition_id: Partition
    segment_anchors: List[DecodedMessage]
    built_up_area_road: bool
    built_up_area_road_verified: bool
    indexer = ContentIndexer(partition="partition_id")


class AdasAttributes(BaseAttributesMultiLayerBinding):
    """Class to associate segment with adas attributes and road topology geometry"""

    _content: ParentContent

    def __init__(self, content: ParentContent, layer_id: str, adapter: Optional[Adapter] = None):
        """
        Instantiate a class Multilayer Topology Geometry binding.
        Assign content to Class attribute _content.

        :param content: Content object that references a catalog at a fixed version
        :param layer_id: ID of the only layer that contains the content
        :param adapter: the ``Adapter`` to transform data between different representations,
            ``None`` to use the adapter defined in ``content``.
        """
        super().__init__(content, layer_id, adapter)
        AdasAttributes._content = content

    @classmethod
    def schema_hrn(cls) -> List[str]:
        """
        :return: the HRN of schema this binding can parse and index
        """

        return [f"hrn:here: schema:::com.here.schema.rib:adas-attributes_v2:{hmc_schema_version}"]

    @classmethod
    def associated_layers(cls) -> Dict[str, SingleLayerBinding]:
        """
        Provide associated dependent/independent layers to the base layer.

        :return: dict of key as single layer and value as SingleLayerBinding Object.
        """
        return {
            "topology_geometry": TopologyGeometry(
                content=cls._content, layer_id="topology-geometry"
            )
        }

    @classmethod
    def object_types(cls) -> Mapping[str, type]:
        """
        :return: the types of attributes supported by this binding
        """
        return {
            "curvature_heading": CurvatureHeading,
            "elevation": Elevation,
            "slope": Slope,
            "link_accuracy": LinkAccuracy,
            "built_up_area_road": BuiltupAreaRoad,
        }

    @classmethod
    def _internal_object_types(cls) -> Mapping[str, type]:
        """
        :return: the types of attributes supported by this binding
        """
        return {}

    @classmethod
    def parse_and_index(cls, partition_id: str, msg: Message) -> ContentPartition:
        """
        Parse and index decoded message.

        :param partition_id: identifier of the partition
        :param msg: decoded content from the layer, in the form of a Protobuf message
        :return: the content of a partition parsed and indexed in its own data structure
        """
        assert hasattr(msg, "curvature_heading")
        assert hasattr(msg, "elevation")
        assert hasattr(msg, "slope")
        assert hasattr(msg, "link_accuracy")
        assert hasattr(msg, "built_up_area_road")

        topology_layer = cls.associated_layers()["topology_geometry"]
        nodes = topology_layer.get(partition_id, "node")
        segments = topology_layer.get(partition_id, "segment")

        def parse_node_anchors(node_indices: List[int]) -> List[Node]:
            return [node_anchor[index] for index in node_indices]

        def parse_segment_anchors(segment_indices: List[int]) -> List[SegmentAnchor]:
            return [segment_anchor[index] for index in segment_indices]

        node_anchor = [
            (
                nodes[p.node_ref.partition_name][p.node_ref.identifier]
                if nodes[p.node_ref.partition_name]
                else None
            )
            for p in msg.node_anchor  # type: ignore
        ]

        segment_anchor = [
            SegmentAnchor(
                partition_id=Partition(partition_id),
                oriented_segment_ref=[
                    (
                        (
                            segments[n.segment_ref.partition_name][n.segment_ref.identifier],
                            n.inverted,
                        )
                        if segments[n.segment_ref.partition_name]
                        else None
                    )
                    for n in p.oriented_segment_ref
                ],
                first_segment_start_offset=decode_message(p.first_segment_start_offset),
                last_segment_end_offset=decode_message(p.last_segment_end_offset),
                attribute_orientation=str(p.attribute_orientation),
            )
            for p in msg.segment_anchor  # type: ignore
        ]

        curvature_heading = [
            CurvatureHeading(
                partition_id=Partition(partition_id),
                segment_anchors=parse_segment_anchors(p.segment_anchor_index),
                curvature=int(p.curvature),
                heading=int(p.heading),
            )
            for p in msg.curvature_heading  # type: ignore
        ]

        slope = [
            Slope(
                partition_id=Partition(partition_id),
                segment_anchors=parse_segment_anchors(p.segment_anchor_index),
                slope=int(p.slope),
                accuracy=decode_enum(p, "accuracy"),
            )
            for p in msg.slope  # type: ignore
        ]

        elevation = [
            Elevation(
                partition_id=Partition(partition_id),
                node_anchors=parse_node_anchors(p.node_anchor_index),
                segment_anchors=parse_segment_anchors(p.segment_anchor_index),
                elevation=int(p.elevation),
                accuracy=decode_enum(p, "accuracy"),
                signed_elevation=int(p.signed_elevation),
            )
            for p in msg.elevation  # type: ignore
        ]

        link_accuracy = [
            LinkAccuracy(
                partition_id=Partition(partition_id),
                segment_anchors=parse_segment_anchors(p.segment_anchor_index),
                link_accuracy=int(p.link_accuracy),
            )
            for p in msg.link_accuracy  # type: ignore
        ]

        built_up_area_road = [
            BuiltupAreaRoad(
                partition_id=Partition(partition_id),
                segment_anchors=parse_segment_anchors(p.segment_anchor_index),
                built_up_area_road=bool(p.built_up_area_road),
                built_up_area_road_verified=bool(p.built_up_area_road_verified),
            )
            for p in msg.built_up_area_road  # type: ignore
        ]

        return ContentPartition(
            cls,
            curvature_heading=curvature_heading,
            slope=slope,
            elevation=elevation,
            link_accuracy=link_accuracy,
            built_up_area_road=built_up_area_road,
        )
