# coding: utf-8
"""get list of :class:`objects<supervisely.annotation.obj_class.ObjClass>` from supervisely project"""

from __future__ import annotations

from typing import Dict, List, NamedTuple, Optional

from supervisely.api.module_api import ApiField, ModuleApi
from supervisely.video_annotation.key_id_map import KeyIdMap


class ObjectClassApi(ModuleApi):
    """
    API for working with :class:`ObjClass<supervisely.annotation.obj_class.ObjClass>`. :class:`ObjectClassApi<ObjectClassApi>` object is immutable.

    :param api: API connection to the server.
    :type api: Api
    :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()

        # Pass values into the API constructor (optional, not recommended)
        # api = sly.Api(server_address="https://app.supervise.ly", token="4r47N...xaTatb")

        project_id = 1951
        obj_class_infos = api.object_class.get_list(project_id)
    """

    @staticmethod
    def info_sequence():
        """
        NamedTuple ObjectClassInfo information about ObjectClass.

        :Example:

         .. code-block:: python

            ObjectClassInfo(id=22309,
                            name='lemon',
                            description='',
                            shape='bitmap',
                            color='#51C6AA',
                            settings={},
                            created_at='2021-03-02T10:04:33.973Z',
                            updated_at='2021-03-11T09:37:07.111Z')
        """
        return [
            ApiField.ID,
            ApiField.NAME,
            ApiField.DESCRIPTION,
            ApiField.SHAPE,
            ApiField.COLOR,
            ApiField.SETTINGS,
            ApiField.CREATED_AT,
            ApiField.UPDATED_AT,
        ]

    @staticmethod
    def info_tuple_name():
        """
        NamedTuple name - **ObjectClassInfo**.
        """
        return "ObjectClassInfo"

    def get_list(
        self, project_id: int, filters: Optional[List[Dict[str, str]]] = None
    ) -> List[NamedTuple]:
        """
        List of ObjClasses in the given Project.

        :param project_id: Project ID in which the ObjClasses are located.
        :type project_id: int
        :param filters: List of params to sort output ObjClasses.
        :type filters: List[dict], optional
        :return: List of ObjClasses with information from the given Project. See :class:`info_sequence<info_sequence>`
        :rtype: :class:`List[NamedTuple]`
        :Usage example:

         .. code-block:: python

            import supervisely as sly

            os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
            os.environ['API_TOKEN'] = 'Your Supervisely API Token'
            api = sly.Api.from_env()

            project_id = 1951
            obj_class_infos = api.object_class.get_list(project_id)
            print(obj_class_infos)
            # Output: [ObjectClassInfo(id=22309,
            #                          name='lemon',
            #                          description='',
            #                          shape='bitmap',
            #                          color='#51C6AA',
            #                          settings={},
            #                          created_at='2021-03-02T10:04:33.973Z',
            #                          updated_at='2021-03-11T09:37:07.111Z'),
            #  ObjectClassInfo(id=22310,
            #                  name='kiwi',
            #                  description='',
            #                  shape='bitmap',
            #                  color='#FF0000',
            #                  settings={},
            #                  created_at='2021-03-02T10:04:33.973Z',
            #                  updated_at='2021-03-11T09:37:07.111Z')
            # ]

            obj_class_list = api.object_class.get_list(1951, filters=[{'field': 'name', 'operator': '=', 'value': 'lemon' }])
            print(obj_class_list)
            # Output: [
            #     [
            #         22309,
            #         "lemon",
            #         "",
            #         "bitmap",
            #         "#51C6AA",
            #         {},
            #         "2021-03-02T10:04:33.973Z",
            #         "2021-03-11T09:37:07.111Z"
            #     ]
            # ]
        """
        return self.get_list_all_pages(
            "advanced.object_classes.list",
            {ApiField.PROJECT_ID: project_id, "filter": filters or []},
        )

    def get_name_to_id_map(self, project_id: int) -> Dict[str, int]:
        """
        :param project_id: Project ID in which the ObjClasses are located.
        :type project_id: int
        :return: Dictionary Key ID Map {'key': id}
        :rtype: :class:`KeyIdMap<supervisely.video_annotation.key_id_map.KeyIdMap>`
        :Usage example:

        .. code-block:: python

            import supervisely as sly

            os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
            os.environ['API_TOKEN'] = 'Your Supervisely API Token'
            api = sly.Api.from_env()

            obj_class_map = api.object_class.get_name_to_id_map(1951)
            print(obj_class_map)
            # Output: {'lemon': 22309, 'kiwi': 22310, 'cucumber': 22379}
        """
        objects_infos = self.get_list(project_id)
        return {object_info.name: object_info.id for object_info in objects_infos}

    def _get_info_by_id(self, id, method, fields=None):
        response = self._get_response_by_id(id, method, id_field=ApiField.ID, fields=fields)
        return (
            self._convert_json_info(response.json(), skip_missing=True)
            if (response is not None)
            else None
        )

    def get_info_by_id(self, id):
        return self._get_info_by_id(
            id,
            "advanced.object_classes.info",
        )

    # def _object_classes_to_json(self, object_classes: KeyIndexedCollection, objclasses_name_id_map=None, project_id=None):
    #     pass #@TODO: implement
    #     # if objclasses_name_id_map is None and project_id is None:
    #     #     raise RuntimeError("Impossible to get ids for projectTags")
    #     # if objclasses_name_id_map is None:
    #     #     objclasses_name_id_map = self.get_name_to_id_map(project_id)
    #     # tags_json = []
    #     # for tag in tags:
    #     #     tag_json = tag.to_json()
    #     #     tag_json[ApiField.TAG_ID] = tag_name_id_map[tag.name]
    #     #     tags_json.append(tag_json)
    #     # return tags_json
    #
    # def append_to_video(self, video_id, tags: KeyIndexedCollection, key_id_map: KeyIdMap = None):
    #     if len(tags) == 0:
    #         return []
    #     video_info = self._api.video.get_info_by_id(video_id)
    #     tags_json = self._tags_to_json(tags, project_id=video_info.project_id)
    #     ids = self.append_to_video_json(video_id, tags_json)
    #     KeyIdMap.add_tags_to(key_id_map, [tag.key() for tag in tags], ids)
    #     return ids
    #
    # def append_to_video_json(self, video_id, tags_json):
    #     if len(tags_json) == 0:
    #         return
    #     response = self._api.post('videos.tags.bulk.add', {ApiField.VIDEO_ID: video_id, ApiField.TAGS: tags_json})
    #     ids = [obj[ApiField.ID] for obj in response.json()]
    #     return ids

    def update(
        self,
        id: int,
        name: Optional[str] = None,
        description: Optional[str] = None,
        hotkey: Optional[str] = None,
        shape: Optional[str] = None,
        color: Optional[str] = None,
        settings: Optional[dict] = None,
    ) -> NamedTuple:
        """
        Update the class with the given ID on the server.
        Returned object contains updated information about the class except settings.

        :param id: ID of the class to update.
        :type id: int
        :param name: New name of the class.
        :type name: str, optional
        :param description: New description of the class.
        :type description: str, optional
        :param hotkey: New hotkey of the class (e.g., "K").
        :type hotkey: str, optional
        :param shape: New shape of the class.
        :type shape: str, optional
        :param color: New color of the class in HEX format (e.g., #FFFFFF).
        :type color: str, optional
        :param settings: New settings of the class.
                        Do not pass "availableShapes" for shape other than "any".
                        Do not pass "availableShapes" that does not contain the current shape.
        :type settings: dict, optional

        :return: Updated class information
        :rtype: :class:`ObjectClassInfo<ObjectClassInfo>`

        :Usage example:

        .. code-block:: python

            import supervisely as sly

            api = sly.Api.from_env()

            obj_class_info = api.object_class.update(
                id=22309,
                shape='any',
                settings={
                    "availableShapes": [
                        "bitmap",
                        "polygon",
                    ],
                },
            )
        """
        if all(arg is None for arg in [name, description, hotkey, shape, color, settings]):
            raise ValueError(
                f"To update the class with ID: {id}, you must specify at least one parameter to update; all are currently None"
            )

        data = {ApiField.ID: id}
        if name is not None:
            data[ApiField.NAME] = name
        if description is not None:
            data[ApiField.DESCRIPTION] = description
        if hotkey is not None:
            data[ApiField.HOTKEY] = hotkey
        if shape is not None:
            data[ApiField.SHAPE] = shape
        if color is not None:
            data[ApiField.COLOR] = color
        if settings is not None:
            data[ApiField.SETTINGS] = settings

        response = self._api.post("advanced.object_classes.editInfo", data)
        return self._convert_json_info(response.json(), skip_missing=True)
