"""Item module.

Module to create ``Item`` instances.

Any object from an Ansys Dynamic Reporting database can be represented
as an ``Item`` instance. This class allows for easy creation and manipulation
of such objects.

Examples
--------
::

    import ansys.dynamicreporting.core as adr
    adr_service = Service()
    ret = adr_service.connect()
    my_img = adr_service.create_item()
    my_img.item_image = 'Image_to_push_on_report'

"""
import os.path
import requests
import sys
from typing import Optional

from .adr_utils import in_ipynb, table_attr, type_maps
import webbrowser

try:
    from IPython.display import IFrame
except ImportError:
    pass


# Generate the items for the ADR database
class Item:

    """Provides for creating an object that represents an Ansys Dynamic Reporting item.

    Create an instance of this class for each item in the database that you want to
    interact with. When the object is created, no type is set. The type, determined the
    first time that you set the ``item_*`` attribute, cannot be changed.

    This code creates an instance with the object ``my_txt`` as a text item:

    >>> my_txt = adr_service.create_item()
    >>> my_txt.item_text = '<h1>The test</h1>This is a text item'


    The type of the item created in the preceding code cannot be changed. However,
    the attributes describing the object can be reset at any time. These hanges are
    automatically propagated into the database. The attributes described in the
    following "Parameters" section can be used to control the rendering of these objects.

    .. note::
       These attributes mirror the generic data item attributes described in
       `Data Items`_ in the documentation for Ansys Dynamic Reporting.

    .. _Data Items: https://nexusdemo.ensight.com/docs/html/Nexus.html?DataItems.html

    Parameters
    ----------
    service : ansys.dynamicreporting.core.Service, optional
        Ansys Dynamic Reporting object that provides the connection to the database
        that the item is to interact with. The default is ``None``.
    obj_name : str, optional
        Name of the item object in the database. The default is ``default``.
    source : str, optional
        Name of the source for the item in the database. The default is ``"ADR"``.


    Examples
    --------
    Initialize the ``Service`` class inside an Ansys Dynamic Reporting service and
    create an object as a text item::

        import ansys.dynamicreporting.core as adr
        adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
        adr_service.connect(url='http://localhost:8010')
        my_txt = adr_service.create_item()
        my_txt.item_text = '<h1>The test</h1>This is a text item'

    """

    def __init__(self, service: 'ADR' = None, obj_name: Optional[str] = "default",
                 source: Optional[str] = "ADR") -> None:
        self.item = None
        self.serverobj = service.serverobj
        self._url = None
        self.logger = service.logger
        self.source = source
        self.obj_name = obj_name
        self.type = None
        self.item_text = ""
        """Text (HTML and LaTeX formatting)"""
        self.item_image = None
        """Image object (Image and PNG binary files)"""
        self.item_scene = None
        """3D scene (AVZ, PLY, GBT, and STL files)"""
        # Attributes for the table items
        self.table_attr = table_attr
        self.item_table = None
        """Table values (Must be in a numpy array)"""
        self.table_dict = {}
        self.item = self.serverobj.create_item(name=self.obj_name, source=self.source)

    @property
    def url(self):
        """URL corresponding to the item"""
        if self.serverobj.get_URL() is not None and self.item.guid is not None:
            self._url = self.serverobj.get_URL() + "/reports/report_display/?usemenus=off&query=A%7Ci_guid%7C%eq%7C"
            self._url += str(self.item.guid)
        else:
            self._url = None
        return self._url

    def __pushonly__(self):
        """
        Push self to the server - with server existence check
        """
        ret = 0
        if self._url is None:
            _ = self.url
        if self.serverobj is not None:
            ret = self.serverobj.put_objects([self.item])
        else:
            self.logger.error("No connection to service established")
        return ret

    def __push__(self, value):
        if self.type == "text":
            self.item.set_payload_html(value)
        elif self.type == "image":
            # If the image is passed as a file, first open it. Otherwise, directly
            # pass it as a payload value
            if os.path.exists(value):
                with open(value, "rb") as fb:
                    img = fb.read()
            else:
                img = value
            self.item.set_payload_image(img)
        elif self.type == "scene":
            self.item.set_payload_scene(value)
        elif self.type == "table":
            self.item.set_payload_table(self.table_dict)
        elif self.type == "animation":
            self.item.set_payload_animation(value)
        elif self.type == "file":
            self.item.set_payload_file(value)
        elif self.type == "tree":
            self.item.set_payload_tree(value)
        _ = self.__pushonly__()

    def __setattr__(self, name, value, only_set=False):
        # If only_set is set to True, then skip the push methods. This is needed when using the
        # setattr to create Item objs that correspond to what is in the database, but
        # not to actually push changes to the database items - for example, when querying it
        if name == "item":
            super().__setattr__(name, value)
            return 0
        if self.item is None:
            super().__setattr__(name, value)
            return 0
        if name in type_maps:
            if self.type is None:
                self.type = type_maps[name]
            if self.type != type_maps[name]:
                self.logger.error(f"Can not set {name} on an item of type: {self.type}")
                return -1
            if name == "item_table":
                self.table_dict["array"] = value
            if only_set is False:
                self.__push__(value)
        if name in self.table_attr:
            if self.type == "table":
                if value is not None:
                    self.table_dict[name] = value
                    if "array" in self.table_dict.keys():
                        if only_set is False:
                            self.__push__(value)
        super().__setattr__(name, value)
        return 0

    def __copyattrs__(self, dataitem=None):
        """
        Copy the attributes from a data Item into the current Item
        This is useful in the query method when creating a Item that corresponds to an existing
        DataItem

        Parameters
        ----------
        dataitem : utils.report_objects.ItemREST
            ADR item to copy from. Default: None
        """
        if dataitem is None:
            return
        if dataitem.type == "table":
            for t_attr in self.table_attr:
                self.__setattr__(t_attr, dataitem.payloaddata.get(t_attr, None), only_set=True)

    def visualize(self, new_tab: Optional[bool] = False) -> None:
        """Render this item only.

        Parameters
        ----------
        new_tab : bool, optional
            Whether to render the item in a new tab if the current environment is a Jupyter
            notebook. The default is ``False``, in which case the item is rendered in the
            current location. If the environment is not a Jupyter notebook, the item is
            always rendered in a new tab.

        Returns
        -------
        Item
            Rendered item.

        Examples
        --------
        Create a text item and render it in a new tab::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect(url='http://localhost:8010')
            my_txt = adr_service.create_item()
            my_txt.item_text = '<h1>The test</h1>This is a text item'
            my_txt.visualize(new_tab = True)


        """
        if in_ipynb() and not new_tab:
            iframe = self.get_iframe()
            if iframe is not None:
                display(iframe)
            else: # pragma: no cover
                self.logger.error("Could not generate an IFrame")
        else:
            if self._url is None: # pragma: no cover
                self.logger.error("Could not obtain a url")
            else:
                webbrowser.open_new(self._url)

    def get_iframe(self, width=0, height=0):
        """Get the iframe object corresponding to the item.

        Parameters
        ----------
        width : int, optional
            Width of the iframe object. The default is ``min(Item width * 1,1, 1000)``.
            For example, if the item width is ``0``, the default is ``1000``.
        height : int, optional
            Height of the iframe object. The default is ``min(Item height, fixed height)``,
            where the fixed height is ``800`` for an item scene and ``400`` otherwise.

        Returns
        -------
        iframe
            iframe object corresponding to the item. If no iframe can be generated,
            ``None`` is returned.

        Examples
        --------
        ::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect(url='http://localhost:8010')
            my_txt = adr_service.create_item()
            my_txt.item_text = '<h1>The test</h1>This is a text item'
            item_iframe = my_txt.get_iframe()

        """
        if 'IPython.display' in sys.modules:
            if width == 0:
                if self.item.width == 0:
                    width = 1000
                else:
                    width = min(self.item.width * 1.1, 1000)
            if height == 0:
                if self.type == "scene":
                    height = 800
                else:
                    height = 400
                if self.item.height > 0:
                    height = min(self.item.height * 1.1, height)
            iframe = IFrame(src=self._url, width=width, height=height)
        else:
            iframe = None
        return iframe

    def set_tags(self, tagstring: str = '') -> bool:
        """Set tags on the item.

        Parameters
        ----------
        tagstring : str, optional
            Tags to set on the item. Separate multiple tags with a space. The
            tag syntax is ``tagname=value``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        Examples
        --------
        ::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect()
            my_txt = adr_service.create_item()
            my_txt.item_text = '<h1>The test</h1>This is a text item'
            my_txt.set_tags("tagone=1 tagtwo=two")

        """
        self.item.set_tags(tagstring)
        ret = self.__pushonly__()
        return ret == requests.codes.ok

    def get_tags(self) -> str:
        """Get the tags on the item.

        Returns
        -------
        str
            Tags on the item.

        Examples
        --------
        ::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect()
            item_list = adr_service.query()
            first_item = item_list[0]
            all_tags = first_item.get_tags()

        """
        tags = self.item.get_tags()
        return tags

    def add_tag(self, tag: str = '', value: str = '') -> bool:
        """Add a tag to the item.

        Parameters
        ----------
        tag : str, optional
            Tag name. The default is ``""``.
        value str : str, optional
            Tag value.  The default is ``""``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        Examples
        --------
        ::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect()
            my_txt = adr_service.create_item()
            my_txt.item_text = '<h1>The test</h1>This is a text item'
            my_txt.add_tag(tag='tagone', value='one')

        """
        self.item.add_tag(tag=tag, value=value)
        ret = self.__pushonly__()
        return ret == requests.codes.ok

    def rem_tag(self, tag: str = '') -> bool:
        """Remove a tag on the item.

        Parameters
        ----------
        tag : str, optional
            Tag to remove. The default is ``""``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        Examples
        --------
        ::

            import ansys.dynamicreporting.core as adr
            adr_service = adr.Service(ansys_installation = r'C:\\Program Files\\ANSYS Inc\\v232')
            ret = adr_service.connect()
            item_list = adr_service.query()
            first_item = item_list[0]
            all_tags = first_item.rem_tags(tag='tagone')

        """
        self.item.rem_tag(tag=tag)
        ret = self.__pushonly__()
        return ret == requests.codes.ok
