from __future__ import annotations

from typing import Any, Dict, Iterator, List, Optional

from supervisely.annotation.tag_meta import TagMeta
from supervisely.annotation.tag_meta_collection import TagMetaCollection
from supervisely.pointcloud_annotation.pointcloud_episode_tag import (
    PointcloudEpisodeTag,
)
from supervisely.pointcloud_annotation.pointcloud_tag_collection import (
    PointcloudTagCollection,
)
from supervisely.video_annotation.key_id_map import KeyIdMap


class PointcloudEpisodeTagCollection(PointcloudTagCollection):
    """
    Collection with :class:`~supervisely.pointcloud_annotation.pointcloud_episode_tag.PointcloudEpisodeTag` instances.
    :class:`~supervisely.pointcloud_annotation.pointcloud_episode_tag_collection.PointcloudEpisodeTagCollection` object is immutable.

    :Usage Example:

        .. code-block:: python

            import supervisely as sly

            # Create TagMetas (see class TagMeta for additional information about creating TagMetas)
            colors = ["brown", "white", "black", "red", "chocolate", "gold", "grey"]
            meta_car_color = sly.TagMeta('car color', sly.TagValueType.ONEOF_STRING, possible_values=colors)

            # Create tags
            tag_car_color_white = sly.PointcloudEpisodeTag(meta_car_color, value="white", frame_range=(15, 20))
            tag_car_color_red = sly.PointcloudEpisodeTag(meta_car_color, value="red", frame_range=(11, 22))
            tags_arr = [tag_car_color_white, tag_car_color_red]

            # Create PointcloudEpisodeTagCollection from point cloud episodes Tags
            tags = sly.PointcloudEpisodeTagCollection(tags_arr)

            # Add item to PointcloudEpisodeTagCollection
            meta_bus = sly.TagMeta('bus', sly.TagValueType.NONE)
            tag_bus = sly.PointcloudEpisodeTag(meta_bus, frame_range=(11, 14))

            # Remember that PointcloudEpisodeTagCollection is immutable, and we need to assign new instance of PointcloudEpisodeTagCollection to a new variable
            tags = tags.add(tag_bus)

            # You can also add multiple items to collection
            meta_truck = sly.TagMeta('truck', sly.TagValueType.NONE)
            meta_moto = sly.TagMeta('moto', sly.TagValueType.NONE)

            tag_truck = sly.PointcloudEpisodeTag(meta_truck, frame_range=(6, 10))
            tag_moto = sly.PointcloudEpisodeTag(meta_moto, frame_range=(11, 15))

            additional_tags = [tag_truck, tag_moto]

            tags = tags.add_items(additional_tags)

            # Has key, checks if given key exist in collection
            tags.has_key(meta_moto.key())
            # Output: True

            # Find intersection of given list of instances with collection items
            meta_bus = sly.TagMeta('bus', sly.TagValueType.NONE)
            tag_bus = sly.PointcloudEpisodeTag(meta_bus, frame_range=(0, 5))

            meta_truck = sly.TagMeta('truck', sly.TagValueType.NONE, frame_range=(6, 11))
            tag_truck = sly.PointcloudEpisodeTag(meta_truck)

            tags_vehicles = sly.PointcloudEpisodeTagCollection([tag_bus, tag_truck])

            tags_intersections = tags.intersection(tags_vehicles)
            print(tags_intersections.to_json())
            # Output: []

            # Let's add the moto Tag from another collection and compare them again
            tags_vehicles = tags_vehicles.add(tag_moto)

            tags_intersections = tags.intersection(tags_vehicles)
            print(tags_intersections.to_json())
            # Output: [
            #     {
            #         "name": "moto",
            #         "frameRange": [11, 15],
            #         "key": "3f37e10a658e440db3ab9f5c0be7fb67"
            #     }
            # ]

            # Find difference between collection and given list of Tags or PointcloudEpisodeTagCollection
            meta_car = sly.TagMeta('car', sly.TagValueType.NONE)
            tag_car = sly.PointcloudEpisodeTag(meta_car, frame_range=(6, 11))

            meta_bicycle = sly.TagMeta('bicycle', sly.TagValueType.NONE)
            tag_bicycle = sly.PointcloudEpisodeTag(meta_bicycle, frame_range=(6, 11))

            tags_vehicles = sly.PointcloudEpisodeTagCollection([tag_car, tag_bicycle])

            meta_pedestrian = sly.TagMeta('pedestrian', sly.TagValueType.NONE)
            tag_pedestrian = sly.PointcloudEpisodeTag(meta_pedestrian, frame_range=(14, 20))

            meta_road = sly.TagMeta('road', sly.TagValueType.NONE)
            tag_road = sly.PointcloudEpisodeTag(meta_road, frame_range=(13, 22))

            difference = tags_vehicles.difference([tag_pedestrian, tag_road])
            print(difference.to_json())
            # Output: [
            #     {
            #         "name": "car",
            #         "frameRange": [6, 11],
            #         "key": "eba949cb996e44ac8aebec72a0875b13"
            #     },
            #     {
            #         "name": "bicycle",
            #         "frameRange": [6, 11],
            #         "key": "f9f03baec21e428ba04525d8df003e22"
            #     }
            # ]

            # Merge collection and given list of collections
            tags_vehicles = sly.PointcloudEpisodeTagCollection([tag_car, tag_bicycle])
            tags_merge = sly.PointcloudEpisodeTagCollection([tag_pedestrian, tag_road])

            merged_collections = tags_vehicles.merge(tags_merge)
            print(merged_collections.to_json())
            # Output: [
            #     {
            #         "frameRange": [6, 11],
            #         "key": "a6527453a3d34e68865bc0ad4b66f673",
            #         "name": "car"
            #     },
            #     {
            #         "frameRange": [6, 11],
            #         "key": "e9a9a306fd9c4ea180d1edf2a6fb1cf0",
            #         "name": "bicycle"
            #     },
            #     {
            #         "frameRange": [14, 20],
            #         "key": "8480532ff4db4600839ab403b1d0ab85",
            #         "name": "pedestrian"
            #     },
            #     {
            #         "frameRange": [13, 22],
            #         "key": "828f58c0c5174dbba40f7374dcc8016a",
            #         "name": "road"
            #     }
            # ]
    """

    item_type = PointcloudEpisodeTag

    def __iter__(self) -> Iterator[PointcloudEpisodeTag]:
        return next(self)

    @classmethod
    def from_api_response(
        cls,
        data: List[Dict],
        tag_meta_collection: TagMetaCollection,
        id_to_tagmeta: Optional[Dict[int, TagMeta]] = None,
    ) -> PointcloudEpisodeTagCollection:
        """
        Create a PointcloudEpisodeTagCollection object from API response data.

        :param data: API response data.
        :type data: List[Dict]
        :param tag_meta_collection: Tag metadata collection.
        :type tag_meta_collection: :class:`~supervisely.annotation.tag_meta_collection.TagMetaCollection`
        :param id_to_tagmeta: Mapping of tag IDs to tag metadata.
        :type id_to_tagmeta: Optional[Dict[int, :class:`~supervisely.annotation.tag_meta.TagMeta`]]
        :returns: Pointcloud episode tags collection.
        :rtype: :class:`~supervisely.pointcloud_annotation.pointcloud_episode_tag_collection.PointcloudEpisodeTagCollection`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                project_id = 18428
                pcd_id = 19481209
                project_meta_json = api.project.get_meta(project_id)
                project_meta = sly.ProjectMeta.from_json(project_meta_json)
                pcd_info = api.pointcloud_episode.get_info_by_id(pcd_id)

                tag_collection = sly.PointcloudEpisodeTagCollection.from_api_response(
                    pcd_info.tags, project_meta.tag_metas
                )
                print(tag_collection)

                # Output:
                # Tags:
                # +-------+--------------+-------+
                # |  Name |  Value type  | Value |
                # +-------+--------------+-------+
                # | color | oneof_string |  red  |
                # |  car  |     none     |  None |
                # +-------+--------------+-------+
        """

        return super().from_api_response(data, tag_meta_collection, id_to_tagmeta=id_to_tagmeta)

    # def get_by_name(self, tag_name: str) -> List[PointcloudEpisodeTag]:
    #     return super().get_by_name(tag_name)

    # def get_single_by_name(self, tag_name: str) -> PointcloudEpisodeTag:
    #     return super().get_single_by_name(tag_name)

    @classmethod
    def from_json(
        cls,
        data: List[Dict],
        tag_meta_collection: TagMetaCollection,
        key_id_map: Optional[KeyIdMap] = None,
    ) -> PointcloudEpisodeTagCollection:
        """
        Convert a list with dicts in json format to :class:`~supervisely.pointcloud_annotation.pointcloud_episode_tag_collection.PointcloudEpisodeTagCollection`.
        Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.

        :param data: List with dicts in json format.
        :type data: :class:`List[Dict]`
        :param tag_meta_collection: Tag metadata collection.
        :type tag_meta_collection: :class:`~supervisely.annotation.tag_meta_collection.TagMetaCollection`
        :param key_id_map: Key ID map.
        :type key_id_map: :class:`~supervisely.video_annotation.key_id_map.KeyIdMap`
        :returns: Pointcloud episode tags collection.
        :rtype: :class:`~supervisely.pointcloud_annotation.pointcloud_episode_tag_collection.PointcloudEpisodeTagCollection`

        :Usage Example:

            .. code-block:: python

                import supervisely as sly

                # Initialize TagMetaCollection
                meta_weather = sly.TagMeta('Weather', sly.TagValueType.ANY_STRING)

                seasons = ["Winter", "Spring", "Summer", "Autumn"]
                meta_season = sly.TagMeta('Season', sly.TagValueType.ONEOF_STRING, possible_values=seasons)

                metas_arr = [meta_weather, meta_season]
                tag_metas = sly.TagMetaCollection(metas_arr)

                data = [
                    {"name":"Weather", "value":"Sunny"},
                    {"name":"Season", "value":"Spring"}
                ]

                tags = sly.PointcloudEpisodeTagCollection.from_json(data, tag_metas)
        """

        return super().from_json(data, tag_meta_collection, key_id_map=key_id_map)
