# Source code for tecplot.session.aux_data

    from builtins import int, str, super

    import ctypes
    import itertools as it

    from collections import namedtuple
    from contextlib import contextmanager
    from enum import Enum
    from six import string_types

    from ..tecutil import _tecutil
    from ..constant import *
    from ..exception import *
    from ..tecutil import lock, lock_attributes

    @lock_attributes
    class AuxData(ctypes.c_void_p):
        """Auxiliary data.

        The |Tecplot Engine| can hold auxiliary data attached to one of the
        following objects:

            - `Layout <layout.aux_data>`
            - `Page <Page.aux_data>`
            - `Frame <Frame.aux_data>`
            - `Dataset <Dataset.aux_data>`
            - `Variable <Variable.aux_data>`
            - `Zone <OrderedZone.aux_data>`
            - `Linemap <XYLinemap.aux_data>`

        Auxiliary data is an ordered key-value pair that behaves like an ordered
        dictionary, or `OrderedDict <collections.OrderedDict>`. Keys are strings
        which are ordered alphabetically and values can additionally be access by
        index. The keys must be alphanumeric (special characters "." and "_" are
        allowed), must not contain spaces, and must begin with a non-numeric
        character or underscore. Values, on the other hand, are arbitrary strings
        and can contain anything except the null character. In this example, we
        query the auxiliary data attached to the dataset and add some information
        to it. Notice that the stored order is alphabetical::

            >>> import tecplot as tp
            >>> aux = tp.active_frame().aux_data
            >>> aux['info'] = 'Here is some information.'
            >>> aux['Xavg'] = 3.14159
            >>> aux['note'] = 'Aux data values are always converted to strings.'
            >>> for k, v in aux.items():
            ...     print('{}: {}'.format(k,v))
            info: Here is some information.
            note: Aux data values are always converted to strings.
            Xavg: 3.14159
        """
        def __init__(self, parent, object_type, object_index=None):
            self.parent = parent
            self.object_type = object_type
            self.getref_args = [] if object_index is None else [object_index + 1]
            super().__init__(self._native_reference)

        @property
        def _native_reference(self):
            _dispatch = {
                AuxDataObjectType.Dataset: _tecutil.AuxDataDataSetGetRef,
                AuxDataObjectType.Frame: _tecutil.AuxDataFrameGetRef,
                AuxDataObjectType.Layout: _tecutil.AuxDataLayoutGetRef,
                AuxDataObjectType.Linemap: _tecutil.AuxDataLineMapGetRef,
                AuxDataObjectType.Page: _tecutil.AuxDataPageGetRef,
                AuxDataObjectType.Variable: _tecutil.AuxDataVarGetRef,
                AuxDataObjectType.Zone: _tecutil.AuxDataZoneGetRef}
            with self._activated_parent():
                return _dispatch[self.object_type](*self.getref_args)

        @contextmanager
        def _activated_parent(self):
            if self.parent:
                with self.parent.activated():
                    yield
            else:
                yield

        @contextmanager
        def assignment(self):
            _tecutil.AuxDataBeginAssign()
            try:
                yield
            finally:
                _tecutil.AuxDataEndAssign()

        def index(self, key):
            """Returns the zero-based index of the element based on key.

            Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> print(frame.aux_data.index('other_info'))
                0
                >>> print(frame.aux_data.index('result'))
                1
            """
            try:
                success, index = _tecutil.AuxDataGetItemIndex(self, key)
                index -= 1
                if not success:
                    raise TecplotKeyError
            except TecplotSystemError:
                raise TecplotKeyError
            return index

        _Item = namedtuple('Item', ['key', 'value', 'dtype', 'retain'])

        def _item(self, index):
            res =_tecutil.AuxDataGetItemByIndex(self, index + 1)
            key, value, dtype, retain = res
            if dtype is AuxDataType.String:
                value = ctypes.cast(value, ctypes.c_char_p).value.decode('utf-8')
            else:
                # if we ever add another type to AuxDataType, this would
                # need to be expanded as well as setitem which currently
                # just converts every input to a string.
                raise TecplotNotImplementedError
            return AuxData._Item(key, value, dtype, retain)

        def key(self, index):
            """Returns the key at a specific zero-based index.

            Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> print(frame.aux_data.key(0))
                other_info
                >>> print(frame.aux_data.key(1))
                result
            """
            return self._item(index).key

        def __getitem__(self, key):
            if isinstance(key, string_types):
                key = self.index(key)
            return self._item(key).value

        @lock()
        def __setitem__(self, key, value):
            if isinstance(key, int):
                try:
                    key = self.key(key)
                except:
                    raise TecplotIndexError
            if not _tecutil.AuxDataSetItem(self, key, str(value),
                                           AuxDataType.String.value, True):
                raise TecplotSystemError()

        @lock()
        def __delitem__(self, key):
            if isinstance(key, string_types):
                key = self.index(key)
            _tecutil.AuxDataDeleteItemByIndex(self, key + 1)

        def __len__(self):
            return _tecutil.AuxDataGetNumItems(self)

        def __iter__(self):
            self._current_index = -1
            self._current_length = len(self)
            return self

        def __next__(self):
            self._current_index += 1
            if self._current_index < self._current_length:
                return self.key(self._current_index)
            else:
                raise StopIteration

        def next(self):
            return self.__next__()

        def items(self):
            """Yields all key/value pairs of the Aux Data attached to the parent.

            Elements are always ordered alphabetically by the keys. Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> for key, value in frame.aux_data.items():
                ...     print(key, value)
                other_info 128
                result 3.1415
            """
            for i in range(len(self)):
                item = self._item(i)
                yield item.key, item.value

        def keys(self):
            """Yields all keys of the Aux Data attached to the parent.

            Elements are always ordered alphabetically by the keys. Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> for value in frame.aux_data.keys():
                ...     print(value)
                other_info
                result
            """
            for key in self:
                yield key

        def values(self):
            """Yields all values of the Aux Data attached to the parent.

            Elements are always ordered alphabetically by the keys. Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> for value in frame.aux_data.values():
                ...     print(value)
                128
                3.1415
            """
            for _, value in self.items():
                yield value

        def as_dict(self):
            """Returns a Python dict of the Aux Data attached to the parent.

            Note that this will remove the alphabetical ordering guarantee that Aux
            Data has since Python dict objects are unordered. Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data['result'] = '3.1415'
                >>> frame.aux_data['other_info'] = '128'
                >>> aux = frame.aux_data.as_dict()
                >>> print(aux)
                {'result': '3.1415', 'other_info': '128'}
            """
            return {k: v for k, v in self.items()}

        def __str__(self):
            return str(self.as_dict())

        def update(self, *other, **kwargs):
            """Update Aux Data with key/value pairs from another Aux Data or dict.

            Example usage::

                >>> frame = tp.active_frame()
                >>> frame.aux_data.update({'result': '3.1415', 'other_info': '128'})
                >>> print(frame.aux_data)
                {'result': '3.1415', 'other_info': '128'}
            """
            with self.assignment():
                for d in it.chain(other, [kwargs]):
                    for k, v in d.items():
                        self[k] = v

        def clear(self):
            """Deletes all Aux Data from the associated object.

            Example usage::

                >>> print(frame.aux_data)
                {'bb': 'test bb', 'cc': 'test cc', 'aa': 'test aa'}
                >>> frame.aux_data.clear()
                >>> print(frame.aux_data)
                {}
            """
            for key in list(self.keys()):
                del self[key]