# Source code for tecplot.tecutil.util

    from builtins import int, str, super

    import ctypes, inspect, logging, os, textwrap, warnings

    try:
        from collections.abc import Iterable
    except ImportError:
        from collections import Iterable
    from collections import namedtuple
    from contextlib import contextmanager
    from ctypes import cast, c_int, POINTER
    from six import string_types

    from ..exception import *
    from ..constant import Color

    log = logging.getLogger(__name__)

    maxint64 = 2**(64 - 1) - 1
    minint64 = -maxint64 - 1
    maxuint64 = 2**64 - 1

    class Index(int):
        """Position identifier type.

        This type is used internally to represent a position in a list. It is
        used to indicate that a change between zero-based indexing and one-based
        indexing must occur at the TecUtil boundary.

        This type can be treated exactly like a Python native `int` and is only
        meaningful internally to the tecplot Python module.
        """

    XYPosition = namedtuple('XYPosition', ['x', 'y'])
    XYPosition.__new__.__defaults__ = (None, None, None)

    XYZPosition = namedtuple('XYZPosition', ['x', 'y', 'z'])
    XYZPosition.__new__.__defaults__ = (None, None, None)

    RectPosition = namedtuple('RectPosition', ['x1', 'y1', 'x2', 'y2'])

    RectSize = namedtuple('RectSize', ['width', 'height'])
    RectSize.__new__.__defaults__ = (None, None)

    IJKIndex = namedtuple('IJKIndex', ['i', 'j', 'k'])
    IJKIndex.__new__.__defaults__ = (None, None, None)

    Limits = namedtuple('Limits', ['min', 'max'])
    Limits.__new__.__defaults__ = (None, None)

    IndexRange = namedtuple('IndexRange', ['min', 'max', 'step'])
    """Index range specification along some axis.

    This is similar to Python's :class:`slice` object except that ``max`` is included
    in the evaluated indexes. Here are some things to note:

        * All indices start with 0 and go to some maximum index ``m``.
        * Negative values represent the indexes starting with the maximum at -1
          and continuing back to the beginning of the range.
        * A step of `None`, 0 and 1 are all equivalent and mean that no elements
          are skipped.
        * A negative step indicates a skip less than the maximum.
    """
    IndexRange.__new__.__defaults__ = (None, None, None)

    def flatten_args(*args):
        flatargs = []
        for a in args:
            if isinstance(a, Iterable) and not isinstance(a, string_types):
                flatargs += list(a)
            else:
                flatargs.append(a)
        return tuple(flatargs)

    def array_to_enums(array_pointer, array_size, enum_type):
        indexes = cast(array_pointer, POINTER(c_int))
        return tuple(enum_type(indexes[i]) for i in range(array_size))

    def inherited_property(cls):
        def _copy_property(prop):
            attr = getattr(cls, prop.__name__)
            return property(attr.fget, attr.fset, attr.fdel, prop.__doc__)
        return _copy_property

    def lock_attributes(cls):
        """
        As a decorator of a class, this ensures that no new attributes are created
        after __init__() is called.
        """
        if __debug__:
            def _setattr(self, name, value):
                if not name.startswith('_') and name not in dir(self):
                    stacknames = [f[0].f_code.co_name for f in inspect.stack()[:3]]
                    if '__init__' not in stacknames:
                        msg = 'No attribute: {}.{}'
                        classname = self.__class__.__name__
                        raise TecplotAttributeError(msg.format(classname, name))
                return super(cls, self).__setattr__(name, value)
            cls.__setattr__ = _setattr
        return cls

    _VarInfo = namedtuple('_VarInfo', ('types', 'values', 'names'))

    def check_arglist_argtypes(function_name, *args):
        for arg in args:
            vinfo = _VarInfo(*arg)
            for name, value in zip(vinfo.names, vinfo.values):
                if value is not None:
                    if not isinstance(value, tuple(vinfo.types)):
                        errfmt = '{}: Type of  parameter {} must be one of: {}'
                        types = ', '.join(t.__name__ for t in vinfo.types)
                        errmsg = errfmt.format(function_name, name, types)
                        raise TecplotTypeError(errmsg)

    def color_spec(color, plot=None):
        """
            color_spec(Color.Blue, plot)          --> Color.Blue
            color_spec(Color.MultiColor, plot)    --> plot.contour(0)
            color_spec(Color.MultiColor2, plot)   --> plot.contour(1)
            color_spec(Color.Blue)                --> Color.Blue
            color_spec(plot.contour(0))           --> Color.MultiColor
            color_spec(plot.contour(1))           --> Color.MultiColor2

            color_spec(plot.rgb_coloring)         --> Color.RGBColor
            color_spec(Color.RGBColor, plot)      --> plot.rgb_coloring
        """
        color_spec._indexes = {
            Color.RGBColor: Index(Color.RGBColor.value),
            Color.MultiColor: Index(0),
            Color.MultiColor2: Index(1),
            Color.MultiColor3: Index(2),
            Color.MultiColor4: Index(3),
            Color.MultiColor5: Index(4),
            Color.MultiColor6: Index(5),
            Color.MultiColor7: Index(6),
            Color.MultiColor8: Index(7)}
        color_spec._multicolors = {v: k for k, v in color_spec._indexes.items()}
        try:
            if plot:
                if color == Color.RGBColor:
                    return plot.rgb_coloring
                else:
                    return plot.contour(color_spec._indexes[Color(color)])
            else:
                return color_spec._multicolors[Index(color.index)]
        except (AttributeError, KeyError):
            return Color(color)

    def filled_slice(slice_, maxlen):
        """Convert start, stop, step in slice to real integers.

        None and negative values are converted to actual default values depending
        on the maxlen given.
        """
        if slice_.start is None:
            start = 0
        elif slice_.start < 0:
            start = maxlen + slice_.start
        else:
            start = slice_.start
        start = min(max(start, 0), maxlen)

        if slice_.stop is None:
            stop = maxlen
        elif slice_.stop < 0:
            stop = maxlen + slice_.stop
        else:
            stop = slice_.stop
        stop = min(max(stop, 0), maxlen)

        if slice_.step is None:
            step = 1
        else:
            step = min(max(slice_.step, 1), maxlen)

        return slice(start, stop, step)

    def array_to_str(arr, maxlen=10):
        try:
            itr = iter(arr)
            item = next(itr)
            ret = '[' + str(item)
            for i, item in enumerate(itr, start=2):
                if i > maxlen:
                    ret += ' ...'
                    break
                ret += ', {}'.format(item)
            return ret + ']'
        except StopIteration:
            return '[]'
        except TypeError:
            return str(arr)

    class ListWrapper(object):
        """Converts a list to a wrapped paragraph of items.

        Unline textwrap.TextWrapper, items in the list are not broken over
        multiple lines even if they contain spaces.
        """
        def __init__(self, initial_indent='', subsequent_indent='    ',
                     initial_width=70, subsequent_width=70, delim=',',
                     prefix='', suffix=''):
            assert (len(prefix) + len(initial_indent)) < initial_width
            assert (len(suffix) + len(subsequent_indent)) < subsequent_width
            self.initial_indent = initial_indent
            self.subsequent_indent = subsequent_indent
            self.initial_width = initial_width
            self.subsequent_width = subsequent_width
            self.delim = delim
            self.prefix = prefix
            self.suffix = suffix

        def wrap(self, str_list):
            ret = []
            max_space = self.subsequent_width - len(self.subsequent_indent)

            line = "{}{}".format(self.prefix, self.initial_indent)

            itr = iter(str_list)
            try:
                item = next(itr)
                line += "'{}'".format(item)
            except StopIteration:
                pass  # no items in list

            space_left = self.initial_width - len(line)

            for item in itr:
                s = "{} '{}'".format(self.delim, str(item))
                if len(s) < space_left or max_space <= space_left:
                    line += s
                    space_left -= len(s)
                else:
                    ret.append(line + self.delim)
                    line = "{}'{}'".format(self.subsequent_indent, item)
                    space_left = self.subsequent_width - len(line)

            if len(self.suffix) <= space_left:
                ret.append(line + self.suffix)
            else:
                ret.append(line)
                ret.append("{}{}".format(self.subsequent_indent, self.suffix))
            return ret

        def fill(self, str_list):
            return '\n'.join(self.wrap(str_list))

    @contextmanager
    def optional(cls, args):
        """Context for optional arguments that can be None.

        In this example, variables is an optional parameter. If not `None`, then
        ``varset`` will be the result of ``IndexSet(*variables)``, if `None`, then
        ``varset`` will be `None`::

            def fn(variables=None):
                with optional(IndexSet, variables) as varset:
                    _tecutil.Fn(varset)

        The ``cls`` parameter must be a class and must have the ``__enter__`` and
        ``__exit__`` methods implemented.
        """
        if args is None:
            yield None
        else:
            with cls(*flatten_args(args)) as obj:
                yield obj

    def normalize_path(nodepath):
        """Convert relative paths to absolute and normalize.

        Always use Python's current working directory to convert to absolute paths.
        However, relative paths are disallowed when connected to a non-local
        instance of the TecUtil Server.
        """
        if nodepath is not None:
            nodepath = str(nodepath)
            from tecplot.tecutil import _tecutil_connector
            if _tecutil_connector.connected:
                if _tecutil_connector.client.host not in ['localhost', '127.0.0.1']:
                    if not os.path.isabs(nodepath):
                        msg = 'paths must be absolute when connected to Tecplot 360'
                        raise TecplotLogicError(msg)
            return os.path.abspath(nodepath)

    def api_changed(msg, version_changed, sdk_version_changed, warning=False):
        msg = textwrap.dedent('''\
        PyTecplot API Change (version {})
        Tecplot 360 version: {}
        {}
        ''').format(version_changed, sdk_version_changed, textwrap.fill(msg))
        if warning:
            if __debug__:
                msg += 'Please update your code as the legacy interface will\n' \
                    +  'be removed in the next major release of PyTecplot.'
                warnings.warn(msg, TecplotDeprecationWarning)
        else:
            raise TecplotInterfaceChangeError(msg)

    def api_moved(old, new, version_changed, sdk_version_changed, warning=False):
        msg = textwrap.dedent('''\
        PyTecplot API Change (version {})
        Tecplot 360 version: {}
            {} has been moved to {}
        ''').format(version_changed, sdk_version_changed, old, new)
        if warning:
            if __debug__:
                msg += 'Please update your code as the legacy interface will\n' \
                    +  'be removed in the next major release of PyTecplot.'
                warnings.warn(msg, TecplotDeprecationWarning)
        else:
            raise TecplotInterfaceChangeError(msg)