from __future__ import annotations

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

import supervisely.sly_logger as logger
from supervisely.annotation.tag_collection import TagCollection
from supervisely.annotation.tag_meta import TagMeta
from supervisely.annotation.tag_meta_collection import TagMetaCollection
from supervisely.pointcloud_annotation.pointcloud_tag import PointcloudTag
from supervisely.video_annotation.key_id_map import KeyIdMap


class PointcloudTagCollection(TagCollection):
    """
    Collection with :class:`~supervisely.pointcloud_annotation.pointcloud_tag.PointcloudTag` instances.
    :class:`~supervisely.pointcloud_annotation.pointcloud_tag_collection.PointcloudTagCollection` object is immutable.

    :Usage Example:

        .. code-block:: python

            import supervisely as sly

            # Create TagMetas (see class TagMeta for additional information about creating TagMetas)
            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)

            # Create Pointcloud Tags
            tag_weather = sly.PointcloudTag(meta_weather, value="Sunny")
            tag_season = sly.PointcloudTag(meta_season, value="Spring")
            tags_arr = [tag_weather, tag_season]

            # Create PointcloudTagCollection from Pointcloud Tags
            tags = sly.PointcloudTagCollection(tags_arr)

            # Add item to PointcloudTagCollection
            meta_potato = sly.TagMeta('potato', sly.TagValueType.NONE)
            tag_potato = sly.PointcloudTag(meta_potato)

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

            # You can also add multiple items to collection
            meta_cabbage = sly.TagMeta('cabbage', sly.TagValueType.NONE)
            meta_carrot = sly.TagMeta('carrot', sly.TagValueType.NONE)
            meta_turnip = sly.TagMeta('turnip', sly.TagValueType.NONE)

            tag_cabbage = sly.PointcloudTag(meta_cabbage)
            tag_carrot = sly.PointcloudTag(meta_carrot)
            tag_turnip = sly.PointcloudTag(meta_turnip)

            additional_veggies = [tag_cabbage, tag_carrot, tag_turnip]

            tags = tags.add_items(additional_veggies)

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

            # Intersection, finds intersection of given list of instances with collection items
            meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE)
            tag_dog = sly.PointcloudTag(meta_dog)

            meta_cat = sly.TagMeta('cat', sly.TagValueType.NONE)
            tag_cat = sly.PointcloudTag(meta_cat)

            meta_turtle = sly.TagMeta('turtle', sly.TagValueType.NONE)
            tag_turtle = sly.PointcloudTag(meta_turtle)

            tags_animals = sly.PointcloudTagCollection([tag_dog, tag_cat, tag_turtle])

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

            # Let's add the potato Tag from another collection and compare them again
            tags_animals = tags_animals.add(tag_potato)

            tags_intersections = tags.intersection(tags_animals)
            print(tags_intersections.to_json())
            # Output: [
            #     {
            #         "name":"potato",
            #         "key": "058ad7993a534082b4d94cc52542a97d"
            #     }
            # ]

            # Difference, finds difference between collection and given list of Tags or TagCollection
            meta_car = sly.TagMeta('car', sly.TagValueType.NONE)
            tag_car = sly.Tag(meta_car)

            meta_bicycle = sly.TagMeta('bicycle', sly.TagValueType.NONE)
            tag_bicycle = sly.PointcloudTag(meta_bicycle)

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

            meta_pedestrian = sly.TagMeta('pedestrian', sly.TagValueType.NONE)
            tag_pedestrian = sly.PointcloudTag(meta_pedestrian)

            meta_road = sly.TagMeta('road', sly.TagValueType.NONE)
            tag_road = sly.PointcloudTag(meta_road)

            difference = tags_vehicles.difference([tag_pedestrian, tag_road])
            print(difference.to_json())
            # Output: [
            #     {
            #         "name": "car",
            #         "key": "058ad7993a534082b4d94cc52542a97d"
            #     },
            #     {
            #         "name": "bicycle",
            #         "key": ""0c0033c5b4834d4cbabece4317295f07"
            #     }
            # ]

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

            merged_collections = tags_vehicles.merge(tags_merge)
            print(merged_collections.to_json())
            # Output: [
            #     {
            #         "name":"pedestrian"
            #     },
            #     {
            #         "name":"road"
            #     },
            #     {
            #         "name":"car"
            #     },
            #     {
            #         "name":"bicycle"
            #     }
            # ]
    """

    item_type = PointcloudTag

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

    # @classmethod
    # def from_api_response(
    #     cls,
    #     data: List[Dict],
    #     tag_meta_collection: TagMetaCollection,
    #     id_to_tagmeta: Optional[Dict[int, TagMeta]] = None
    # ) -> PointcloudTagCollection:
    #     return super().from_api_response(data, tag_meta_collection, id_to_tagmeta)

    # def get_by_name(self, tag_name: str) -> List[PointcloudTag]:
    #     """
    #     Get list of Pointcloud Tags with provided name.

    #     :param tag_name: Pointcloud Tag name.
    #     :type tag_name: str
    #     :returns: List of Pointcloud Tags.
    #     :rtype: List[:class:`~supervisely.pointcloud_annotation.pointcloud_tag.PointcloudTag`]
    #     """
    #     res = []
    #     for tag in self:
    #         if tag.name == tag_name:
    #             res.append(tag)
    #     return res

    # def get_single_by_name(self, tag_name: str) -> PointcloudTag:
    #     """
    #     Get Pointcloud Tag with provided name.
    #     Method will raise error If collection contains more than 1 tag with provided name.

    #     :param tag_name: Pointcloud Tag name.
    #     :type tag_name: str
    #     :returns: PointcloudTag object or None If no elements with provided name in collection.
    #     :rtype: :class:`~supervisely.pointcloud_annotation.pointcloud_tag.PointcloudTag` or :class:`NoneType`
    #     :raises ValueError: If collection contains more than 1 tag with provided name.
    #     """
    #     res = []
    #     for tag in self:
    #         if tag.name == tag_name:
    #             res.append(tag)
    #     if len(res) == 0:
    #         return None
    #     if len(res) > 1:
    #         raise ValueError(
    #             f"There are more than one tag {tag_name} in VideoTagCollection. Use method get_by_name instead"
    #         )
    #     return res[0]

    def to_json(self, key_id_map: Optional[KeyIdMap] = None) -> List[Dict]:
        """
        Convert the :class:`~supervisely.pointcloud_annotation.pointcloud_tag_collection.PointcloudTagCollection` to a list of json dicts.
        Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.

        :param key_id_map: Key ID map.
        :type key_id_map: :class:`~supervisely.video_annotation.key_id_map.KeyIdMap`
        :returns: List of dictionaries in json format.
        :rtype: List[Dict]

        :Usage Example:

            .. code-block:: python

                import supervisely as sly

                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)

                tag_weather = sly.PointcloudTag(meta_weather, value="Sunny")
                tag_season = sly.PointcloudTag(meta_season, value="Spring")

                tags_arr = [tag_weather, tag_season]
                tags = sly.PointcloudTagCollection(tags_arr)

                tags_json = tags.to_json()
                # Output: [
                #     {
                #         "name":"Weather",
                #         "value":"Sunny",
                #         "key": "058ad7993a534082b4d94cc52542a97d"
                #     },
                #     {
                #         "name":"Season",
                #         "value":"Spring",
                #         "key": "0c0033c5b4834d4cbabece4317295f07"
                #     }
                # ]
        """

        return [tag.to_json(key_id_map) for tag in self]

    @classmethod
    def from_json(
        cls,
        data: List[Dict],
        tag_meta_collection: TagMetaCollection,
        key_id_map: Optional[KeyIdMap] = None,
    ) -> PointcloudTagCollection:
        """
        Convert a list with dictionaries in json format to :class:`~supervisely.pointcloud_annotation.pointcloud_tag_collection.PointcloudTagCollection`.
        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: 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 tag collection.
        :rtype: :class:`~supervisely.pointcloud_annotation.pointcloud_tag_collection.PointcloudTagCollection`

        :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.PointcloudTagCollection.from_json(data, tag_metas)
        """

        tags = [
            cls.item_type.from_json(tag_json, tag_meta_collection, key_id_map) for tag_json in data
        ]
        return cls(tags)
