# Copyright (C) 2021-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.
"""HERE Map Matcher module
"""
import json
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Tuple, cast

from here.platform.api.factory.apifactory import API, APIFactory
from here.platform.api.map_matching_api import MapMatchingApi

"""HERE map matcher abstraction."""
if TYPE_CHECKING:
    from here.platform.platform import Platform


class RoadAccess(Enum):
    """Road access enum"""

    car = 0
    unrestricted = 1


class MapMatcher:
    """
    HERE map matcher abstraction.

    Access basic fields like versions and hrn of map matcher.
    Example: ``map_matcher.hrn``
    """

    def __init__(self, version: str, hrn: str, platform: "Platform"):
        """
        Instantiate the :class:`MapMatcher` for the given :param:`hrn`
        and :param:`version`

        :param hrn: the HERE Resource Name of the map matcher
        :param version: map version to use or `latest` to use the latest available version.
        :param platform: instance of Platform
        """
        self.version = version
        self.hrn = hrn
        self.platform = platform
        self._api_factory: APIFactory = self.platform.api_factory

    @property
    def _map_matching_api(self) -> MapMatchingApi:
        return cast(MapMatchingApi, self._api_factory.get_api(API.MAP_MATCHING))

    def get_matched_path(
        self, lat_lon_list: List[Dict], road_access: RoadAccess, interpolate: bool = False
    ) -> Tuple[List, List]:
        """
        Get matched path corresponding to the latitude and longitude list.
        Example: ``[{"lat": 52, "lng": 13.26952}, {"lat": 52.499, "lng": 13.269}]``

        :param lat_lon_list: list of dict of lat and long
        :param road_access: map matching mode to use. Valid values are `unrestricted` and `car`
        :param interpolate: specifies whether to add interpolated points to sub-paths in order to
        retrieve the full path geometry. Default value is `False`
        :raises ValueError: Incorrect road access value
        :return: List of matched path refs and list of matched path points
        """
        data = {"trace": lat_lon_list}
        if road_access == "car":
            road_access_str = RoadAccess.car.name
        elif road_access == "unrestricted":
            road_access_str = RoadAccess.unrestricted.name
        else:
            raise ValueError("Invalid road access.")
        matched_path_resp = self._map_matching_api.get_matched_path(
            version=self.version, road_access=road_access_str, interpolate=interpolate, data=data
        )
        matched_path_json = json.loads(matched_path_resp.replace("'", '"'))
        matched_path_ref_list = []
        matched_path_points_list = []
        s = matched_path_json["refReplacements"]
        for matched_path in matched_path_json["matchedSubPaths"]:
            matched_path_ref = matched_path["segments"][0]["ref"]
            for x, y in s.items():
                matched_path_ref = matched_path_ref.replace(f"${x}", y)
            matched_path_ref_list.append(matched_path_ref.split("#", 1)[0])
            matched_path_points_list.append(matched_path["segments"][0]["points"][0])
        return matched_path_ref_list, matched_path_points_list

    def get_matched_point(
        self, lat_lon: Dict, road_access: RoadAccess, interpolate: bool = False
    ) -> Tuple[List, List]:
        """
        Get matched point corresponding to the latitude and longitude.
        Example: ``{"lat": 52, "lng": 13.26952}``

        :param lat_lon: dict of lat and long
        :param road_access: map matching mode to use. Valid values are `unrestricted` and `car`
        :param interpolate: specifies whether to add interpolated points to sub-paths in order to
        retrieve the full path geometry. Default value is `False`
        :raises ValueError: Incorrect road access value
        :return: List of matched point refs and list of matched points
        """
        data = {"trace": [lat_lon, lat_lon]}
        if road_access == "car":
            road_access_str = RoadAccess.car.name
        elif road_access == "unrestricted":
            road_access_str = RoadAccess.unrestricted.name
        else:
            raise ValueError("Invalid road access.")
        matched_path_resp = self._map_matching_api.get_matched_path(
            version=self.version, road_access=road_access_str, interpolate=interpolate, data=data
        )
        matched_path_json = json.loads(matched_path_resp.replace("'", '"'))
        matched_point_ref_list = []
        matched_points_list = []
        s = matched_path_json["refReplacements"]
        for matched_path in matched_path_json["matchedSubPaths"]:
            matched_path_ref = matched_path["segments"][0]["ref"]
            for x, y in s.items():
                matched_path_ref = matched_path_ref.replace(f"${x}", y)
            matched_point_ref_list.append(matched_path_ref.split("#", 1)[0])
            matched_points_list.append(matched_path["segments"][0]["points"][0])
        return matched_point_ref_list, matched_points_list
