# 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.

"""
Helper utilities for the Kepler-based inspector
"""

import json
from typing import Dict, Union

import geojson
import geopandas as gpd
from geopandas import GeoDataFrame
from here.inspector.utils import Features
from mapquadlib import HereQuad


def _geojson_with_longkey(tile_id: int) -> dict:
    """
    Return GeoJSON geometry for given mapquad with its long key as parameter.

    The quad's "long key" (an integer) will appear as "tile_id" in the
    properties.
    :param tile_id: Tile id
    :returns: A dict representing a GeoJSON geometry (polygon) for the mapquad.
    """

    quad = HereQuad.from_long_key(tile_id)
    gj: dict = quad.bounding_box._geojson()
    if gj:
        fix_coordinates(gj)
    feature = {"type": "Feature", "geometry": gj, "properties": {"tile_id": tile_id}}
    return feature


def fix_coordinates(geometry: Dict) -> Dict:
    """
    fix_coordinates modifies any coordinate values of (180, 90) or (-180, -90)
    in a geometry dict to (179.999, 89.999) or (-179.999, -89.999) respectively.

    The function takes in a single argument:
    :param geometry: A dictionary representing a geometry in GeoJSON format.
    It should contain a 'coordinates' key with list of coordinates.

    :returns: modified geometry dictionary.
    """
    for i, coord in enumerate(geometry["coordinates"][0]):
        if coord[0] == 180.0:
            geometry["coordinates"][0][i][0] = 179.999
        elif coord[0] == -180.0:
            geometry["coordinates"][0][i][0] = -179.999
        if coord[1] == 90.0:
            geometry["coordinates"][0][i][1] = 89.999
        elif coord[1] == -90.0:
            geometry["coordinates"][0][i][1] = -89.999
    return geometry


def convert_to_2d_2(gdf: Union[Features, GeoDataFrame]) -> Union[Features, GeoDataFrame]:
    """
    Convert the feature object from 3D to 2D
     by removing the z coordinates from the geometry.
    If a GeoDataFrame is passed, it will convert all
    the geometries of all the features in the dataframe.
    :param gdf: A Feature object or GeoDataFrame with geometry and coordinates in 3D.
    :return: A Feature object or GeoDataFrame
    with the same properties but in 2D by removing the z coordinates.
    """

    # Define a recursive function to convert nested coordinates to 2D
    def convert_nested_coords_to_2d(coords):
        for coord in coords:
            if isinstance(coord[0], list):
                convert_nested_coords_to_2d(coord)
            else:
                coord[0] = (
                    179.9999
                    if coord[0] == 180.0
                    else -179.9999
                    if coord[0] == -180.0
                    else coord[0]
                )
                coord[1] = (
                    89.9999 if coord[1] == 90.0 else -89.9999 if coord[1] == -90.0 else coord[1]
                )
                coord[:] = coord[:2]

    def conversion(geom):
        # Check if the object is a Feature
        if geom["type"] == "Feature":
            # Get the geometry of the feature
            geometry = geom["geometry"]

            # Check if the geometry is a Point
            if geometry["type"] == "Point":
                # Convert the coordinates to 2D
                geometry["coordinates"] = geometry["coordinates"][:2]
            # Check if the geometry is a LineString or Polygon
            elif geometry["type"] in ["LineString", "Polygon"]:
                # Convert the coordinates to 2D
                convert_nested_coords_to_2d(geometry["coordinates"])
            # Check if the geometry is a MultiPoint, MultiLineString, or MultiPolygon
            elif geometry["type"] in ["MultiPoint", "MultiLineString", "MultiPolygon"]:
                # Convert the coordinates to 2D
                for subcoords in geometry["coordinates"]:
                    convert_nested_coords_to_2d(subcoords)
        # Check if the object is a FeatureCollection
        elif geom["type"] == "FeatureCollection":
            # Loop through the features in the collection
            for feature in geom["features"]:
                # Convert the feature to 2D
                convert_to_2d_2(feature)
        return geom

    if isinstance(gdf, gpd.GeoDataFrame):
        features = json.loads(gdf.to_json())
        for feature in features["features"]:
            conversion(feature["geometry"])
        gdf.set_geometry(gpd.GeoDataFrame.from_features(features).geometry)
    elif isinstance(gdf, (dict, geojson.feature.Feature, geojson.feature.FeatureCollection)):
        geom = gdf
        conversion(geom)
    return gdf


def process_dataframe(new_df_feature: Features):
    """
    Convert the Feature object from GeoJSON format to a Python dictionary.
    This function is useful when the add data method
    doesn't accept FeatureCollection object.
    :param new_df_feature: a Feature object in GeoJSON format.
    :return: a Python dictionary containing the same
    information as the input Feature object.
    """
    # Serialize the FeatureCollection to a JSON string
    json_string = geojson.dumps(new_df_feature)

    # Parse the JSON string into a dictionary
    new_json_string = json.loads(json_string)

    return new_json_string


HEIGHT = 600
