'''
salabim  discrete event simulation

see www.salabim.org for more information, the manual, updates and license information.
'''
from __future__ import print_function  # compatibility with Python 2.x
from __future__ import division  # compatibility with Python 2.x

__version__ = '2.2.17'

import heapq
import random
import time
import math
import array
import collections
import glob
import os
import inspect
import platform
import sys
import itertools
import string
import io
import pickle
import logging

Pythonista = (sys.platform == 'ios')
Windows = (sys.platform.startswith('win'))


class SalabimError(Exception):
    pass


class g():
    pass


if Pythonista:
    import scene
    import ui
    import objc_util

inf = float('inf')
nan = float('nan')
omitted = ['omitted']


class ItemFile(object):
    '''
    define an item file to be used with read_item, read_item_int, read_item_float and read_item_bool

    Parameters
    ----------
    filename : str
        file to be used for subsequent read_item, read_item_int, read_item_float and read_item_bool calls |n|
        or |n|
        content to be interpreted used in subsequent read_item calls. The content should have at least one linefeed
        character and will be usually  triple quoted.

    Note
    ----
    It is advised to use ItemFile with a context manager, like ::

        with sim.ItemFile('experiment0.txt') as f:
            run_length = f.read_item_float() |n|
            run_name = f.read_item() |n|

    Alternatively, the file can be opened and closed explicitely, like ::

        f = sim.ItemFile('experiment0.txt')
        run_length = f.read_item_float()
        run_name = f.read_item()
        f.close()

    Item files consists of individual items separated by whitespace |n|
    If a blank is required in an item, use single or double quotes |n|
    All text following # on a line is ignored |n|
    All texts on a line within curly brackets {} is ignored and considered white space.

    Example ::

        Item1
        'Item 2'
            Item3 Item4 # comment
        Item5 {five} Item6 {six}
        'Double quote" in item'
        "Single quote' in item"
        True
    '''

    def __init__(self, filename):
        self.iter = self._nextread()
        if '\n' in filename:
            self.open_file = io.StringIO(filename)
        else:
            self.open_file = open(filename, 'r')

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.open_file.close()

    def close(self):
        self.open_file.close()

    def read_item_int(self):
        '''
        read next field from the ItemFile as int.

        if the end of file is reached, EOFError is raised
        '''
        return int(self.read_item().replace(',', '.'))

    def read_item_float(self):
        '''
        read next item from the ItemFile as float

        if the end of file is reached, EOFError is raised
        '''

        return float(self.read_item().replace(',', '.'))

    def read_item_bool(self):
        '''
        read next item from the ItemFile as bool

        A value of False (not case sensitive) will return False |n|
        A value of 0 will return False |n|
        The null string will return False |n|
        Any other value will return True

        if the end of file is reached, EOFError is raised
        '''
        result = self.read_item().strip().lower()
        if result == 'false':
            return False
        try:
            if float(result) == 0:
                return False
        except:
            pass
        if result == '':
            return False
        return True

    def read_item(self):
        '''
        read next item from the ItemFile

        if the end of file is reached, EOFError is raised
        '''
        try:
            return next(self.iter)
        except StopIteration:
            raise EOFError

    def _nextread(self):
        remove = string.whitespace.replace(' ', '')
        quotes = '\'"'

        for line in self.open_file:
            mode = '.'
            result = ''
            for c in line:
                if c not in remove:
                    if mode in quotes:
                        if c == mode:
                            mode = '.'
                            yield result  # even return the null string
                            result = ''
                        else:
                            result += c
                    elif mode == '{':
                        if c == '}':
                            mode = '.'
                    else:
                        if c == '#':
                            break
                        if c in quotes:
                            if result:
                                yield result
                            result = ''
                            mode = c
                        elif c == '{':
                            if result:
                                yield result
                            result = ''
                            mode = c

                        elif c == ' ':
                            if result:
                                yield result
                            result = ''
                        else:
                            result += c
            if result:
                yield result


class Monitor(object):
    '''
    Monitor object

    Parameters
    ----------
    name : str
        name of the monitor |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    monitor : bool
        if True (default), monitoring will be on. |n|
        if False, monitoring is disabled |n|
        it is possible to control monitoring later,
        with the monitor method

    type : str
        specifies how tallied values are to be stored
            - 'any' (default) stores values in a list. This allows
               non numeric values. In calculations the values are
               forced to a numeric value (0 if not possible)
            - 'bool' (True, False) Actually integer >= 0 <= 255 1 byte
            - 'int8' integer >= -128 <= 127 1 byte
            - 'uint8' integer >= 0 <= 255 1 byte
            - 'int16' integer >= -32768 <= 32767 2 bytes
            - 'uint16' integer >= 0 <= 65535 2 bytes
            - 'int32' integer >= -2147483648<= 2147483647 4 bytes
            - 'uint32' integer >= 0 <= 4294967295 4 bytes
            - 'int64' integer >= -9223372036854775808 <= 9223372036854775807 8 bytes
            - 'uint64' integer >= 0 <= 18446744073709551615 8 bytes
            - 'float' float 8 bytes

    env : Environment
        environment where the monitor is defined |n|
        if omitted, default_env will be used
    '''

    cached_x = [(0, 0), (0, 0)]  # index=ex0, value=[hash,x]

    def __init__(self, name=omitted, monitor=True, type='any', env=omitted, *args, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        _set_name(name, self.env._nameserializeMonitor, self)
        try:
            self.xtypecode, _ = type_to_typecode_off(type)
        except KeyError:
            raise SalabimError('type (' + type + ') not recognized')
        self._timestamp = False
        self.reset(monitor)
        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of a monitor.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments are passed
        '''
        pass

    def reset(self, monitor=omitted):
        '''
        resets monitor

        Parameters
        ----------
        monitor : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled
            if omitted, no change of monitoring state
        '''

        if monitor is omitted:
            monitor = self._monitor
        if self.xtypecode:
            self._x = array.array(self.xtypecode)
        else:
            self._x = []
        self.monitor(monitor)

    def monitor(self, value=omitted):
        '''
        enables/disabled monitor

        Parameters
        ----------
        value : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, no change

        Returns
        -------
        True, if monitoring enabled. False, if not : bool
        '''
        if value is not omitted:
            self._monitor = value
        return self.monitor

    def tally(self, x):
        '''
        Parameters
        ----------
        x : any, preferably int, float or translatable into int or float
            value to be tallied
        '''
        if self._monitor:
            self._x.append(x)

    def name(self):
        '''
        Returns
        -------
        Name of the monitor : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the monitor (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the monitor : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def mean(self, ex0=False):
        '''
        mean of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        mean : float
        '''

        x = self.x(ex0=ex0)
        if x:
            return sum(x) / len(x)
        else:
            return nan

    def std(self, ex0=False):
        '''
        standard deviation of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        standard deviation : float
        '''
        x = self.x(ex0=ex0)
        if x:
            wmean = self.mean(ex0)
            wvar = sum(((vx - wmean)**2) for vx in x) / len(x)
            return math.sqrt(wvar)
        else:
            return nan

    def minimum(self, ex0=False):
        '''
        minimum of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        minimum : float
        '''
        x = self.x(ex0=ex0)
        if x:
            return min(x)
        else:
            return nan

    def maximum(self, ex0=False):
        '''
        maximum of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        maximum : float
        '''

        x = self.x(ex0=ex0)
        if x:
            return max(x)
        else:
            return nan

    def median(self, ex0=False):
        '''
        median of tallied values weighted wioth their durations

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        median : float
        '''
        return self.percentile(50, ex0=ex0)

    def percentile(self, q, ex0=False):
        '''
        q-th percentile of tallied values

        Parameters
        ----------
        q : float
            percentage of the distribution |n|
            values <0 are treated a 0 |n|
            values >100 are treated as 100

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        : float
            q-th percentile |n|
            0 returns the minimum, 50 the median and 100 the maximum
        '''

        q = max(0, min(q, 100))
        if self._timestamp:
            x, weights = self.xduration(ex0=ex0)
        else:
            x = self.x(ex0=ex0)
            weights = [1] * len(x)
        if len(x) == 1:
            return x[0]
        sumweights = sum(weights)
        if not sumweights:
            return nan

        xweights = sorted(zip(x, weights), key=lambda v: v[0])
        x_sorted, weights_sorted = zip(*xweights)
        weights_sorted = [0] + list(weights_sorted)
        threshold = sumweights * q / 100
        vcum_imin1 = 0
        for i, v in enumerate(weights_sorted):
            vcum_i = vcum_imin1 + v
            if vcum_i > threshold:
                break
            vcum_imin1 = vcum_i
        return interpolate(threshold, vcum_imin1, vcum_i, x_sorted[i - 1], x_sorted[min(i, len(x_sorted) - 1)])

    def bin_count(self, lowerbound, upperbound, ex0=False):
        '''
        count of the number of tallied values in range (lowerbound,upperbound]

        Parameters
        ----------
        lowerbound : float
            non inclusive lowerbound

        upperbound : float
            inclusive upperbound

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        number of values >lowerbound and <=upperbound : int
        '''
        x = self.x(ex0=ex0)
        return sum(1 for vx in x if (vx > lowerbound) and (vx <= upperbound))

    def number_of_entries(self, ex0=False):
        '''
        count of the number of entries

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        number of entries : int
        '''
        return len(self.x(ex0=ex0))

    def number_of_entries_zero(self):
        '''
        count of the number of zero entries

        Returns
        -------
        number of zero entries : int
        '''
        return self.number_of_entries() - self.number_of_entries(ex0=True)

    def print_statistics(self, show_header=True, show_legend=True, do_indent=False):
        '''
        print monitor statistics

        Parameters
        ----------
        show_header: bool
            primarily for internal use

        show_legend: bool
            primarily for internal use

        do_indent: bool
            primarily for internal use
        '''
        if do_indent:
            l = 45
        else:
            l = 0
        indent = pad('', l)

        if show_header:
            print(indent + 'Statistics of {} at {}'.format(self.name(), fn(self.env._now - self.env._offset, 13, 3)))

        if show_legend:
            print(
                indent + '                        all    excl.zero         zero')
            print(
                pad('-' * (l - 1) + ' ', l) + '-------------- ------------ ------------ ------------')

        if self._timestamp:
            if self.duration() == 0:
                print(pad(self.name(), l) + 'no data')
                return
            else:
                print(pad(self.name(), l) + 'duration      {}{}{}'.
                    format(fn(self.duration(), 13, 3),
                    fn(self.duration(ex0=True), 13, 3), fn(self.duration_zero(), 13, 3)))
        else:
            if self.number_of_entries() == 0:
                print(pad(self.name(), l) + 'no entries')
                return
            else:
                print(pad(self.name(), l) + 'entries       {}{}{}'.
                    format(fn(self.number_of_entries(), 13, 3),
                    fn(self.number_of_entries(ex0=True), 13, 3), fn(self.number_of_entries_zero(), 13, 3)))

        print(indent + 'mean          {}{}'.
              format(fn(self.mean(), 13, 3), fn(self.mean(ex0=True), 13, 3)))
        print(indent + 'std.deviation {}{}'.
              format(fn(self.std(), 13, 3), fn(self.std(ex0=True), 13, 3)))
        print()
        print(indent + 'minimum       {}{}'.
              format(fn(self.minimum(), 13, 3), fn(self.minimum(ex0=True), 13, 3)))
        print(indent + 'median        {}{}'.
              format(fn(self.percentile(50), 13, 3), fn(self.percentile(50, ex0=True), 13, 3)))
        print(indent + '90% percentile{}{}'.
              format(fn(self.percentile(90), 13, 3), fn(self.percentile(90, ex0=True), 13, 3)))
        print(indent + '95% percentile{}{}'.
              format(fn(self.percentile(95), 13, 3), fn(self.percentile(95, ex0=True), 13, 3)))
        print(indent + 'maximum       {}{}'.
              format(fn(self.maximum(), 13, 3), fn(self.maximum(ex0=True), 13, 3)))

    def print_histogram(self, number_of_bins=30, lowerbound=0, bin_width=1, ex0=False):
        '''
        print monitor statistics and histogram

        Parameters
        ----------
        number_of_bins : int
            number of bins |n|
            if 0, also the header of the histogram will be surpressed

        lowerbound: float
            first bin

        bin_width : float
            width of the bins

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes
        '''
        if self._timestamp:
            x, weights = self.xduration(ex0=ex0)
            weight_total = sum(weights)
        else:
            x = self.x(ex0=ex0)
            weight_total = len(x)

        print('Histogram of', self.name())
        if weight_total == 0:
            print()
            if self._timestamp:
                print('no data')
            else:
                print('no entries')
            return

        self.print_statistics(show_header=False, show_legend=True, do_indent=False)
        if number_of_bins > 0:
            print()
            if self._timestamp:
                print('           <=      duration     %  cum%')
            else:
                print('           <=       entries     %  cum%')

            cumperc = 0
            for i in range(-1, number_of_bins + 1):
                if i == -1:
                    lb = -inf
                else:
                    lb = lowerbound + i * bin_width
                if i == number_of_bins:
                    ub = inf
                else:
                    ub = lowerbound + (i + 1) * bin_width
                count = self.bin_count(lb, ub)
                if weight_total == 0:
                    perc = nan
                    cumperc = nan
                    s = '|'
                else:
                    perc = count / weight_total
                    cumperc += perc
                    scale = 80
                    n = int(perc * scale)
                    ncum = int(cumperc * scale) + 1
                    s = ('*' * n) + (' ' * (scale - n))
                    s = s[:ncum - 1] + '|' + s[ncum + 1:]

                print('{} {}{}{} {}'.
                      format(fn(ub, 13, 3), fn(count, 13, 3), fn(perc * 100, 6, 1), fn(cumperc * 100, 6, 1), s))

    def x(self, ex0=False, force_numeric=True):
        '''
        array/list of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        convert_to_numeric : bool
            if True (default), convert non numeric tallied values numeric if possible, otherwise assume 0 |n|
            if False, do not interpret x-values, return as list if type is list

        Returns
        -------
        all tallied values : array/list
        '''
        thishash = hash((tuple(self._x), force_numeric))

        if Monitor.cached_x[ex0][0] == thishash:
            return Monitor.cached_x[ex0][1]

        if self.xtypecode or (not force_numeric):
            xall = self._x
            typecode = self.xtypecode
        else:
            xall = list_to_array(self._x)
            typecode = xall.typecode

        if ex0:
            x = [vx for vx in xall if vx != 0]
            if typecode:
                x = array.array(typecode, x)
        else:
            x = xall

        Monitor.cached_x[ex0] = (thishash, x)
        return x


class MonitorTimestamp(Monitor):
    '''
    monitortimestamp object

    Parameters
    ----------
    name : str
        name of the timestamped monitor
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    initial_tally : any, usually float
        initial value to be tallied (default 0) |n|
        if it important to provide the value at time=now

    monitor : bool
        if True (default), monitoring will be on. |n|
        if False, monitoring is disabled |n|
        it is possible to control monitoring later,
        with the monitor method

    type : str
        specifies how tallied values are to be stored
        Using a int, uint of float type results in less memory usage and better
        performance. Note that the getter should never return the number not to use
        as this is used to indicate 'off'

            - 'any' (default) stores values in a list. This allows for
               non numeric values. In calculations the values are
               forced to a numeric value (0 if not possible) do not use -inf
            - 'bool' bool (False, True). Actually integer >= 0 <= 254 1 byte do not use 255
            - 'int8' integer >= -127 <= 127 1 byte do not use -128
            - 'uint8' integer >= 0 <= 254 1 byte do not use 255
            - 'int16' integer >= -32767 <= 32767 2 bytes do not use -32768
            - 'uint16' integer >= 0 <= 65534 2 bytes do not use 65535
            - 'int32' integer >= -2147483647 <= 2147483647 4 bytes do not use -2147483648
            - 'uint32' integer >= 0 <= 4294967294 4 bytes do not use 4294967295
            - 'int64' integer >= -9223372036854775807 <= 9223372036854775807 8 bytes do not use -9223372036854775808
            - 'uint64' integer >= 0 <= 18446744073709551614 8 bytes do not use 18446744073709551615
            - 'float' float 8 bytes do not use -inf

    env : Environment
        environment where the monitor is defined |n|
        if omitted, default_env will be used

    Note
    ----
    A MonitorTimestamp collects both the value and the time.
    All statistics are based on the durations as weights.

    Example
    -------
        Tallied at time   0: 10 (xnow in definition of monitortimestamp) |n|
        Tallied at time  50: 11 |n|
        Tallied at time  70: 12 |n|
        Tallied at time  80: 10 |n|
        Now = 100

        This results in:  |n|
        x=  10 duration 50 |n|
        x=  11 duration 20 |n|
        x=  12 duration 10 |n|
        x=  10 duration 20

        And thus a mean of (10*50+11*20+12*10+10*20)/(50+20+10+20)
    '''

    cached_xduration = [(0, ()), (0, ())]  # index=ex0, value=[hash,(x,duration)]

    def __init__(self, name=omitted, initial_tally=0, monitor=True, type='any', env=omitted, *args, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        try:
            self.xtypecode, self.off = type_to_typecode_off(type)
        except KeyError:
            raise SalabimError('type (' + type + ') not recognized')

        _set_name(name, self.env._nameserializeComponent, self)
        self._timestamp = True
        self._tally = initial_tally
        self.reset(monitor=monitor)
        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of a monitortimestamp.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments are passed
        '''
        pass

    def __call__(self):  # direct moneypatching __call__ doesn't work
        return self._tally

    def get(self):
        '''
        Returns
        -------
        last tallied value : any, usually float

            Instead of this method, the timestamped monitor can also be called directly, like |n|

            level = sim.MonitorTimestamp('level') |n|
            ... |n|
            print(level()) |n|
            print(level.get())  # identical |n|
        '''
        return self._tally

    def reset(self, monitor=omitted):
        '''
        resets timestamped monitor

        Parameters
        ----------
        monitor : bool
            if True (default), monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, the monitor state remains unchanged
        '''
        if monitor is not omitted:
            self._monitor = monitor
        if self.xtypecode:
            self._x = array.array(self.xtypecode)
        else:
            self._x = []

        if self._monitor:
            self._x.append(self._tally)
        else:
            self._x.append(self.off)
        self._t = array.array('d')
        self._t.append(self.env._now)

    def monitor(self, value=omitted):
        '''
        enables/disabled timestamped monitor

        Parameters
        ----------
        value : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, no change

        Returns
        -------
        True, if monitoring enabled. False, if not : bool
        '''

        if value is not omitted:
            self._monitor = value
            if self._monitor:
                self.tally(self._tally)
            else:
                self._tally_off()  # can't use tally() here because self._tally should be untouched
        return self.monitor

    def tally(self, value):
        '''
        tally value

        Arguments
        ---------
        value : any, usually float
        '''
        self._tally = value
        if self._monitor:
            t = self.env._now
            if self._t[-1] == t:
                self._x[-1] = value
            else:
                self._x.append(value)
                self._t.append(t)

    def _tally_off(self):
        t = self.env._now
        if self._t[-1] == t:
            self._x[-1] = self.off
        else:
            self._x.append(self.off)
            self._t.append(t)

    def name(self):
        '''
        Returns
        -------
        Name of the monitor : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the monitortimestamp (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the monitortimestamp : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def mean(self, ex0=False):
        '''
        mean of tallied values, weighted with their durations

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        mean : float
        '''
        x, duration = self.xduration(ex0=ex0)
        sumduration = sum(duration)
        if sumduration:
            return sum(vx * vduration for vx, vduration in zip(x, duration)) / sumduration
        else:
            return nan

    def std(self, ex0=False):
        '''
        standard deviation of tallied values, weighted with their durations

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        standard deviation : float
        '''
        x, duration = self.xduration(ex0=ex0)
        sumduration = sum(duration)
        if sumduration:
            wmean = self.mean(ex0)
            wvar = sum((vduration * (vx - wmean)**2) for vx, vduration in zip(x, duration)) / sumduration
            return math.sqrt(wvar)
        else:
            return nan

    def minimum(self, ex0=False):
        '''
        minimum of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        minimum : float
        '''
        x, duration = self.xduration(ex0=ex0)
        if x:
            return min(x)
        else:
            return nan

    def maximum(self, ex0=False):
        '''
        maximum of tallied values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        maximum : float
        '''
        x, duration = self.xduration(ex0=ex0)
        if x:
            return max(x)
        else:
            return nan

    def median(self, ex0=False):
        '''
        median of tallied values weighted with their durations

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        median : float
        '''
        return self.percentile(50, ex0=ex0)

    def percentile(self, q, ex0=False):
        '''
        q-th percentile of tallied values, weighted with their durations

        Parameters
        ----------
        q : float
            percentage of the distribution |n|
            values <0 are treated a 0 |n|
            values >100 are treated as 100

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        q-th percentile: float
            0 returns the minimum, 50 the median and 100 the maximum
        '''
        return Monitor.percentile(self, q, ex0=ex0)

    def bin_count(self, lowerbound, upperbound, ex0=False):
        '''
        count of the number of tallied values,
        weighted with the duration in range (lowerbound,upperbound]

        Parameters
        ----------
        lowerbound : float
            non inclusive lowerbound

        upperbound : float
            inclusive upperbound

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        Returns
        -------
        number of values >lowerbound and <=upperbound: int
        '''
        x, duration = self.xduration(ex0=ex0)
        return sum((vduration for vx, vduration in zip(x, duration) if (vx > lowerbound) and (vx <= upperbound)))

    def duration(self, ex0=False):
        '''
        total duration

        Parameters
        ----------
        ex0 : bool
            if False (default), include samples with value 0. if True, exclude zero samples.

        Returns
        -------
        total duration : float
        '''
        _, duration = self.xduration(ex0=ex0)
        return sum(duration)

    def duration_zero(self):
        '''
        total duration of samples with value 0

        Returns
        -------
        total duration of zero samples : float
        '''
        return self.duration() - self.duration(ex0=True)

    def xduration(self, ex0=False, force_numeric=True):
        '''
        tuple of array with x-values and array with durations

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        force_numeric : bool
            if True (default), convert non numeric tallied values numeric if possible, otherwise assume 0 |n|
            if False, do not interpret x-values, return as list if type is list

        Returns
        -------
        array/list with x-values and array with durations : tuple
        '''

        thishash = hash((tuple(self._x), tuple(self._t), force_numeric))

        if MonitorTimestamp.cached_xduration[ex0][0] == thishash:
            return MonitorTimestamp.cached_xduration[ex0][1]

        durationall = array.array('d')
        for i, t in enumerate(self._t):
            if i != 0:
                durationall.append(t - lastt)  # NOQA
            lastt = t

        durationall.append(self.env._now - lastt)

        if self.xtypecode or (not force_numeric):
            xall = self._x
            typecode = self.xtypecode
        else:
            xall = list_to_array(self._x)
            typecode = xall.typecode

        duration = array.array('d')
        if typecode:
            x = array.array(typecode)
        else:
            x = []

        for vx, vduration in zip(xall, durationall):
            if vx != self.off:
                if (not ex0) or (vx != 0):
                    x.append(vx)
                    duration.append(vduration)

        MonitorTimestamp.cached_xduration[ex0] = (thishash, (x, duration))
        return x, duration

    def xt(self, ex0=False, exoff=False, force_numeric=True, add_now=True):
        '''
        tuple of array/list with x-values and array with timestamp

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        exoff : bool
            if False (default), include self.off. if True, exclude self.off's

        force_numeric : bool
            if True (default), convert non numeric tallied values numeric if possible, otherwise assume 0 |n|
            if False, do not interpret x-values, return as list if type is list

        add_now : bool
            if True (default), the last tallied x-value and the current time is added to the result |n|
            if False, the result ends with the last tallied value and the time that was tallied

        Returns
        -------
        array/list with x-values and array with timestamps : tuple

        Note
        ----
        The value self.off is stored when monitoring is turned off |n|
        The timestamps are not corrected for any reset_now() adjustment.
        '''
        if self.xtypecode or (not force_numeric):
            xall = self._x
            typecode = self.xtypecode
        else:
            xall = list_to_array(self._x)
            typecode = xall.typecode

        if typecode:
            x = array.array(typecode)
        else:
            x = []
        t = array.array('d')
        if add_now:
            addx = [xall[-1]]
            addt = [self.env._now]
        else:
            addx = []
            addt = []
        for vx, vt in zip(itertools.chain(xall, addx), itertools.chain(self._t, addt)):
            if not ex0 or (vx != 0):
                x.append(vx)
                t.append(vt)

        return x, t

    def tx(self, ex0=False, exoff=False, force_numeric=False, add_now=True):
        '''
        tuple of array with timestamps and array/list with x-values

        Parameters
        ----------
        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes

        exoff : bool
            if False (default), include self.off. if True, exclude self.off's

        force_numeric : bool
            if True (default), convert non numeric tallied values numeric if possible, otherwise assume 0 |n|
            if False, do not interpret x-values, return as list if type is list

        add_now : bool
            if True (default), the current time and the last tallied x-value added to the result |n|
            if False, the result ends with the time of the last tally and the last tallied x-value

        Returns
        -------
        array with timestamps and array/list with x-values : tuple

        Note
        ----
        The value self.off is stored when monitoring is turned off |n|
        The timestamps are not corrected for any reset_now() adjustment.
        '''
        return tuple(reversed(self.xt(ex0=ex0, exoff=exoff, force_numeric=force_numeric, add_now=add_now)))

    def print_statistics(self, show_header=True, show_legend=True, do_indent=False):
        '''
        print timestamped monitor statistics

        Parameters
        ----------
        show_header: bool
            primarily for internal use

        show_legend: bool
            primarily for internal use

        do_indent: bool
            primarily for internal use
        '''

        Monitor.print_statistics(self, show_header, show_legend, do_indent)

    def print_histogram(self, number_of_bins=30, lowerbound=0, bin_width=1, ex0=False):
        '''
        print timestamped monitor statistics and histogram

        Parameters
        ----------
        number_of_bins : int
            number of bins |n|
            if 0, also the header of the histogram will be surpressed

        lowerbound: float
            first bin

        bin_width : float
            width of the bins

        ex0 : bool
            if False (default), include zeroes. if True, exclude zeroes
        '''
        Monitor.print_histogram(self, number_of_bins, lowerbound, bin_width, ex0)


if Pythonista:

    class AnimationScene(scene.Scene):

        def __init__(self, env, *args, **kwargs):
            scene.Scene.__init__(self, *args, **kwargs)

        def setup(self):
            pass

        def touch_ended(self, touch):
            env = g.animation_env
            if env is not None:
                for uio in env.ui_objects:
                    ux = uio.x + env.xy_anchor_to_x(uio.xy_anchor, screen_coordinates=True)
                    uy = uio.y + env.xy_anchor_to_y(uio.xy_anchor, screen_coordinates=True)
                    if uio.type == 'button':
                        if touch.location in \
                                scene.Rect(ux - 2, uy - 2, uio.width + 2, uio.height + 2):
                            uio.action()
                            break  # new buttons might have been installed
                    if uio.type == 'slider':
                        if touch.location in\
                                scene.Rect(ux - 2, uy - 2, uio.width + 4, uio.height + 4):
                            xsel = touch.location[0] - ux
                            uio._v = uio.vmin + \
                                round(-0.5 + xsel / uio.xdelta) * uio.resolution
                            uio._v = max(min(uio._v, uio.vmax), uio.vmin)
                            if uio.action is not None:
                                uio.action(uio._v)
                                break  # new items might have been installed

        def draw(self):
            env = g.animation_env
            g.in_draw = True
            if (env is not None) and env._animate and env.running:
                scene.background(env.pythonistacolor('bg'))

                if env._synced:
                    if env.paused:
                        env.t = env.start_animation_time
                    else:
                        env.t = \
                            env.start_animation_time +\
                            ((time.time() -
                              env.start_animation_clocktime) * env._speed)
                    while (env.peek() < env.t) and env.running and env._animate:
                        env.step()
                else:
                    if (env._step_pressed or (not env.paused)) and env._animate:
                        env.step()
                        if not env._current_component._suppress_pause_at_step:
                            env._step_pressed = False
                        env.t = env._now
                if not env.paused:
                    env.frametimes.append(time.time())

                env.an_objects.sort(
                    key=lambda obj: (-obj.layer(env.t), obj.sequence))
                touchvalues = self.touches.values()

                capture_image = Image.new('RGB',
                    (env._width, env._height), env.colorspec_to_tuple('bg'))

                env.animation_pre_tick(env.t)
                for ao in env.an_objects:
                    ao.make_pil_image(env.t)
                    if ao._image_visible:
                        capture_image.paste(ao._image,
                            (int(ao._image_x),
                            int(env._height - ao._image_y - ao._image.size[1])),
                            ao._image)
                env.animation_post_tick(env.t)

                ims = scene.load_pil_image(capture_image)
                scene.image(ims, 0, 0, *capture_image.size)
                scene.unload_image(ims)

                for uio in env.ui_objects:
                    ux = uio.x + env.xy_anchor_to_x(uio.xy_anchor, screen_coordinates=True)
                    uy = uio.y + env.xy_anchor_to_y(uio.xy_anchor, screen_coordinates=True)

                    if uio.type == 'button':
                        linewidth = uio.linewidth

                        scene.push_matrix()
                        scene.fill(env.pythonistacolor(uio.fillcolor))
                        scene.stroke(env.pythonistacolor(uio.linecolor))
                        scene.stroke_weight(linewidth)
                        scene.rect(ux - 4, uy + 2, uio.width + 8, uio.height - 4)
                        scene.tint(env.pythonistacolor(uio.color))
                        scene.translate(ux + uio.width / 2,
                                        uy + uio.height / 2)
                        scene.text(uio.text(), uio.font,
                                   uio.fontsize, alignment=5)
                        scene.tint(1, 1, 1, 1)
                        # required for proper loading of images
                        scene.pop_matrix()
                    elif uio.type == 'slider':
                        scene.push_matrix()
                        scene.tint(env.pythonistacolor(uio.labelcolor))
                        v = uio.vmin
                        x = ux + uio.xdelta / 2
                        y = uy
                        mindist = inf
                        v = uio.vmin
                        while v <= uio.vmax:
                            if abs(v - uio._v) < mindist:
                                mindist = abs(v - uio._v)
                                vsel = v
                            v += uio.resolution
                        thisv = uio._v
                        for touch in touchvalues:
                            if touch.location in\
                                    scene.Rect(ux, uy, uio.width, uio.height):
                                xsel = touch.location[0] - ux
                                vsel = round(-0.5 + xsel /
                                             uio.xdelta) * uio.resolution
                                thisv = vsel
                        scene.stroke(env.pythonistacolor(uio.linecolor))
                        v = uio.vmin
                        xfirst = -1
                        while v <= uio.vmax:
                            if xfirst == -1:
                                xfirst = x
                            if v == vsel:
                                scene.stroke_weight(3)
                            else:
                                scene.stroke_weight(1)
                            scene.line(x, y, x, y + uio.height)
                            v += uio.resolution
                            x += uio.xdelta

                        scene.push_matrix()
                        scene.translate(xfirst, uy + uio.height + 2)
                        if uio.label:
                            scene.text(uio.label, uio.font, uio.fontsize, alignment=9)
                        scene.pop_matrix()
                        scene.translate(ux + uio.width, uy + uio.height + 2)
                        scene.text(str(thisv) + ' ',
                                   uio.font, uio.fontsize, alignment=7)
                        scene.tint(1, 1, 1, 1)
                        # required for proper loading of images later
                        scene.pop_matrix()
            g.in_draw = False


class Qmember():
    def __init__(self):
        pass

    def insert_in_front_of(self, m2, c, q, priority):
        m1 = m2.predecessor
        m1.successor = self
        m2.predecessor = self
        self.predecessor = m1
        self.successor = m2
        self.priority = priority
        self.component = c
        self.queue = q
        self.enter_time = c.env._now
        q._length += 1
        for iter in q._iter_touched:
            q._iter_touched[iter] = True
        c._qmembers[q] = self
        if q.env._trace:
            q.env.print_trace('', '', c.name(), 'enter ' + q.name())
        q.length.tally(q._length)
        if q._animate_on:
            q._animate_update()


class Queue(object):
    '''
    Queue object

    Parameters
    ----------
    fill : Queue, list or tuple
        fill the queue with the components in fill |n|
        if omitted, the queue will be empty at initialization

    name : str
        name of the queue |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    monitor : bool
        if True (default) , both length and length_of_stay are monitored |n|
        if False, monitoring is disabled.

    env : Environment
        environment where the queue is defined |n|
        if omitted, default_env will be used

    _isinternal : bool
        for internal use only
    '''

    def __init__(self, name=omitted, monitor=True, fill=omitted, env=omitted, _isinternal=False, *args, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        _set_name(name, self.env._nameserializeQueue, self)
        self._head = Qmember()
        self._tail = Qmember()
        self._head.successor = self._tail
        self._head.predecessor = None
        self._tail.successor = None
        self._tail.predecessor = self._head
        self._head.component = None
        self._tail.component = None
        self._head.priority = 0
        self._tail.priority = 0
        self._length = 0
        self._iter_sequence = 0
        self._iter_touched = {}
        self._animate_on = False
        self.length = MonitorTimestamp(
            'Length of ' + self.name(), initial_tally=0, monitor=monitor, type='uint32', env=self.env)
        self.length_of_stay = Monitor(
            'Length of stay in ' + self.name(), monitor=monitor, type='float')
        if fill is not omitted:
            for c in fill:
                c._enter(self)
        if not _isinternal:
            if self.env._trace:
                self.env.print_trace('', '', self.name() + ' create')
        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of a queue.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments are passed
        '''
        pass

    def _animate_update(self):
        if self._animate_reverse:
            q = reversed(self)
        else:
            q = self
        x = self._animate_x
        y = self._animate_y
        for c in q:
            if self not in c._aos:
                c._aos[self] = c.animation_objects(self)
                if isinstance(c._aos[self], (tuple, list)):
                    if len(c._aos[self]) <= 1:
                        raise SalabimError('at least 2 elements (sizex, sizey) required')
                    for i, el in enumerate(c._aos[self]):
                        if i == 0:
                            try:
                                float(el)
                            except ValueError:
                                raise SalabimError('element #0 (sizex) of tuple/list not numeric')
                        elif i == 1:
                            try:
                                float(el)
                            except ValueError:
                                raise SalabimError('element #1 (sizey) of tuple/list not numeric')
                        else:
                            if not isinstance(el, Animate):
                                raise SalabimError('element #{} of tuple/list is not Animate type'.format(i))
                else:
                    raise SalabimError('animation_objects should return list or tuple')

            for ao in c._aos[self][2:]:
                ao.x0 = x
                ao.y0 = y
            x += self._displacement_x * c._aos[self][0]
            y += self._displacement_y * c._aos[self][1]

    def animate(self, x=500, y=100, direction='w', on=True, reverse=False):
        '''
        turns on/off animation for the queue

        Parameters
        ----------
        x : float
            x-position of the first component in the queue |n|
            default: 500

        y : float
            y-position of the first component in the queue |n|
            default: 100

        direction : str
            if 'w', waiting line runs westwards (i.e. from right to left) (default) |n|
            if 'n', waiting line runs northeards (i.e. from bottom to top) |n|
            if 'e', waiting line runs eastwards (i.e. from left to right) |n|
            if 's', waiting line runs southwards (i.e. from top to bottom)

        on : bool
            if True (default) do animate the queue. If False, do not animate.

        reverse : bool
            if False (default), display in normal order. If True, reversed.
        '''

        self._animate_on = on
        if on:
            self._animate_x = x
            self._animate_y = y
            self._displacement_x, self._displacement_y = \
                {'w': (-1, 0), 'n': (0, 1), 'e': (1, 0), 's': (0, -1)}[direction.lower()]

            self._animate_reverse = reverse
            self._animate_update()
        else:
            for c in self:
                c._remove_from_aos(self)

    def reset_monitors(self, monitor=omitted):
        '''
        resets queue monitor length_of_stay and timestamped monitor length

        Parameters
        ----------
        monitor : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, no change of monitoring state

        Note
        ----
        it is possible to reset individual monitoring with length_of_stay.reset() and length.reset()
        '''
        self.length.reset(monitor=monitor)
        self.length_of_stay.reset(monitor=monitor)

    def monitor(self, value):
        '''
        enables/disables monitoring of length_of_stay and length

        Parameters
        ----------
        value : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|

        Note
        ----
        it is possible to individually control monitoring with length_of_stay.monitor() and length.monitor()
        '''

        self.length.monitor(value=value)
        self.length_of_stay.monitor(value=value)

    def __repr__(self):
        return objectclass_to_str(self) + '(' + self.name() + ')'

    def print_info(self):
        '''
        prints information about the component
        '''
        print(objectclass_to_str(self) + ' ' + hex(id(self)))
        print('  name=' + self.name())
        if self._length:
            print('  component(s):')
            mx = self._head.successor
            while mx != self._tail:
                print('    ' + pad(mx.component.name(), 20) +
                    ' enter_time' + time_to_string(mx.enter_time - self.env._offset) +
                    ' priority=' + str(mx.priority))
                mx = mx.successor
        else:
            print('  no components')

    def print_statistics(self):
        '''
        prints a summary of statistics of a queue
        '''
        print('Statistics of {} at {}'.format(self.name(), fn(self.env._now - self.env._offset, 13, 3)))
        self.length.print_statistics(
            show_header=False, show_legend=True, do_indent=True)

        print()
        self.length_of_stay.print_statistics(
            show_header=False, show_legend=False, do_indent=True)

    def name(self):
        '''
        Returns
        -------
        Name of the queue : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the queue (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the queue : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def add(self, component):
        '''
        adds a component to the tail of a queue

        Parameters
        ----------
        component : Component
            component to be added to the tail of the queue |n|
            may not be member of the queue yet

        Note
        ----
        the priority will be set to
        the priority of the tail of the queue, if any
        or 0 if queue is empty |n|
        This method is equivalent to append()
        '''
        component.enter(self)

    def append(self, component):
        '''
        appends a component to the tail of a queue

        Parameters
        ----------
        component : Component
            component to be appened to the tail of the queue |n|
            may not be member of the queue yet

        Note
        ----
        the priority will be set to
        the priority of the tail of the queue, if any
        or 0 if queue is empty |n|
        This method is equivalent to add()
        '''
        component.enter(self)

    def add_at_head(self, component):
        '''
        adds a component to the head of a queue

        Parameters
        ----------

        component : Component
            component to be added to the head of the queue |n|
            may not be member of the queue yet

        Note
        ----
        the priority will be set to
        the priority of the head of the queue, if any
        or 0 if queue is empty
        '''
        component.enter_at_head(self)

    def add_in_front_of(self, component, poscomponent):
        '''
        adds a component to a queue, just in front of a component

        Parameters
        ----------
        component : Component
            component to be added to the queue |n|
            may not be member of the queue yet

        poscomponent : Component
            component in front of which component will be inserted |n|
            must be member of the queue

        Note
        ----
        the priority of component will be set to the priority of poscomponent
        '''
        component.enter_in_front_off(self, poscomponent)

    def insert(self, index, component):
        '''
        Insert component before index-th element of the queue

        Parameters
        ----------
        index : int
            component to be added just before index'th element |n|
            should be >=0 and <=len(self)

        component : Component
            component to be added to the queue

        Note
        ----
        the priority of component will be set to the priority of the index'th component,
        or 0 if the queue is empty
        '''
        if index < 0:
            raise SalabimError('index <0')
        if index > self._length:
            raise SalabimError('index > lengh of queue')
        component._checknotinqueue(self)
        mx = self._head.successor
        count = 0
        while mx != self._tail:
            if count == index:
                break
            count = count + 1
            mx = mx.successor
        priority = mx.priority
        Qmember().insert_in_front_of(mx, component, self, priority)

    def add_behind(self, component, poscomponent):
        '''
        adds a component to a queue, just behind a component

        Parameters
        ----------
        component : Component
            component to be added to the queue |n|
            may not be member of the queue yet

        poscomponent : Component
            component behind which component will be inserted |n|
            must be member of the queue

        Note
        ----
        the priority of component will be set to the priority of poscomponent

        '''
        component.enter_behind(self, poscomponent)

    def add_sorted(self, component, priority):
        '''
        adds a component to a queue, according to the priority

        Parameters
        ----------
        component : Component
            component to be added to the queue |n|
            may not be member of the queue yet

        priority : float
            priority of the component |n|

        Note
        ----
        component will be placed just after the last component with
        a priority <= priority
        '''
        component.enter_sorted(self, priority)

    def remove(self, component=omitted):
        '''
        removes component from the queue

        Parameters
        ----------
        component : Component
            component to be removed |n|
            if omitted, all components will be removed.

        Note
        ----
        component must be member of the queue
        '''
        if component is omitted:
            self.clear()
        else:
            component.leave(self)

    def head(self):
        '''
        Returns
        -------
        the head component of the queue, if any. None otherwise : Component

        Note
        ----
        q[0] is a more Pythonic way to access the head of the queue
        '''
        return self._head.successor.component

    def tail(self):
        '''
        Returns
        -------
        the tail component of the queue, if any. None otherwise : Component

        Note
        -----
        q[-1] is a more Pythonic way to access the tail of the queue
        '''
        return self._tail.predecessor.component

    def pop(self, index=omitted):
        '''
        removes a component by its position (or head)

        Parameters
        ----------
        index : int
            index-th element to remove, if any |n|
            if omitted, return the head of the queue, if any

        Returns
        -------
        The i-th component or head : Component
            None if not existing
        '''
        if index is omitted:
            c = self._head.successor.component
        else:
            c = self[index]
        if c is not None:
            c.leave(self)
        return c

    def successor(self, component):
        '''
        successor in queue

        Parameters
        ----------
        component : Component
            component whose successor to return |n|
            must be member of the queue

        Returns
        -------
        successor of component, if any : Component
            None otherwise
        '''
        return component.successor(self)

    def predecessor(self, component):
        '''
        predecessor in queue

        Parameters
        ----------
        component : Component
            component whose predecessor to return |n|
            must be member of the queue

        Returns
        -------
        predecessor of component, if any : Component |n|
            None otherwise.
        '''
        return component.predecessor(self)

    def __contains__(self, component):
        return component._member(self) is not None

    def __getitem__(self, key):
        if isinstance(key, slice):
            # Get the start, stop, and step from the slice
            startval, endval, incval = key.indices(self._length)
            if incval > 0:
                l = []
                targetval = startval
                mx = self._head.successor
                count = 0
                while mx != self._tail:
                    if targetval >= endval:
                        break
                    if targetval == count:
                        l.append(mx.component)
                        targetval += incval
                    count += 1
                    mx = mx.successor
            else:
                l = []
                targetval = startval
                mx = self._tail.predecessor
                count = self._length - 1
                while mx != self._head:
                    if targetval <= endval:
                        break
                    if targetval == count:
                        l.append(mx.component)
                        targetval += incval  # incval is negative here!
                    count -= 1
                    mx = mx.predecessor

            return list(l)

        elif isinstance(key, int):
            if key < 0:  # Handle negative indices
                key += self._length
            if key < 0 or key >= self._length:
                return None
            mx = self._head.successor
            count = 0
            while mx != self._tail:
                if count == key:
                    return mx.component
                count = count + 1
                mx = mx.successor

            return None  # just for safety

        else:
            raise TypeError('Invalid argument type.')

    def __delitem__(self, key):
        if isinstance(key, slice):
            for c in self[key]:
                self.remove(c)
        elif isinstance(key, int):
            self.remove(self[key])
        else:
            raise SalabimError('Invalid argument type')

    def __len__(self):
        return self._length

    def __reversed__(self):
        self._iter_sequence += 1
        iter_sequence = self._iter_sequence
        self._iter_touched[iter_sequence] = False
        iter_list = []
        mx = self._tail.predecessor
        while mx != self._head:
            iter_list.append(mx)
            mx = mx.predecessor
        iter_index = 0
        while len(iter_list) > iter_index:
            if self._iter_touched[iter_sequence]:
                # place all taken qmembers on the list
                iter_list = iter_list[:iter_index]
                mx = self._tail.predecessor
                while mx != self._head:
                    if mx not in iter_list:
                        iter_list.append(mx)
                    mx = mx.precessor
                self._iter_touched[iter_sequence] = False
            else:
                c = iter_list[iter_index].component
                if c is not None:  # skip deleted components
                    yield c
                iter_index += 1

        del self._iter_touched[iter_sequence]

    def __add__(self, q):
        return self.union(q)

    def __or__(self, q):
        return self.union(q)

    def __sub__(self, q):
        return self.difference(q)

    def __and__(self, q):
        return self.intersection(q)

    def __xor__(self, q):
        return self.symmetric_difference(q)

    def count(self, component):
        '''
        component count

        Parameters
        ---------
        component : Component
            component to count

        Returns
        -------
        number of occurences of component in the queue

        Note
        ----
        The result can only be 0 or 1
        '''
        return component.count(self)

    def index(self, component):
        '''
        get the index of a component in the queue

        Parameters
        ----------
        component : Component
            component to be queried |n|
            does not need to be in the queue

        Returns
        -------
        index of component in the queue : int
            0 denotes the head, |n|
            returns -1 if component is not in the queue
        '''
        return component.index(self)

    def component_with_name(self, txt):
        '''
        returns a component in the queue according to its name

        Parameters
        ----------
        txt : str
            name of component to be retrieved

        Returns
        -------
        the first component in the queue with name txt : Component |n|
            returns None if not found
        '''
        mx = self._head.successor
        while mx != self._tail:

            if mx.component.name() == txt:
                return mx.component
            mx = mx.successor
        return None

    def __iter__(self):
        self._iter_sequence += 1
        iter_sequence = self._iter_sequence
        self._iter_touched[iter_sequence] = False
        iter_list = []
        mx = self._head.successor
        while mx != self._tail:
            iter_list.append(mx)
            mx = mx.successor
        iter_index = 0
        while len(iter_list) > iter_index:
            if self._iter_touched[iter_sequence]:
                # place all taken qmembers on the list
                iter_list = iter_list[:iter_index]
                mx = self._head.successor
                while mx != self._tail:
                    if mx not in iter_list:
                        iter_list.append(mx)
                    mx = mx.successor
                self._iter_touched[iter_sequence] = False
            else:
                c = iter_list[iter_index].component
                if c is not None:  # skip deleted components
                    yield c
                iter_index += 1

        del self._iter_touched[iter_sequence]

    def extend(self, q):
        '''
        extends the queue with components of q that are not already in self

        Parameters
        ----------
        q : queue, list or tuple

        Note
        ----
        The components added to the queue will get the priority of the tail of self.
        '''
        for c in q:
            if c not in self:
                c._enter(q)

    def as_set(self):
        return {c for c in self}

    def as_list(self):
        return [c for c in self]

    def union(self, q, name=omitted, monitor=False):
        '''
        Parameters
        ----------
        q : Queue
            queue to be unioned with self

        name : str
            name of the  new queue |n|
            if omitted, self.name() + q.name()

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the queue

        Returns
        -------
        queue containing all elements of self and q : Queue

        Note
        ----
        the priority will be set to 0 for all components in the
        resulting  queue |n|
        the order of the resulting queue is as follows |n|:
        first all components of self, in that order,
        followed by all components in q that are not in self,
        in that order. |n|
        Alternatively, the more pythonic | operator is also supported, e.g. q1 | q2
        '''
        save_trace = self.env._trace
        self.env._trace = False
        if name is omitted:
            name = self.name() + ' | ' + q.name()
        q1 = type(self)(name=name, monitor=monitor, env=self.env)
        self_set = self.as_set()

        mx = self._head.successor
        while mx != self._tail:
            Qmember().insert_in_front_of(q1._tail, mx.component, q1, 0)
            mx = mx.successor

        mx = q._head.successor
        while mx != q._tail:
            if mx.component not in self_set:
                Qmember().insert_in_front_of(q1._tail, mx.component, q1, 0)
            mx = mx.successor

        self.env._trace = save_trace
        return q1

    def intersection(self, q, name=omitted, monitor=False):
        '''
        returns the intersect of two queues

        Parameters
        ----------
        q : Queue
            queue to be intersected with self

        name : str
            name of the  new queue |n|
            if omitted, self.name() + q.name()

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the queue

        Returns
        -------
        queue with all elements that are in self and q : Queue

        Note
        ----
        the priority will be set to 0 for all components in the
        resulting  queue |n|
        the order of the resulting queue is as follows: |n|
        in the same order as in self. |n|
        Alternatively, the more pythonic & operator is also supported, e.g. q1 & q2
        '''
        save_trace = self.env._trace
        self.env._trace = False
        if name is omitted:
            name = self.name() + ' & ' + q.name()
        q1 = type(self)(name=name, monitor=monitor, env=self.env)
        q_set = q.as_set()
        mx = self._head.successor
        while mx != self._tail:
            if mx.component in q_set:
                Qmember().insert_in_front_of(q1._tail, mx.component, q1, 0)
            mx = mx.successor
        self.env._trace = save_trace
        return q1

    def difference(self, q, name=omitted, monitor=monitor):
        '''
        returns the difference of two queues

        Parameters
        ----------
        q : Queue
            queue to be 'subtracted' from self

        name : str
            name of the  new queue |n|
            if omitted, self.name() - q.name()

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the queue

        Returns
        -------
        queue containing all elements of self that are not in q

        Note
        ----
        the priority will be copied from the original queue.
        Also, the order will be maintained. |n|
        Alternatively, the more pythonic - operator is also supported, e.g. q1 - q2
        '''
        if name is omitted:
            name = self.name() + ' - ' + q.name()
        save_trace = self.env._trace
        self.env._trace = False
        q1 = type(self)(name=name, monitor=monitor, env=self.env)
        q_set = q.as_set()
        mx = self._head.successor
        while mx != self._tail:
            if mx.component not in q_set:
                Qmember().insert_in_front_of(
                    q1._tail, mx.component, q1, mx.priority)
            mx = mx.successor
        self.env._trace = save_trace
        return q1

    def symmetric_difference(self, q, name=omitted, monitor=monitor):
        '''
        returns the symmetric difference of two queues

        Parameters
        ----------
        q : Queue
            queue to be 'subtracted' from self

        name : str
            name of the  new queue |n|
            if omitted, self.name() - q.name()

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the queue

        Returns
        -------
        queue containing all elements that are either in self or q, but not in both

        Note
        ----
        the priority of all elements will be set to 0 for all components in the new queue.
        Order: First, elelements in self (in that order), then elements in q (in that order)
        Alternatively, the more pythonic ^ operator is also supported, e.g. q1 ^ q2
        '''
        if name is omitted:
            name = self.name() + ' ^ ' + q.name()
        save_trace = self.env._trace
        self.env._trace = False
        q1 = type(self)(name=name, monitor=monitor, env=self.env)

        intersection_set = self.as_set() & q.as_set()
        mx = self._head.successor
        while mx != self._tail:
            if mx.component not in intersection_set:
                Qmember().insert_in_front_of(
                    q1._tail, mx.component, q1, 0)
            mx = mx.successor
        mx = q._head.successor
        while mx != q._tail:
            if mx.component not in intersection_set:
                Qmember().insert_in_front_of(
                    q1._tail, mx.component, q1, 0)
            mx = mx.successor

        self.env._trace = save_trace
        return q1

    def copy(self, name=omitted, monitor=monitor):
        '''
        returns a copy of two queues

        Parameters
        ----------
        name : str
            name of the new queue |n|
            if omitted, 'copy of ' + self.name()

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the queue

        Returns
        -------
        queue with all elements of self : Queue

        Note
        ----
        The priority will be copied from original queue.
        Also, the order will be maintained.
        '''
        save_trace = self.env._trace
        self.env._trace = False
        if name is omitted:
            name = 'copy of ' + self.name()
        q1 = type(self)(name=name, env=self.env)
        mx = self._head.successor
        while mx != self._tail:
            Qmember().insert_in_front_of(q1._tail, mx.component, q1, mx.priority)
            mx = mx.successor
        self.env._trace = save_trace
        return q1

    def move(self, name=omitted, monitor=monitor):
        '''
        makes a copy of a queue and empties the original

        Parameters
        ----------
        name : str
            name of the new queue

        monitor : bool
            if True, monitor the queue |n|
            if False (default), do not monitor the yqueue

        Returns
        -------
        queue containing all elements of self: Queue

        Note
        ----
        Priorities will be kept |n|
        self will be emptied
        '''
        q1 = self.copy(name, monitor=monitor)
        self.clear()
        return q1

    def clear(self):
        '''
        empties a queue

        removes all components from a queue
        '''
        mx = self._head.successor
        while mx != self._tail:
            c = mx.component
            mx = mx.successor
            c._leave(self)
        if self.env._trace:
            self.env.print_trace('', '', self.name() + ' clear')


class Environment(object):
    '''
    environment object

    Parameters
    ----------
    trace : bool
        defines whether to trace or not |n|
        if omitted, False

    random_seed : hashable object, usually int
        the seed for random, equivalent to random.seed() |n|
        if None, a purely random value (based on the current time) will be used
        (not reproducable) |n|
        if the null string (''), no action on random is taken |n|
        if omitted, 1234567 will be used.

    name : str
        name of the environment |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class (lowercased)
        or 'default environment' if isdefault_env is True.

    print_trace_header : bool
        if True (default) print a (two line) header line as a legend |n|
        if False, do not print a header |n|
        note that the header is only printed if trace=True

    isdefault_env : bool
        if True (default), this environment becomes the default environment |n|
        if False, this environment will not be the default environment |n|
        if omitted, this environment becomes the default environment |n|

    Note
    ----
    The trace may be switched on/off later with trace |n|
    The seed may be later set with random_seed() |n|
    Initially, the random stream will be seeded with the value 1234567.
    If required to be purely, not not reproducable, values, use
    random_seed=None.
    '''
    _nameserialize = {}
    cached_modelname_width = [None, None]

    def __init__(self, trace=False, random_seed=omitted, name=omitted,
      print_trace_header=True, isdefault_env=True, *args, **kwargs):
        if isdefault_env:
            g.default_env = self
        if name is omitted:
            if isdefault_env:
                name = 'default environment'
        self._trace = trace
        self._source_files = {inspect.getframeinfo(_get_caller_frame()).filename: 0}
        if random_seed != '':
            if random_seed is omitted:
                random_seed = 1234567
            random.seed(random_seed)
        _set_name(name, Environment._nameserialize, self)
        self._buffered_trace = False
        self._suppress_trace_standby = True
        if self._trace:
            if print_trace_header:
                self.print_trace_header()
            self.print_trace('', '', self.name() + ' initialize')
        self.env = self
        # just to allow main to be created; will be reset later
        self._nameserializeComponent = {}
        self._now = 0
        self._offset = 0
        self._main = Component(name='main', env=self, process=None)
        self._main._status = current
        self._main.frame = _get_caller_frame()
        self._current_component = self._main
        if self._trace:
            self.print_trace('{:10.3f}'.format(0), 'main', 'current')
        self._nameserializeQueue = {}
        self._nameserializeComponent = {}
        self._nameserializeResource = {}
        self._nameserializeState = {}
        self._nameserializeMonitor = {}
        self._nameserializeMonitorTimestamp = {}
        self._seq = 0
        self._event_list = []
        self._standbylist = []
        self._pendingstandbylist = []

        self.an_objects = []
        self.ui_objects = []
        self.serial = 0
        self._speed = 1
        self._animate = False
        self.running = False
        self.t = 0
        self._synced = True
        self._step_pressed = False
        self.stopped = False
        if Pythonista:
            self._width, self._height = ui.get_screen_size()
            self._width = int(self._width)
            self._height = int(self._height)
        else:
            self._width = 1024
            self._height = 768
        self._x0 = 0
        self._y0 = 0
        self._x1 = self._width
        self._scale = 1
        self._y1 = self._y0 + self._height
        self._background_color = 'white'
        self._foreground_color = 'black'
        self._fps = 30
        self._modelname = ''
        self.use_toplevel = False
        self._show_fps = False
        self._show_time = True
        self._video = ''
        self._video_out = None
        self.an_modelname()
        self.an_clocktext()

        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of an environment.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments are passed
        '''
        pass

    def serialize(self):
        self.serial += 1
        return self.serial

    def __repr__(self):
        return objectclass_to_str(self) + ' (' + self.name() + ')'

    def animation_pre_tick(self, t):
        '''
        called just before the animation object loop. |n|
        Default behaviour: just return

        Parameters
        ----------
        t : float
            Current (animation) time.
        '''
        return

    def animation_post_tick(self, t):
        '''
        called just after the animation object loop. |n|
        Default behaviour: just return

        Parameters
        ----------
        t : float
            Current (animation) time.
        '''
        return

    def print_info(self):
        '''
        prints information about the environment
        '''
        print(objectclass_to_str(self) + ' ' + hex(id(self)))
        print('  name=' + self.name())
        print('  now=' + time_to_string(self._now - self._offset))
        print('  current_component=' + self._current_component.name())
        print('  trace=' + str(self._trace))

    def step(self):
        '''
        executes the next step of the future event list

        for advanced use with animation / GUI loops
        '''
        if len(self.env._pendingstandbylist) > 0:
            c = self.env._pendingstandbylist.pop(0)
            if c._status == standby:  # skip cancelled components
                c._status = current
                c._scheduled_time = inf
                self.env._current_component = c
                if self._trace:
                    self.print_trace('{:10.3f}'.format(self._now - self.env._offset), c.name(),
                        'current (standby)', s0=c.lineno_txt(), _optional=self._suppress_trace_standby)
                try:
                    next(c._process)
                    return
                except StopIteration:
                    c.release()
                    if self._trace:
                        self.print_trace('', '', c.name() + ' ended', s0='')
                    c._status = data
                    c._scheduled_time = inf
                    c._process = None
                    for ao in self.an_objects[:]:
                        if ao.parent == c:
                            self.an_objects.remove(ao)
                    return
        if len(self.env._standbylist) > 0:
            self._pendingstandbylist = list(self.env._standbylist)
            self._standbylist = []

        if self._event_list:
            (t, _, c) = heapq.heappop(self._event_list)
        else:
            t = inf
            c = self._main
        c._on_event_list = False
        self.env._now = t

        self._current_component = c

        c._status = current
        c._scheduled_time = inf
        if self._trace:
            self.print_trace('{:10.3f}'.format(self._now - self._offset), c.name(),
              'current', s0=c.lineno_txt())
        if c == self._main:
            self.running = False
            return
        c._check_fail()
        try:
            next(c._process)
            return
        except StopIteration:
            c.release()
            if self._trace:
                self.print_trace('', '', c.name() + ' ended', s0='')
            c._status = data
            c._scheduled_time = inf
            c._process = None
            for ao in self.an_objects[:]:
                if ao.parent == c:
                    self.an_objects.remove(ao)
            return

    def _print_event_list(self, s):
        print('eventlist ', s)
        for (t, seq, comp) in self._event_list:
            print('{:10.3f} {}'.format(t, comp.name()))

    def animation_parameters(self,
      animate=True, synced=omitted, speed=omitted, width=omitted, height=omitted,
      x0=omitted, y0=omitted, x1=omitted, background_color=omitted, foreground_color=omitted,
      fps=omitted, modelname=omitted, use_toplevel=omitted,
      show_fps=omitted, show_time=omitted,
      video=omitted):
        '''
        set animation parameters

        Parameters
        ----------
        animate : bool
            animate indicator |n|
            if not specified, set animate on |n|

        synced : bool
            specifies whether animation is synced |n|
            if omitted, no change. At init of the environment synced will be set to True

        speed : float
            speed |n|
            specifies how much faster or slower than real time the animation will run.
            e.g. if 2, 2 simulation time units will be displayed per second.

        width : int
            width of the animation in screen coordinates |n|
            if omitted, no change. At init of the environment, the width will be
            set to 1024 for CPython and the current screen width for Pythonista.

        height : int
            height of the animation in screen coordinates |n|
            if omitted, no change. At init of the environment, the height will be
            set to 768 for CPython and the current screen height for Pythonista.

        x0 : float
            user x-coordinate of the lower left corner |n|
            if omitted, no change. At init of the environment, x0 will be set to 0.

        y0 : float
            user y_coordinate of the lower left corner |n|
            if omitted, no change. At init of the environment, y0 will be set to 0.

        x1 : float
            user x-coordinate of the lower right corner |n|
            if omitted, no change. At init of the environment, x1 will be set to 1024
            for CPython and the current screen width for Pythonista.

        background_color : colorspec
            color of the background |n|
            if omitted, no change. At init of the environment, this will be set to white.

        foreground_color : colorspec
            color of foreground (texts) |n|
            if omitted and background_color is specified, either white of black will be used,
            in order to get a good contrast with the background color. |n|
            if omitted and background_color is also omitted, no change. At init of the
            environment, this will be set to black.

        fps : float
            number of frames per second

        modelname : str
            name of model to be shown in upper left corner,
            along with text 'a salabim model' |n|
            if omitted, no change. At init of the environment, this will be set
            to the null string, which implies suppression of this feature.

        use_toplevel : bool
            if salabim animation is used in parallel with
            other modules using tkinter, it might be necessary to
            initialize the root with tkinter.TopLevel().
            In that case, set this parameter to True. |n|
            if False (default), the root will be initialized with tkinter.Tk()

        show_fps : bool
            if True, show the number of frames per second |n|
            if False, do not show the number of frames per second (default)

        show_time: bool
            if True, show the time (default)  |n|
            if False, do not show the time

        video : str
            if video is not omitted, a mp4 format video with the name video
            will be created. |n|
            The video has to have a .mp4 etension |n|
            This requires installation of opencv (cv2) and numpy.

        Note
        ----
        The y-coordinate of the upper right corner is determined automatically
        in such a way that the x and scaling are the same. |n|
        '''
        frame_changed = False
        video_parameters_changed = False
        if speed is not omitted:
            self._speed = speed
            self.set_start_animation()

        if show_fps is not omitted:
            if show_fps != show_fps:
                self._show_fps = show_fps

        if show_time is not omitted:
            self._show_time = show_time

        if synced is not omitted:
            self._synced = synced
            self.set_start_animation()

        if width is not omitted:
            if self._width != width:
                self._width = width
                frame_changed = True
                width_changed = True

        if height is not omitted:
            if self._height != height:
                self._height = height
                frame_changed = True
                height_changed = True

        if fps is not omitted:
            if self._fps != fps:
                self._fps = fps
                fps_changed = True

        if x0 is not omitted:
            if self._x0 != x0:
                self._x0 = x0
                self.uninstall_uios()

        if x1 is not omitted:
            if self._x1 != x1:
                self._x1 = x1
                self.uninstall_uios()

        if y0 is not omitted:
            if self._y0 != y0:
                self._y0 = y0
                self.uninstall_uios()

        if background_color is not omitted:
            if background_color in ('fg', 'bg'):
                raise SalabimError(background_color + 'not allowed for background_color')
            if self._background_color != background_color:
                self._background_color = background_color
                frame_changed = True
            if foreground_color is omitted:
                self._foreground_color = 'white' if self.is_dark('bg') else 'black'

        if foreground_color is not omitted:
            if foreground_color in ('fg', 'bg'):
                raise SalabimError(foreground_color + 'not allowed for foreground_color')
            self._foreground_color = foreground_color

        if modelname is not omitted:
            self._modelname = modelname

        if use_toplevel is not omitted:
            self.use_toplevel = use_toplevel

        if animate is None:
            animate = self._animate  # no change
        else:
            if animate != self._animate:
                frame_changed = True

        self._scale = self._width / (self._x1 - self._x0)
        self._y1 = self._y0 + self._height / self._scale

        if g.animation_env is not self:
            if g.animation_env is not None:
                g.animation_env.video_close()
            if animate:
                frame_changed = True
            else:
                frame_changed = False  # no animation required, so leave running animation_env untouched

        video_opened = False
        if video is not omitted:
            if video != self._video:
                if self._video:
                    self.video_close()
                self._video = video
                if self._video:
                    video_opened = True
                    can_animate(try_only=False)
                    can_video(try_only=False)
                    extension = os.path.splitext(self._video)[1].lower()
                    if extension == '.mp4':
                        self.video_fourcc = 'MP4V'
                    elif extension == '.avi':
                        self.video_fourcc = 'MP4V'
                    else:
                        raise SalabimError('unsupported video file extension')

                    fourcc = cv2.VideoWriter_fourcc(*self.video_fourcc)
                    self._video_out = cv2.VideoWriter(
                        self._video, fourcc, self._fps, (self._width, self._height))

        if self._video and (not video_opened):
            if width_changed:
                raise SalabimError('width changed while recording video.')
            if height_changed:
                raise SalabimError('height changed while recording video.')                
            if fps_changed:
                raise SalabimError('fps changed while recording video.')
                                
        if self._video:
            self.video_t = self.t

        if frame_changed:
            if g.animation_env is not None:
                g.animation_env._animate = animate
                if not Pythonista:
                    g.animation_env.root.destroy()
                g.animation_env = None

            if animate:
                can_animate(try_only=False)  # install modules

                g.animation_env = self
                self.t = self._now  # for the call to set_start_animation
                self.set_start_animation()

                self.paused = False

                if Pythonista:
                    if g.animation_scene is None:
                        g.animation_scene = AnimationScene(env=self)
                        scene.run(g.animation_scene, frame_interval=60 / self._fps, show_fps=False)

                else:
                    if self.use_toplevel:
                        self.root = tkinter.Toplevel()
                    else:
                        self.root = tkinter.Tk()
                    g.canvas = tkinter.Canvas(
                        self.root, width=self._width, height=self._height)
                    g.canvas.configure(background=self.colorspec_to_hex('bg', False))
                    g.canvas.pack()
                    g.canvas_objects = []

                self.uninstall_uios()  # this causes all ui objects to be (re)installed

                self.an_menu_buttons()

        self._animate = animate

    def video_close(self):
        '''
        closes the current animation video recording, if any.
        '''
        if self._video_out:
            self._video_out.release()
            self._video_out = None
            self._video = ''

    def uninstall_uios(self):
        for uio in self.ui_objects:
            uio.installed = False

    def x0(self, value=omitted):
        '''
        x coordinate of lower left corner of animation

        Parameters
        ----------
        value : float
            new x coordinate

        Returns
        -------
        x coordinate of lower left corner of animation : float
        '''
        if value is not omitted:
            self.animation_parameters(x0=value, animate=None)
        return self._x0

    def x1(self, value=omitted):
        '''
        x coordinate of upper right corner of animation : float

        Parameters
        ----------
        value : float
            new x coordinate |n|
            if not specified, no change

        Returns
        -------
        x coordinate of upper right corner of animation : float
        '''
        if value is not omitted:
            self.animation_parameters(x1=value, animate=None)
        return self._x1

    def y0(self, value=omitted):
        '''
        y coordinate of lower left corner of animation

        Parameters
        ----------
        value : float
            new y coordinate |n|
            if not specified, no change

        Returns
        -------
        y coordinate of lower left corner of animation : float
        '''
        if value is not omitted:
            self.animation_parameters(y0=value, animate=None)
        return self._y0

    def y1(self):
        '''
        y coordinate of upper right corner of animation

        Returns
        -------
        y coordinate of upper right corner of animation : float

        Note
        ----
        It is not possible to set this value explicitely.
        '''
        return self._y1

    def scale(self):
        '''
        scale of the animation, i.e. width / (x1 - x0)

        Returns
        -------
        scale : float

        Note
        ----
        It is not possible to set this value explicitely.
        '''
        return self._scale

    def width(self, value=omitted):
        '''
        width of the animation in screen coordinates

        Parameters
        ----------
        value : int
            new width |n|
            if not specified, no change


        Returns
        -------
        width of animation : int
        '''
        if value is not omitted:
            self.animation_parameters(width=value, animate=None)
        return self._width

    def height(self, value=omitted):
        '''
        height of the animation in screen coordinates

        Parameters
        ----------
        value : int
            new height |n|
            if not specified, no change

        Returns
        -------
        height of animation : int
        '''
        if value is not omitted:
            self.animation_parameters(height=value, animate=None)
        return self._height

    def background_color(self, value=omitted):
        '''
        background_color of the animation

        Parameters
        ----------
        value : colorspec
            new background_color |n|
            if not specified, no change

        Returns
        -------
        background_color of animation : colorspec
        '''
        if value is not omitted:
            self.animation_parameters(background_color=value, animate=None)
        return self._background_color    

    def foreground_color(self, value=omitted):
        '''
        foreground_color of the animation

        Parameters
        ----------
        value : colorspec
            new foreground_color |n|
            if not specified, no change

        Returns
        -------
        foreground_color of animation : colorspec
        '''
        if value is not omitted:
            self.animation_parameters(foreground_color=value, animate=None)
        return self._foreground_color    

    def animate(self, value=omitted):
        '''
        animate indicator

        Parameters
        ----------
        value : bool
            new animate indicator |n|
            if not specified, no change

        Returns
        -------
        animate status : bool

        Note
        ----
        When the run is not issued, no acction will be taken.
        '''
        if value is not omitted:
            self.animation_parameters(animate=value)
        return self._animate

    def modelname(self, value=omitted):
        '''
        modelname

        Parameters
        ----------
        value : str
            new modelname |n|
            if not specified, no change

        Returns
        -------
        modelname : str

        Note
        ----
        If modelname is the null string, nothing will be displayed.
        '''
        if value is not omitted:
            self.animation_parameters(modelname=value, animate=None)
        return self._modelname

    def video(self, value=omitted):
        '''
        video name

        Parameters
        ----------
        value : str
            new video name |n|
            if not specified, no change

        Returns
        -------
        video : str

        Note
        ----
        If video is the null string, the video (if any) will be closed.
        '''
        if value is not omitted:
            self.animation_parameters(video=value, animate=None)
        return self._video

    def fps(self, value=omitted):
        '''
        fps

        Parameters
        ----------
        value : bool
            new fps |n|
            if not specified, no change

        Returns
        -------
        fps : bool
        '''
        if value is not omitted:
            self.animation_parameters(fps=value, animate=None)
        return self._fps

    def show_time(self, value=omitted):
        '''
        show_time

        Parameters
        ----------
        value : bool
            new show_time |n|
            if not specified, no change

        Returns
        -------
        show_time : bool
        '''
        if value is not omitted:
            self.animation_parameters(show_time=value, animate=None)
        return self._show_time

    def show_fps(self, value=omitted):
        '''
        show_fps

        Parameters
        ----------
        value : bool
            new show_fps |n|
            if not specified, no change

        Returns
        -------
        show_fps : bool
        '''
        if value is not omitted:
            self.animation_parameters(show_fps=value, animate=None)
        return self._show_fps

    def synced(self, value=omitted):
        '''
        synced

        Parameters
        ----------
        value : bool
            new synced |n|
            if not specified, no change

        Returns
        -------
        synced : bool
        '''
        if value is not omitted:
            self.animation_parameters(synced=value, animate=None)
        return self._synced

    def speed(self, value=omitted):
        '''
        speed

        Parameters
        ----------
        value : float
            new speed |n|
            if not specified, no change

        Returns
        -------
        speed : float
        '''
        if value is not omitted:
            self.animation_parameters(speed=value, animate=None)
        return self._speed

    def peek(self):
        '''
        returns the time of the next component to become current |n|
        if there are no more events, peek will return inf |n|
        Only for advance use with animation / GUI event loops
        '''
        if len(self.env._pendingstandbylist) > 0:
            return self.env._now
        else:
            if self._event_list:
                return self._event_list[0][0]
            else:
                return inf

    def main(self):
        '''
        Returns
        -------
        the main component : Component
        '''
        return self._main

    def now(self):
        '''
        Returns
        -------
        the current simulation time : float
        '''
        return self._now - self.env._offset

    def reset_now(self, new_now=0):
        '''
        reset the current time

        Parameters
        ----------
        new_now : float
            now will be set not new_now |n|
            default: 0

        Note
        ----
        Internally, salabim still works with the 'old' time. Only in the interface
        from and to the user program, a correction will be applied.

        The registered time in timestamped monitors will be always is the 'old' time.
        This is only relevant when using the time value in MonitorTimestamp.xt() or
        MonitorTimestamp.tx().
        '''
        offset_before = self._offset
        self._offset = self._now - new_now

        if self._trace:
            self.print_trace(
                '', '', 'now reset to {:0.3f}'.format(new_now),
                '(all times are reduced by {:0.3f})'.format(self._offset - offset_before))

    def trace(self, value=omitted):
        '''
        trace status

        Parameters
        ----------
        value : bool
            new trace status |n|
            if omitted, no change

        Returns
        -------
        trace status : bool

        Note
        ----
        If you want to test the status, always include
        parentheses, like

            ``if env.trace():``
        '''
        if value is not omitted:
            self._trace = value
            self._buffered_trace = False
        return self._trace

    def suppress_trace_standby(self, value=omitted):
        '''
        suppress_trace_standby status

        Parameters
        ----------
        value : bool
            new suppress_trace_standby status |n|
            if omitted, no change

        Returns
        -------
        suppress trace status : bool

        Note
        ----
        By default, suppress_trace_standby is True, meaning that standby components are
        (apart from when they become non standby) suppressed from the trace. |n|
        If you set suppress_trace_standby to False, standby components are fully traced.
        '''
        if value is not omitted:
            self._suppress_trace_standby = value
            self._buffered_trace = False
        return self._suppress_trace_standby

    def current_component(self):
        '''
        Returns
        -------
        the current_component : Component
        '''
        return self._current_component

    def run(self, duration=omitted, till=omitted):
        '''
        start execution of the simulation

        Parameters
        ----------
        duration : float
            schedule with a delay of duration |n|
            if 0, now is used

        till : float
            schedule time |n|
            if omitted, inf is assumed

        Note
        ----
        only issue run() from the main level
        '''
        if till is omitted:
            if duration is omitted:
                scheduled_time = inf
            else:
                if duration == inf:
                    scheduled_time = inf
                else:
                    scheduled_time = self.env._now + duration
        else:
            if duration is omitted:
                scheduled_time = till + self.env._offset
            else:
                raise SalabimError('both duration and till specified')

        self._main.frame = _get_caller_frame()
        self._main._reschedule(scheduled_time, False, 'run')

        self.running = True
        while self.running:
            if self._animate:
                self.do_simulate_and_animate()
            else:
                self.do_simulate()
        if self.stopped:
            self.quit()

    def do_simulate(self):
        while g.in_draw:
            pass
        while self.running and not self._animate:
            self.step()

    def do_simulate_and_animate(self):
        if Pythonista:
            while self.running and self._animate:
                pass
        else:
            self.root.after(0, self.simulate_and_animate_loop)
            self.root.mainloop()

    def simulate_and_animate_loop(self):
        while True:
            tick_start = time.time()

            if self._synced or self._video:  # video forces synced
                if self._video:
                    self.t = self.video_t
                else:
                    if self.paused:
                        self.t = self.start_animation_time
                    else:
                        self.t = self.start_animation_time +\
                            ((time.time() - self.start_animation_clocktime) *
                            self._speed)

                while self.peek() < self.t:
                    self.step()
                    if not (self.running and self._animate):
                        self.root.quit()
                        return
            else:
                if self._step_pressed or (not self.paused):
                    self.step()

                    if not self._current_component._suppress_pause_at_step:
                        self._step_pressed = False
                    self.t = self._now

            if not (self.running and self._animate):
                self.root.quit()
                return

            if self._video:
                capture_image = Image.new(
                    'RGB', (self._width, self._height), self.colorspec_to_tuple('bg'))
            if not self.paused:
                self.frametimes.append(time.time())

            self.an_objects.sort(
                key=lambda obj: (-obj.layer(self.t), obj.sequence))

            canvas_objects_iter = iter(g.canvas_objects[:])
            co = next(canvas_objects_iter, None)
            self.animation_pre_tick(self.t)
            for ao in self.an_objects:
                ao.make_pil_image(self.t)

                if ao._image_visible:
                    if co is None:
                        ao.im = ImageTk.PhotoImage(ao._image)
                        co1 = g.canvas.create_image(
                            ao._image_x, self._height - ao._image_y, image=ao.im, anchor=tkinter.SW)
                        g.canvas_objects.append(co1)
                        ao.canvas_object = co1

                    else:
                        if ao.canvas_object == co:
                            if ao._image_ident != ao._image_ident_prev:
                                ao.im = ImageTk.PhotoImage(ao._image)
                                g.canvas.itemconfig(
                                    ao.canvas_object, image=ao.im)

                            if (ao._image_x != ao._image_x_prev) or (ao._image_y != ao._image_y_prev):
                                g.canvas.coords(
                                    ao.canvas_object, (ao._image_x, self._height - ao._image_y))

                        else:
                            ao.im = ImageTk.PhotoImage(ao._image)
                            ao.canvas_object = co
                            g.canvas.itemconfig(
                                ao.canvas_object, image=ao.im)
                            g.canvas.coords(
                                ao.canvas_object, (ao._image_x, self._height - ao._image_y))
                    co = next(canvas_objects_iter, None)

                    if self._video:
                        capture_image.paste(ao._image,
                            (int(ao._image_x), int(self._height - ao._image_y - ao._image.size[1])),
                            ao._image)
                else:
                    ao.canvas_object = None

            self.animation_post_tick(self.t)

            for co in canvas_objects_iter:
                g.canvas.delete(co)
                g.canvas_objects.remove(co)

            for uio in self.ui_objects:
                if not uio.installed:
                    uio.install()

            for uio in self.ui_objects:
                if uio.type == 'button':
                    thistext = uio.text()
                    if thistext != uio.lasttext:
                        uio.lasttext = thistext
                        uio.button.config(text=thistext)

            if self._video and (not self.paused):
                open_cv_image = cv2.cvtColor(
                    np.array(capture_image), cv2.COLOR_RGB2BGR)
                self._video_out.write(open_cv_image)

            g.canvas.update()
            if self._video:
                if not self.paused:
                    self.video_t += self._speed / self._fps
            else:
                if self._synced:
                    tick_duration = time.time() - tick_start
                    if tick_duration < 1 / self._fps:
                        time.sleep(((1 / self._fps) - tick_duration) * 0.8)
                        # 0.8 compensation because of clock inaccuracy

    def modelname_width(self):
        if Environment.cached_modelname_width[0] != self._modelname:
            Environment.cached_modelname_width = \
                [self._modelname, self.env.getwidth(self._modelname + ' : a ', fontsize=18)]
        return Environment.cached_modelname_width[1]

    def modelname_text(self, t):
        return self._modelname + ' : a'

    def modelname_visible(self, t):
        return self._modelname != ''

    def modelname_x_logo(self, t):
        return self.modelname_width() + 8

    def modelname_x_model(self, t):
        return self.modelname_width() + 69

    def modelname_image(self, t):
        return self.salabim_logo()

    def an_modelname(self):
        '''
        function to show the modelname |n|
        called by run(), if animation is True. |n|
        may be overridden to change the standard behaviour.
        '''

        y = -68
        an = Animate(text='',
             x0=8, y0=y,
             anchor='w', fontsize0=18,
             screen_coordinates=True, xy_anchor='nw', env=self)
        an.visible = self.modelname_visible
        an.text = self.modelname_text
        an = Animate(image=None,
             y0=y, offsety0=5,
             anchor='w', width0=61,
             screen_coordinates=True, xy_anchor='nw', env=self)
        an.visible = self.modelname_visible
        an.x = self.modelname_x_logo
        an.image = self.modelname_image
        an = Animate(text=' model',
             y0=y,
             anchor='w', fontsize0=18,
             screen_coordinates=True, xy_anchor='nw', env=self)
        an.visible = self.modelname_visible
        an.x = self.modelname_x_model

    def an_menu_buttons(self):
        '''
        function to initialize the menu buttons |n|
        may be overridden to change the standard behaviour.
        '''
        self.remove_topleft_buttons()
        if self.colorspec_to_tuple('bg')[:-1] == self.colorspec_to_tuple('blue')[:-1]:
            fillcolor = 'white'
            color = 'blue'
        else:
            fillcolor = 'blue'
            color = 'white'
        uio = AnimateButton(x=38, y=-21, text='Menu',
            width=50, action=self.env.an_menu, env=self, fillcolor=fillcolor, color=color, xy_anchor='nw')
        
        uio.in_topleft = True

    def an_unsynced_buttons(self):
        '''
        function to initialize the unsynced buttons |n|
        may be overridden to change the standard behaviour.
        '''
        self.remove_topleft_buttons()
        if self.colorspec_to_tuple('bg')[:-1] == self.colorspec_to_tuple('green')[:-1]:
            fillcolor = 'lightgreen'
            color = 'green'
        else:
            fillcolor = 'green'
            color = 'white'
        uio = AnimateButton(x=38, y=-21, text='Go',
          width=50, action=self.env.an_go, env=self, fillcolor=fillcolor, color=color, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 1 * 60, y=-21, text='Step',
          width=50, action=self.env.an_step, env=self, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 3 * 60, y=-21, text='Synced',
          width=50, action=self.env.an_synced_on, env=self, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 4 * 60, y=-21, text='Trace',
          width=50, action=self.env.an_trace, env=self, xy_anchor='nw')
        uio.in_topleft = True

        if self.colorspec_to_tuple('bg')[:-1] == self.colorspec_to_tuple('red')[:-1]:
            fillcolor = 'lightsalmon'
            color = 'white'
        else:
            fillcolor = 'red'
            color = 'white'
        uio = AnimateButton(x=38 + 5 * 60, y=-21, text='Stop',
          width=50, action=self.env.quit, env=self, fillcolor=fillcolor, color=color, xy_anchor='nw')
        uio.in_topleft = True

        uio = Animate(x0=38 + 3 * 60, y0=-35, text='',
          anchor='N', fontsize0=15,
          screen_coordinates=True, xy_anchor='nw')
        uio.text = self.syncedtext
        uio.in_topleft = True

        uio = Animate(x0=38 + 4 * 60, y0=-35, text='',
          anchor='N', fontsize0=15,
          screen_coordinates=True, xy_anchor='nw')
        uio.text = self.tracetext
        uio.in_topleft = True

    def an_synced_buttons(self):
        '''
        function to initialize the synced buttons |n|
        may be overridden to change the standard behaviour.
        '''
        self.remove_topleft_buttons()
        if self.colorspec_to_tuple('bg')[:-1] == self.colorspec_to_tuple('green')[:-1]:
            fillcolor = 'lightgreen'
            color = 'green'
        else:
            fillcolor = 'green'
            color = 'white'

        uio = AnimateButton(x=38, y=-21, text='Go',
          width=50, action=self.env.an_go, env=self, fillcolor=fillcolor, color=color, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 1 * 60, y=-21, text='/2',
          width=50, action=self.env.an_half, env=self, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 2 * 60, y=-21, text='*2',
          width=50, action=self.env.an_double, env=self, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 3 * 60, y=-21, text='Synced',
          width=50, action=self.env.an_synced_off, env=self, xy_anchor='nw')
        uio.in_topleft = True

        uio = AnimateButton(x=38 + 4 * 60, y=-21, text='Trace',
          width=50, action=self.env.an_trace, env=self, xy_anchor='nw')
        uio.in_topleft = True

        if self.colorspec_to_tuple('bg') == self.colorspec_to_tuple('red'):
            fillcolor = 'lightsalmon'
            color = 'white'
        else:
            fillcolor = 'red'
            color = 'white'
        uio = AnimateButton(x=38 + 5 * 60, y=-21, text='Stop',
          width=50, action=self.env.an_quit, env=self, fillcolor=fillcolor, color=color, xy_anchor='nw')
        uio.in_topleft = True

        uio = Animate(x0=38 + 1.5 * 60, y0=-35, text='',
          textcolor0='fg', anchor='N', fontsize0=15,
          screen_coordinates=True, xy_anchor='nw')
        uio.text = self.speedtext
        uio.in_topleft = True

        uio = Animate(x0=38 + 3 * 60, y0=-35, text='',
          anchor='N', fontsize0=15,
          screen_coordinates=True, xy_anchor='nw')
        uio.text = self.syncedtext
        uio.in_topleft = True

        uio = Animate(x0=38 + 4 * 60, y0=-35, text='',
          anchor='N', fontsize0=15,
          screen_coordinates=True, xy_anchor='nw')
        uio.text = self.tracetext
        uio.in_topleft = True

    def remove_topleft_buttons(self):
        for uio in self.ui_objects[:]:
            if getattr(uio, 'in_topleft', False):
                uio.remove()

        for ao in self.an_objects[:]:
            if getattr(ao, 'in_topleft', False):
                ao.remove()

    def an_clocktext(self):
        '''
        function to initialize the system clocktext |n|
        called by run(), if animation is True. |n|
        may be overridden to change the standard behaviour.
        '''
        ao = Animate(x0=0, y0=-5, textcolor0='fg',
            text='', fontsize0=15, font='narrow', anchor='ne',
            screen_coordinates=True, xy_anchor='ne', env=self)
        ao.text = self.clocktext

    def an_half(self):
        self._speed /= 2

    def an_double(self):
        self._speed *= 2

    def an_go(self):
        self.paused = False
        if self._synced:
            self.set_start_animation()
        else:
            self._step_pressed = True  # force to next event
        self.an_menu_buttons()

    def an_quit(self):
        self._animate = False
        self.running = False
        self.stopped = True
        self.quit()

    def quit(self):
        if g.animation_env is not None:
            g.animation_env.animation_parameters(animate=False, video='')  # stop animation
        if Pythonista:
            if g.animation_scene is not None:
                g.animation_scene.view.close()
        quit()

    def an_trace(self):
        self._trace = not self._trace

    def an_synced_on(self):
        self._synced = True
        self.an_synced_buttons()

    def an_synced_off(self):
        self._synced = False
        self.an_unsynced_buttons()

    def an_step(self):
        self._step_pressed = True

    def an_menu(self):
        self.paused = True
        self.set_start_animation()
        if self._synced:
            self.an_synced_buttons()
        else:
            self.an_unsynced_buttons()

    def clocktext(self, t):
        s = ''
        if self._synced and (not self.paused) and self._show_fps:
            if len(self.frametimes) >= 2:
                fps = (len(self.frametimes) - 1) / \
                    (self.frametimes[-1] - self.frametimes[0])
            else:
                fps = 0
            s += 'fps={:.1f}'.format(fps)

        if self._show_time:
            if s != '':
                s += ' '
            s += 't={:.3f}'.format(t - self.env._offset)
        return s

    def tracetext(self, t):
        if self._trace:
            return '= on'
        else:
            return '= off'

    def syncedtext(self, t):
        if self._synced:
            return '= on'
        else:
            return '= off'

    def speedtext(self, t):
        return 'speed = {:.3f}'.format(self._speed)

    def set_start_animation(self):
        self.frametimes = collections.deque(maxlen=30)
        self.start_animation_time = self.t
        self.start_animation_clocktime = time.time()

    def xy_anchor_to_x(self, xy_anchor, screen_coordinates):
        if xy_anchor in ('nw', 'w', 'sw'):
            if screen_coordinates:
                return 0
            else:
                return self._x0

        if xy_anchor in ('n', 'c', 'center', 's'):
            if screen_coordinates:
                return self._width / 2
            else:
                return (self._x0 + self._x1) / 2

        if xy_anchor in ('ne', 'e', 'se', ''):
            if screen_coordinates:
                return self._width
            else:
                return self._x1

        raise SalabimError('incorrect xy_anchor', xy_anchor)

    def xy_anchor_to_y(self, xy_anchor, screen_coordinates):
        if xy_anchor in ('nw', 'n', 'ne'):
            if screen_coordinates:
                return self._height
            else:
                return self._y1

        if xy_anchor in ('w', 'c', 'center', 'e'):
            if screen_coordinates:
                return self._height / 2
            else:
                return (self._y0 + self._y1) / 2

        if xy_anchor in ('sw', 's', 'se', ''):
            if screen_coordinates:
                return 0
            else:
                return self._y0

        raise SalabimError('incorrect xy_anchor', xy_anchor)

    def salabim_logo(self):
        if self.is_dark('bg'):
            return salabim_logo_red_white_200()
        else:
            return salabim_logo_red_black_200()

    def colorspec_to_tuple(self, colorspec):
        '''
        translates a colorspec to a tuple

        Parameters
        ----------
        colorspec: tuple, list or str
            ``#rrggbb`` ==> alpha = 255 (rr, gg, bb in hex) |n|
            ``#rrggbbaa`` ==> alpha = aa (rr, gg, bb, aa in hex) |n|
            ``colorname`` ==> alpha = 255 |n|
            ``(colorname, alpha)`` |n|
            ``(r, g, b)`` ==> alpha = 255 |n|
            ``(r, g, b, alpha)`` |n|
            ``'fg'`` ==> foreground_color |n|
            ``'bg'`` ==> background_color

        Returns
        -------
        (r, g, b, a)
        '''
        if colorspec == 'fg':
            colorspec = self.colorspec_to_tuple(self._foreground_color)
        elif colorspec == 'bg':
            colorspec = self.colorspec_to_tuple(self._background_color)
        if isinstance(colorspec, (tuple, list)):
            if len(colorspec) == 2:
                c = self.colorspec_to_tuple(colorspec[0])
                return (c[0], c[1], c[2], colorspec[1])
            elif len(colorspec) == 3:
                return (colorspec[0], colorspec[1], colorspec[2], 255)
            elif len(colorspec) == 4:
                return colorspec
        else:
            if (colorspec != '') and (colorspec[0]) == '#':
                if len(colorspec) == 7:
                    return (int(colorspec[1:3], 16), int(colorspec[3:5], 16),
                            int(colorspec[5:7], 16), 255)
                elif len(colorspec) == 9:
                    return (int(colorspec[1:3], 16), int(colorspec[3:5], 16),
                            int(colorspec[5:7], 16), int(colorspec[7:9], 16))
            else:
                s = colorspec.split('#')
                if len(s) == 2:
                    alpha = s[1]
                    colorspec = s[0]
                else:
                    alpha = 'FF'
                try:
                    colorhex = colornames()[colorspec.replace(' ', '').lower()]
                    if len(colorhex) == 7:
                        colorhex = colorhex + alpha
                    return self.colorspec_to_tuple(colorhex)
                except:
                    pass

        raise SalabimError('wrong color specification: ' + str(colorspec))

    def colorinterpolate(self, t, t0, t1, v0, v1):
        '''
        does linear interpolation of colorspecs

        Parameters
        ----------
        t : float
            value to be interpolated from

        t0: float
            f(t0)=v0

        t1: float
            f(t1)=v1

        v0: colorspec
            f(t0)=v0

        v1: colorspec
            f(t1)=v1

        Returns
        -------
        linear interpolation between v0 and v1 based on t between t0 and t : colorspec

        Note
        ----
        Note that no extrapolation is done, so if t<t0 ==> v0  and t>t1 ==> v1 |n|
        This function is heavily used during animation
        '''
        if v0 == v1:
            return v0
        if t1 == inf:
            return v0
        if t0 == t1:
            return v1
        vt0 = self.colorspec_to_tuple(v0)
        vt1 = self.colorspec_to_tuple(v1)
        return tuple(int(c) for c in interpolate(t, t0, t1, vt0, vt1))

    def colorspec_to_hex(self, colorspec, withalpha=True):
        v = self.colorspec_to_tuple(colorspec)
        if withalpha:
            return '#{:02x}{:02x}{:02x}{:02x}'.\
                format(int(v[0]), int(v[1]), int(v[2]), int(v[3]))
        else:
            return '#{:02x}{:02x}{:02x}'.\
                format(int(v[0]), int(v[1]), int(v[2]))

    def pythonistacolor(self, colorspec):
        c = self.colorspec_to_tuple(colorspec)
        return (c[0] / 255, c[1] / 255, c[2] / 255, c[3] / 255)

    def is_dark(self, colorspec):
        '''
        Arguments
        ---------
        colorspec : colorspec
            color to check

        Returns
        -------
        : bool
            True, if the colorspec is dark (rather black than white) |n|
            False, if the colorspec is light (rather white than black |n|
            if colorspec has alpha=0 (total transparent), the background_color will be tested
        '''
        rgba = self.colorspec_to_tuple(colorspec)
        if rgba[3] == 0:
            return self.is_dark(self.colorspec_to_tuple(('bg', 255)))
        luma = ((0.299 * rgba[0]) + (0.587 * rgba[1]) + (0.114 * rgba[2])) / 255
        if luma > 0.5:
            return False
        else:
            return True

    def getwidth(self, text, font='', fontsize=20, screen_coordinates=False):
        if not screen_coordinates:
            fontsize = fontsize * self._scale
        f, heightA = getfont(font, fontsize)
        if text == '':  # necessary because of bug in PIL >= 4.2.1
            thiswidth, thisheight = (0, 0)
        else:
            thiswidth, thisheight = f.getsize(text)
        if screen_coordinates:
            return thiswidth
        else:
            return thiswidth / self._scale

    def getfontsize_to_fit(self, text, width, font='', screen_coordinates=False):
        if not screen_coordinates:
            width = width * self._scale

        lastwidth = 0
        for fontsize in range(1, 300):
            f = self.getfont(font, fontsize)
            thiswidth, thisheight = f.getsize(text)
            if thiswidth > width:
                break
            lastwidth = thiswidth
        fontsize = interpolate(
            width, lastwidth, thiswidth, fontsize - 1, fontsize)
        if screen_coordinates:
            return fontsize
        else:
            return fontsize / self._scale

    def name(self):
        '''
        Returns
        -------
        Name of the environment : str
        '''
        return self._name

    def base_name(self):
        '''
        returns the base name of the environment (the name used at initialization)
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the environment : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def print_trace_header(self):
        '''
        print a (two line) header line as a legend |n|
        also the legend for line numbers will be printed |n|
        not that the header is only printed if trace=True
        '''
        self.print_trace('      time', 'current component', 'action', 'information', 'line#')
        self.print_trace(10 * '-', 20 * '-', 35 * '-', 48 * '-', 6 * '-')
        for ref in range(len(self._source_files)):
            for fullfilename, iref in self._source_files.items():
                if ref == iref:
                    self._print_legend(iref)

    def _print_legend(self, ref):
        if ref:
            s = 'line numbers prefixed by ' + chr(ord('A') + ref - 1) + ' refer to'
        else:
            s = 'line numbers refers to'
        for fullfilename, iref in self._source_files.items():
            if ref == iref:
                self.print_trace('', '', s, os.path.basename(fullfilename), '')
                break

    def _frame_to_lineno(self, frame):
        frameinfo = inspect.getframeinfo(frame)
        ref = self._source_files.get(frameinfo.filename)
        new_entry = False
        if ref is None:
            if self._source_files:
                ref = len(self._source_files)
            self._source_files[frameinfo.filename] = ref
            new_entry = True
        if ref == 0:
            pre = ''
        else:
            pre = chr(ref + ord('A') - 1)
        if new_entry:
            self._print_legend(ref)
        return rpad(pre + str(frameinfo.lineno), 5)

    def print_trace(self, s1='', s2='', s3='', s4='', s0=omitted, _optional=False):
        '''
        prints a trace line

        Parameters
        ----------
        s1 : str
            part 1 (usually formatted  now), padded to 10 characters

        s2 : str
            part 2 (usually only used for the compoent that gets current), padded to 20 characters

        s3 : str
            part 3, padded to 35 characters

        s4 : str
            part 4

        s0 : str
            part 0. If omitted, the line number from where the call was given will be used at
            the start of the line. Otherwise s0, left padded to 7 characters will be used at
            the start of the line.

        _optional : bool
            for internal use only. Do not set this flag!

        Note
        ----
        if self.trace is False, nothing is printed |n|
        if the current component's suppress_trace is True, nothing is printed |n|

        '''
        if self._trace:
            if not (hasattr(self, '_current_component') and self._current_component._suppress_trace):
                if s0 is omitted:
                    stack = inspect.stack()
                    filename0 = inspect.getframeinfo(stack[0][0]).filename
                    for i in range(len(inspect.stack())):
                        frame = stack[i][0]
                        if filename0 != inspect.getframeinfo(frame).filename:
                            break

                    s0 = self._frame_to_lineno(_get_caller_frame())
                line = pad(s0, 7) + pad(s1, 10) + ' ' + pad(s2, 20) + ' ' + \
                    pad(s3, max(len(s3), 36)) + ' ' + s4.strip()
                if _optional:
                    self._buffered_trace = line
                else:
                    if self._buffered_trace:
                        print(self._buffered_trace)
                        logging.debug(self.buffered_trace)
                        self._buffered_trace = False
                    print(line)
                    logging.debug(line)

    def beep(self):
        '''
        Beeps

        Works only on Windows and iOS (Pythonista). For other platforms this is just a dummy method.
        '''
        if Windows:
            try:
                import winsound
                winsound.PlaySound(
                    os.environ['WINDIR'] + r'\media\Windows Ding.wav', winsound.SND_FILENAME | winsound.SND_ASYNC)
            except:
                pass

        elif Pythonista:
            try:
                import sound
                sound.stop_all_effects()
                sound.play_effect('game:Beep', pitch=0.3)
            except:
                pass


class Animate(object):
    '''
    defines an animation object

    Parameters
    ----------
    parent : Component
        component where this animation object belongs to (default None) |n|
        if given, the animation object will be removed
        automatically upon termination of the parent

    layer : int
         layer value |n|
         lower layer values are on top of higher layer values (default 0)

    keep : bool
        keep |n|
        if False, animation object is hidden after t1, shown otherwise
        (default True)

    visible : bool
        visible |n|
        if False, animation object is not shown, shown otherwise
        (default True)

    screen_coordinates : bool
        use screen_coordinates |n|
        normally, the scale parameters are use for positioning and scaling
        objects. |n|
        if True, screen_coordinates will be used instead.

    xy_anchor : str
        specifies where x and y (i.e. x0, y0, x1 and y1) are relative to |n|
        possible values are (default: sw) |n|:
        ``nw    n    ne`` |n|
        ``w     c     e`` |n|
        ``sw    s    se`` |n|
        If '', the given coordimates are used untranslated

    t0 : float
        time of start of the animation (default: now)

    x0 : float
        x-coordinate of the origin (default 0) at time t0

    y0 : float
        y-coordinate of the origin (default 0) at time t0

    offsetx0 : float
        offsets the x-coordinate of the object (default 0) at time t0

    offsety0 : float
        offsets the y-coordinate of the object (default 0) at time t0

    circle0 : tuple
         the radius of the circle at time t0

    line0 : tuple
        the line(s) (xa,ya,xb,yb,xc,yc, ...) at time t0

    polygon0 : tuple
        the polygon (xa,ya,xb,yb,xc,yc, ...) at time t0 |n|
        the last point will be auto connected to the start

    rectangle0 : tuple
        the rectangle (xlowerleft,ylowerleft,xupperright,yupperright) at time t0 |n|

    image : str or PIL image
        the image to be displayed |n|
        This may be either a filename or a PIL image

    text : str
        the text to be displayed

    font : str or list/tuple
        font to be used for texts |n|
        Either a string or a list/tuple of fontnames.
        If not found, uses calibri or arial

    anchor : str
        anchor position |n|
        specifies where to put images or texts relative to the anchor
        point |n|
        possible values are (default: c) |n|:
        ``nw    n    ne`` |n|
        ``w     c     e`` |n|
        ``sw    s    se``

    linewidth0 : float
        linewidth of the contour at time t0 (default 0 for polygon, rectangle and circle, 1 for line)

    fillcolor0 : colorspec
        color of interior at time t0 (default foreground_color)

    linecolor0 : colorspec
        color of the contour at time t0 (default foreground_color)

    textcolor0 : colorspec
        color of the text at time 0 (default foreground_color)

    angle0 : float
        angle of the polygon at time t0 (in degrees) (default 0)

    fontsize0 : float
        fontsize of text at time t0 (default 20)

    width0 : float
       width of the image to be displayed at time t0 |n|
       if omitted or None, no scaling

    t1 : float
        time of end of the animation (default inf) |n|
        if keep=True, the animation will continue (frozen) after t1

    x1 : float
        x-coordinate of the origin at time t1(default x0)

    y1 : float
        y-coordinate of the origin at time t1 (default y0)

    offsetx1 : float
        offsets the x-coordinate of the object at time t1 (default offsetx0)

    offsety1 : float
        offsets the y-coordinate of the object at time t1 (default offsety0)

    circle1 : tuple
         the radius of the circle at time t1 (default circle0)

    line1 : tuple
        the line(s) at time t1 (xa,ya,xb,yb,xc,yc, ...) (default: line0) |n|
        should have the same number of elements as line0

    polygon1 : tuple
        the polygon at time t1 (xa,ya,xb,yb,xc,yc, ...) (default: polygon0) |n|
        should have the same number of elements as polygon0

    rectangle1 : tuple
        the rectangle (xlowerleft,ylowerleft,xupperright,yupperright) at time t1
        (default: rectangle0)

    linewidth1 : float
        linewidth of the contour at time t1 (default linewidth0)

    fillcolor1 : colorspec
        color of interior at time t1 (default fillcolor0)

    linecolor1 : colorspec
        color of the contour at time t1 (default linecolor0)

    textcolor1 : colorspec
        color of text at time t1 (default textcolor0)

    angle1 : float
        angle of the polygon at time t1 (in degrees) (default angle0)

    fontsize1 : float
        fontsize of text at time t1 (default: fontsize0)

    width1 : float
       width of the image to be displayed at time t1 (default: width0) |n|

    Note
    ----
    one (and only one) of the following parameters is required:
         - circle0
         - image
         - line0
         - polygon0
         - rectangle0
         - text

    colors may be specified as a
        - valid colorname
        - hexname
        - tuple (R,G,B) or (R,G,B,A)
        - 'fg' or 'bg'

    colornames may contain an additional alpha, like ``red#7f`` |n|
    hexnames may be either 3 of 4 bytes long (``#rrggbb`` or ``#rrggbbaa``) |n|
    both colornames and hexnames may be given as a tuple with an
    additional alpha between 0 and 255,
    e.g. ``(255,0,255,128)``, ('red',127)`` or ``('#ff00ff',128)`` |n|
    fg is the foreground color |n|
    bg is the background color |n|

    Permitted parameters

    ======================  ========= ========= ========= ========= ========= =========
    parameter               circle    image     line      polygon   rectangle text
    ======================  ========= ========= ========= ========= ========= =========
    parent                  -         -         -         -         -         -
    layer                   -         -         -         -         -         -
    keep                    -         -         -         -         -         -
    screen_coordinates      -         -         -         -         -         -
    xy_anchor               -         -         -         -         -         -
    t0,t1                   -         -         -         -         -         -
    x0,x1                   -         -         -         -         -         -
    y0,y1                   -         -         -         -         -         -
    offsetx0,offsetx1       -         -         -         -         -         -
    offsety0,offsety1       -         -         -         -         -         -
    circle0,circle1         -
    image                             -
    line0,line1                                 -
    polygon0,polygon1                                     -
    rectangle0,rectangle1                                           -
    text                                                                      -
    font
    anchor                            -                                       -
    linewidth0,linewidth1    -                  -         -         -
    fillcolor0,fillcolor1    -                            -         -
    linecolor0,linecolor1    -                  -         -         -
    textcolor0,textcolor1.                                                    -
    angle0,angle1                     -         -         -         -         -
    font                                                                      -
    fontsize0,fontsize1                                                       -
    width0,width1                     -
    ======================  ========= ========= ========= ========= ========= =========
    '''

    def __init__(self, parent=omitted, layer=0, keep=True, visible=True,
                 screen_coordinates=False,
                 t0=omitted, x0=0, y0=0, offsetx0=0, offsety0=0,
                 circle0=omitted, line0=omitted, polygon0=omitted, rectangle0=omitted,
                 image=omitted, text=omitted,
                 font='', anchor='c',
                 linewidth0=omitted, fillcolor0='fg', linecolor0='fg', textcolor0='fg',
                 angle0=0, fontsize0=20, width0=omitted,
                 t1=omitted, x1=omitted, y1=omitted, offsetx1=omitted, offsety1=omitted,
                 circle1=omitted, line1=omitted, polygon1=omitted, rectangle1=omitted,
                 linewidth1=omitted, fillcolor1=omitted, linecolor1=omitted, textcolor1=omitted,
                 angle1=omitted, fontsize1=omitted, width1=omitted, xy_anchor='', env=omitted):

        self.env = g.default_env if env is omitted else env
        self._image_ident = None  # denotes no image yet
        self._image = None
        self._image_x = 0
        self._image_y = 0
        self.canvas_object = None

        self.type = self.settype(
            circle0, line0, polygon0, rectangle0, image, text)
        if self.type == '':
            raise SalabimError('no object specified')
        type1 = self.settype(circle1, line1, polygon1, rectangle1, omitted, omitted)
        if (type1 != '') and (type1 != self.type):
            raise SalabimError('incompatible types: ' +
                self.type + ' and ' + type1)

        self.layer0 = layer
        self.parent = (None if parent is omitted else parent)
        self.keep = keep
        self.visible0 = visible
        self.screen_coordinates = screen_coordinates
        self.sequence = self.env.serialize()

        self.circle0 = circle0
        self.line0 = de_none(line0)
        self.polygon0 = de_none(polygon0)
        self.rectangle0 = de_none(rectangle0)
        self.text0 = text

        if image is omitted:
            self.width0 = 0  # just to be able to interpolate
        else:
            self.image0 = spec_to_image(image)
            self.width0 = None if width0 is omitted else width0  # None means original size

        self.font0 = font
        self.anchor0 = anchor
        self.xy_anchor0 = xy_anchor

        self.x0 = x0
        self.y0 = y0
        self.offsetx0 = offsetx0
        self.offsety0 = offsety0

        self.fillcolor0 = fillcolor0
        self.linecolor0 = linecolor0
        self.textcolor0 = textcolor0
        if linewidth0 is omitted:
            if self.type == 'line':
                self.linewidth0 = 1
            else:
                self.linewidth0 = 0
        else:
            self.linewidth0 = linewidth0
        self.angle0 = angle0
        self.fontsize0 = fontsize0

        self.t0 = self.env._now if t0 is omitted else t0

        self.circle1 = self.circle0 if circle1 is omitted else circle1
        self.line1 = self.line0 if line1 is omitted else de_none(line1)
        self.polygon1 = self.polygon0 if polygon1 is omitted else de_none(polygon1)
        self.rectangle1 = self.rectangle0 if rectangle1 is omitted else de_none(rectangle1)

        self.x1 = self.x0 if x1 is omitted else x1
        self.y1 = self.y0 if y1 is omitted else y1
        self.offsetx1 = self.offsetx0 if offsetx1 is omitted else offsetx1
        self.offsety1 = self.offsety0 if offsety1 is omitted else offsety1
        self.fillcolor1 =\
            self.fillcolor0 if fillcolor1 is omitted else fillcolor1
        self.linecolor1 =\
            self.linecolor0 if linecolor1 is omitted else linecolor1
        self.textcolor1 =\
            self.textcolor0 if textcolor1 is omitted else textcolor1
        self.linewidth1 =\
            self.linewidth0 if linewidth1 is omitted else linewidth1
        self.angle1 = self.angle0 if angle1 is omitted else angle1
        self.fontsize1 =\
            self.fontsize0 if fontsize1 is omitted else fontsize1
        self.width1 = self.width0 if width1 is omitted else width1

        self.t1 = inf if t1 is omitted else t1

        self.env.an_objects.append(self)

    def update(self, layer=omitted, keep=omitted, visible=omitted,
               t0=omitted, x0=omitted, y0=omitted, offsetx0=omitted, offsety0=omitted,
               circle0=omitted, line0=omitted, polygon0=omitted, rectangle0=omitted,
               image=omitted, text=omitted, font=omitted, anchor=omitted,
               linewidth0=omitted, fillcolor0=omitted, linecolor0=omitted, textcolor0=omitted,
               angle0=omitted, fontsize0=omitted, width0=omitted,
               t1=omitted, x1=omitted, y1=omitted, offsetx1=omitted, offsety1=omitted,
               circle1=omitted, line1=omitted, polygon1=omitted, rectangle1=omitted,
               linewidth1=omitted, fillcolor1=omitted, linecolor1=omitted, textcolor1=omitted,
               angle1=omitted, fontsize1=omitted, width1=omitted, xy_anchor=omitted):
        '''
        updates an animation object

        Parameters
        ----------
        layer : int
            layer value |n|
            lower layer values are on top of higher layer values (default see below)

        keep : bool
            keep |n|
            if False, animation object is hidden after t1, shown otherwise
            (default see below)

        visible : bool
            visible |n|
            if False, animation object is not shown, shown otherwise
            (default see below)

        t0 : float
            time of start of the animation (default: now)

        x0 : float
            x-coordinate of the origin at time t0 (default see below)

        y0 : float
            y-coordinate of the origin at time t0 (default see below)

        offsetx0 : float
            offsets the x-coordinate of the object at time t0 (default see below)

        offsety0 : float
            offsets the y-coordinate of the object at time t0 (default see below)

        circle0 : tuple
            the radius of the circle at time t0 (default see below)

        line0 : tuple
            the line(s) at time t0 (xa,ya,xb,yb,xc,yc, ...) (default see below)

        polygon0 : tuple
            the polygon at time t0 (xa,ya,xb,yb,xc,yc, ...) |n|
            the last point will be auto connected to the start (default see below)

        rectangle0 : tuple
            the rectangle at time t0 |n|
            (xlowerleft,ylowerlef,xupperright,yupperright) (default see below)

        image : str or PIL image
            the image to be displayed |n|
            This may be either a filename or a PIL image (default see below)

        text : str
            the text to be displayed (default see below)

        font : str or list/tuple
            font to be used for texts |n|
            Either a string or a list/tuple of fontnames. (default see below)
            If not found, uses calibri or arial

        anchor : str
            anchor position |n|
            specifies where to put images or texts relative to the anchor
            point (default see below) |n|
            possible values are (default: c) |n|:
            ``nw    n    ne`` |n|
            ``w     c     e`` |n|
            ``sw    s    se``

        linewidth0 : float
            linewidth of the contour at time t0 (default see below)

        fillcolor0 : colorspec
            color of interior/text at time t0 (default see below)

        linecolor0 : colorspec
            color of the contour at time t0 (default see below)

        angle0 : float
            angle of the polygon at time t0 (in degrees) (default see below)

        fontsize0 : float
            fontsize of text at time t0 (default see below)

        width0 : float
            width of the image to be displayed at time t0 (default see below) |n|
            if None, the original width of the image will be used

        t1 : float
            time of end of the animation (default: inf) |n|
            if keep=True, the animation will continue (frozen) after t1

        x1 : float
            x-coordinate of the origin at time t1 (default x0)

        y1 : float
            y-coordinate of the origin at time t1 (default y0)

        offsetx1 : float
            offsets the x-coordinate of the object at time t1 (default offsetx0)

        offsety1 : float
            offsets the y-coordinate of the object at time t1 (default offset0)

        circle1: tuple
             the radius of the circle at time t1 (default circle0)

        line1 : tuple
            the line(s) at time t1 (xa,ya,xb,yb,xc,yc, ...) (default: line0) |n|
            should have the same number of elements as line0

        polygon1 : tuple
            the polygon at time t1 (xa,ya,xb,yb,xc,yc, ...) (default: polygon0) |n|
            should have the same number of elements as polygon0

        rectangle1 : tuple
            the rectangle at time t (xlowerleft,ylowerleft,xupperright,yupperright)
            (default: rectangle0) |n|

        linewidth1 : float
            linewidth of the contour at time t1 (default linewidth0)

        fillcolor1 : colorspec
            color of interior/text at time t1 (default fillcolor0)

        linecolor1 : colorspec
            color of the contour at time t1 (default linecolor0)

        angle1 : float
            angle of the polygon at time t1 (in degrees) (default angle0)

        fontsize1 : float
            fontsize of text at time t1 (default: fontsize0)

        width1 : float
           width of the image to be displayed at time t1 (default: width0) |n|

        Note
        ----
        The type of the animation cannot be changed with this method. |n|
        The default value of most of the parameters is the current value (at time now)
        '''

        t = self.env._now
        type0 = self.settype(circle0, line0, polygon0, rectangle0, image, text)
        if (type0 != '') and (type0 != self.type):
            raise SalabimError('incorrect type ' +
                type0 + ' (should be ' + self.type)
        type1 = self.settype(circle1, line1, polygon1, rectangle1, omitted, omitted)
        if (type1 != '') and (type1 != self.type):
            raise SalabimError('incompatible types: ' +
                self.type + ' and ' + type1)

        if layer is not omitted:
            self.layer0 = layer
        if keep is not omitted:
            self.keep = keep
        if visible is not omitted:
            self.visible0 = visible
        self.circle0 = self.circle() if circle0 is omitted else circle0
        self.line0 = self.line() if line0 is omitted else de_none(line0)
        self.polygon0 = self.polygon() if polygon0 is omitted else de_none(polygon0)
        self.rectangle0 =\
            self.rectangle() if rectangle0 is omitted else de_none(rectangle0)
        if text is not omitted:
            self.text0 = text
        self.width0 = self.width() if width0 is omitted else width0
        if image is not omitted:
            self.image0 = spec_to_image(image)
            self.width0 = self.image0.size[0] if width0 is omitted else width0

        if font is not omitted:
            self.font0 = font
        if anchor is not omitted:
            self.anchor0 = anchor

        self.x0 = self.x(t) if x0 is omitted else x0
        self.y0 = self.y(t) if y0 is omitted else y0
        self.offsetx0 = self.offsetx(t) if offsetx0 is omitted else offsetx0
        self.offsety0 = self.offsety(t) if offsety0 is omitted else offsety0

        self.fillcolor0 =\
            self.fillcolor(t) if fillcolor0 is omitted else fillcolor0
        self.linecolor0 =\
            self.linecolor(t) if linecolor0 is omitted else linecolor0
        self.textcolor0 =\
            self.textcolor(t) if textcolor0 is omitted else textcolor0
        self.linewidth0 =\
            self.linewidth(t) if linewidth0 is omitted else linewidth0
        self.angle0 = self.angle(t) if angle0 is omitted else angle0
        self.fontsize0 = self.fontsize(t) if fontsize0 is omitted else fontsize0
        self.t0 = self.env._now if t0 is omitted else t0

        self.circle1 = self.circle0 if circle1 is omitted else circle1
        self.line1 = self.line0 if line1 is omitted else de_none(line1)
        self.polygon1 = self.polygon0 if polygon1 is omitted else de_none(polygon1)
        self.rectangle1 =\
            self.rectangle0 if rectangle1 is omitted else de_none(rectangle1)

        self.x1 = self.x0 if x1 is omitted else x1
        self.y1 = self.y0 if y1 is omitted else y1
        self.offsetx1 = self.offsetx0 if offsetx1 is omitted else offsetx1
        self.offsety1 = self.offsety0 if offsety1 is omitted else offsety1
        self.fillcolor1 =\
            self.fillcolor0 if fillcolor1 is omitted else fillcolor1
        self.linecolor1 =\
            self.linecolor0 if linecolor1 is omitted else linecolor1
        self.textcolor1 =\
            self.textcolor0 if textcolor1 is omitted else textcolor1
        self.linewidth1 =\
            self.linewidth0 if linewidth1 is omitted else linewidth1
        self.angle1 = self.angle0 if angle1 is omitted else angle1
        self.fontsize1 =\
            self.fontsize0 if fontsize1 is omitted else fontsize1
        self.width1 = self.width0 if width1 is omitted else width1

        self.t1 = inf if t1 is omitted else t1
        if self not in self.env.an_objects:
            self.env.an_objects.append(self)

    def remove(self):
        '''
        removes the animation object from the animation queue,
        so effectively ending this animation.

        Note
        ----
        The animation object might be still updated, if required
        '''
        if self in self.env.ui_objects:
            self.env.ui_objects.remove(self)
        if self in self.env.an_objects:
            self.env.an_objects.remove(self)

    def x(self, t=omitted):
        '''
        x-position of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        x : float
            default behaviour: linear interpolation between self.x0 and self.x1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.x0, self.x1)

    def y(self, t=omitted):
        '''
        y-position of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        y : float
            default behaviour: linear interpolation between self.y0 and self.y1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.y0, self.y1)

    def offsetx(self, t=omitted):
        '''
        offsetx of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        offsetx : float
            default behaviour: linear interpolation between self.offsetx0 and self.offsetx1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.offsetx0, self.offsetx1)

    def offsety(self, t=omitted):
        '''
        offsety of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        offsety : float
            default behaviour: linear interpolation between self.offsety0 and self.offsety1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.offsety0, self.offsety1)

    def angle(self, t=omitted):
        '''
        angle of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        angle : float
            default behaviour: linear interpolation between self.angle0 and self.angle1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.angle0, self.angle1)

    def linewidth(self, t=omitted):
        '''
        linewidth of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        linewidth : float
            default behaviour: linear interpolation between self.linewidth0 and self.linewidth1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.linewidth0, self.linewidth1)

    def linecolor(self, t=omitted):
        '''
        linecolor of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        linecolor : colorspec
            default behaviour: linear interpolation between self.linecolor0 and self.linecolor1
        '''
        return self.env.colorinterpolate((self.env._now if t is omitted else t),
                                self.t0, self.t1, self.linecolor0, self.linecolor1)

    def fillcolor(self, t=omitted):
        '''
        fillcolor of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        fillcolor : colorspec
            default behaviour: linear interpolation between self.fillcolor0 and self.fillcolor1
        '''
        return self.env.colorinterpolate((self.env._now if t is omitted else t),
                                self.t0, self.t1, self.fillcolor0, self.fillcolor1)

    def circle(self, t=omitted):
        '''
        circle of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        circle : float or tuple
            radius or one element tuple with the radius |n|
            default behaviour: linear interpolation between self.circle0 and self.circle1
        '''

        return interpolate((self.env._now if t is omitted else t),
           self.t0, self.t1,
           self.circle0[0] if isinstance(self.circle0, (list, tuple)) else self.circle0,
           self.circle1[0] if isinstance(self.circle1, (list, tuple)) else self.circle1)

    def textcolor(self, t=omitted):
        '''
        textcolor of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        textcolor : colorspec
            default behaviour: linear interpolation between self.textcolor0 and self.textcolor1
        '''
        return self.env.colorinterpolate((self.env._now if t is omitted else t),
                                self.t0, self.t1, self.textcolor0, self.textcolor1)

    def line(self, t=omitted):
        '''
        line of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        line : tuple
            series of x- and y-coordinates (xa,ya,xb,yb,xc,yc, ...) |n|
            default behaviour: linear interpolation between self.line0 and self.line1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.line0, self.line1)

    def polygon(self, t=omitted):
        '''
        polygon of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        polygon: tuple
            series of x- and y-coordinates describing the polygon (xa,ya,xb,yb,xc,yc, ...) |n|
            default behaviour: linear interpolation between self.polygon0 and self.polygon1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.polygon0, self.polygon1)

    def rectangle(self, t=omitted):
        '''
        rectangle of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        rectangle: tuple
            (xlowerleft,ylowerlef,xupperright,yupperright) |n|
            default behaviour: linear interpolation between self.rectangle0 and self.rectangle1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.rectangle0, self.rectangle1)

    def width(self, t=omitted):
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.width0, self.width1)
        '''
        width position of an animated image object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        width : float
            default behaviour: linear interpolation between self.width0 and self.width1 |n|
            if None, the original width of the image will be used
        '''

    def fontsize(self, t=omitted):
        '''
        fontsize of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        fontsize : float
            default behaviour: linear interpolation between self.fontsize0 and self.fontsize1
        '''
        return interpolate((self.env._now if t is omitted else t),
                           self.t0, self.t1, self.fontsize0, self.fontsize1)

    def text(self, t=omitted):
        '''
        text of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        text : str
            default behaviour: self.text0 (text given at creation or update)
        '''
        return self.text0

    def anchor(self, t=omitted):
        '''
        anchor of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        anchor : str
            default behaviour: self.anchor0 (anchor given at creation or update)
        '''

        return self.anchor0

    def layer(self, t=omitted):
        '''
        layer of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        layer : int or float
            default behaviour: self.layer0 (layer given at creation or update)
        '''
        return self.layer0

    def font(self, t=omitted):
        '''
        font of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        font : str
            default behaviour: self.font0 (font given at creation or update)
        '''
        return self.font0

    def xy_anchor(self, t=omitted):
        '''
        xy_anchor attribute of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        xy_anchor : str
            default behaviour: self.xy_anchor0 (xy_anchor given at creation or update)
        '''
        return self.xy_anchor0

    def visible(self, t=omitted):
        '''
        visible attribute of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        visible : bool
            default behaviour: self.visible0 (visible given at creation or update)
        '''
        return self.visible0

    def image(self, t=omitted):
        '''
        image of an animate object. May be overridden.

        Parameters
        ----------
        t : float
            current time

        Returns
        -------
        image : PIL.Image.Image
            use function spec_to_image to load a file
            default behaviour: self.image0 (image given at creation or update)
        '''
        return self.image0

    def settype(self, circle, line, polygon, rectangle, image, text):
        n = 0
        t = ''
        if circle is not omitted:
            t = 'circle'
            n += 1
        if line is not omitted:
            t = 'line'
            n += 1
        if polygon is not omitted:
            t = 'polygon'
            n += 1
        if rectangle is not omitted:
            t = 'rectangle'
            n += 1
        if image is not omitted:
            t = 'image'
            n += 1
        if text is not omitted:
            t = 'text'
            n += 1
        if n >= 2:
            raise SalabimError('more than one object given')
        return t

    def make_pil_image(self, t):

        visible = self.visible(t)

        if (t >= self.t0) and ((t <= self.t1) or self.keep) and visible:
            self._image_visible = True
            self._image_x_prev = self._image_x
            self._image_y_prev = self._image_y
            self._image_ident_prev = self._image_ident

            x = self.x(t)
            y = self.y(t)
            xy_anchor = self.xy_anchor(t)
            if xy_anchor:
                x += self.env.xy_anchor_to_x(xy_anchor, screen_coordinates=self.screen_coordinates)
                y += self.env.xy_anchor_to_y(xy_anchor, screen_coordinates=self.screen_coordinates)

            offsetx = self.offsetx(t)
            offsety = self.offsety(t)
            angle = self.angle(t)

            if (self.type == 'polygon') or (self.type == 'rectangle') or (self.type == 'line'):
                if self.screen_coordinates:
                    linewidth = self.linewidth(t)
                else:
                    linewidth = self.linewidth(t) * self.env._scale

                linecolor = self.env.colorspec_to_tuple(self.linecolor(t))
                fillcolor = self.env.colorspec_to_tuple(self.fillcolor(t))

                cosa = math.cos(angle * math.pi / 180)
                sina = math.sin(angle * math.pi / 180)

                if self.type == 'rectangle':
                    rectangle = self.rectangle(t)
                    p = [
                        rectangle[0], rectangle[1],
                        rectangle[2], rectangle[1],
                        rectangle[2], rectangle[3],
                        rectangle[0], rectangle[3],
                        rectangle[0], rectangle[1]]

                elif self.type == 'line':
                    p = self.line(t)
                    fillcolor = (0, 0, 0, 0)

                else:
                    p = self.polygon(t)

                if self.screen_coordinates:
                    qx = x
                    qy = y
                else:
                    qx = (x - self.env._x0) * self.env._scale
                    qy = (y - self.env._y0) * self.env._scale

                r = []
                minrx = inf
                minry = inf
                maxrx = -inf
                maxry = -inf
                for i in range(0, len(p), 2):
                    px = p[i]
                    py = p[i + 1]
                    rx = px * cosa - py * sina
                    ry = px * sina + py * cosa
                    if not self.screen_coordinates:
                        rx = rx * self.env._scale
                        ry = ry * self.env._scale
                    minrx = min(minrx, rx)
                    maxrx = max(maxrx, rx)
                    minry = min(minry, ry)
                    maxry = max(maxry, ry)
                    r.append(rx)
                    r.append(ry)
                if self.type == 'polygon':
                    if r[0:1] != r[-2:-1]:
                        r.append(r[0])
                        r.append(r[1])

                rscaled = []
                for i in range(0, len(r), 2):
                    rscaled.append(r[i] - minrx + linewidth)
                    rscaled.append(maxry - r[i + 1] + linewidth)
                rscaled = tuple(rscaled)  # to make it hashable

                self._image_ident = (rscaled, minrx, maxrx, minry, maxry,
                                     fillcolor, linecolor, linewidth)
                if self._image_ident != self._image_ident_prev:
                    self._image = Image.new('RGBA', (int(maxrx - minrx + 2 * linewidth),
                                                     int(maxry - minry + 2 * linewidth)), (0, 0, 0, 0))
                    draw = ImageDraw.Draw(self._image)
                    if fillcolor[3] != 0:
                        draw.polygon(rscaled, fill=fillcolor)
                    if (linewidth > 0) and (linecolor[3] != 0):
                        draw.line(rscaled, fill=linecolor,
                                  width=int(linewidth))
                    del draw

                self._image_x = qx + minrx - linewidth + \
                    (offsetx * cosa - offsety * sina)
                self._image_y = qy + minry - linewidth + \
                    (offsetx * sina + offsety * cosa)

            elif self.type == 'circle':
                if self.screen_coordinates:
                    linewidth = self.linewidth(t)
                else:
                    linewidth = self.linewidth(t) * self.env._scale
                fillcolor = self.env.colorspec_to_tuple(self.fillcolor(t))
                linecolor = self.env.colorspec_to_tuple(self.linecolor(t))
                circle = self.circle(t)
                radius = circle[0] if isinstance(circle, (list, tuple)) else circle
                if self.screen_coordinates:
                    qx = x
                    qy = y
                else:
                    qx = (x - self.env._x0) * self.env._scale
                    qy = (y - self.env._y0) * self.env._scale
                    linewidth *= self.env._scale
                    radius *= self.env._scale

                self._image_ident = (radius, linewidth, linecolor, fillcolor)
                if self._image_ident != self._image_ident_prev:
                    nsteps = int(math.sqrt(radius) * 6)
                    tangle = 2 * math.pi / nsteps
                    sint = math.sin(tangle)
                    cost = math.cos(tangle)
                    p = []
                    x = radius
                    y = 0

                    for i in range(nsteps + 1):
                        x, y = (x * cost - y * sint, x * sint + y * cost)
                        p.append(x + radius + linewidth)
                        p.append(y + radius + linewidth)

                    self._image = Image.new('RGBA', (int(radius * 2 + 2 * linewidth),
                                                     int(radius * 2 + 2 * linewidth)), (0, 0, 0, 0))
                    draw = ImageDraw.Draw(self._image)
                    if fillcolor[3] != 0:
                        draw.polygon(p, fill=fillcolor)
                    if (linewidth > 0) and (linecolor[3] != 0):
                        draw.line(p, fill=linecolor, width=int(linewidth))
                    del draw

                dx = offsetx
                dy = offsety
                cosa = math.cos(angle * math.pi / 180)
                sina = math.sin(angle * math.pi / 180)
                ex = dx * cosa - dy * sina
                ey = dx * sina + dy * cosa
                self._image_x = qx + ex - radius - linewidth - 1
                self._image_y = qy + ey - radius - linewidth - 1

            elif self.type == 'image':
                image = self.image(t)
                if isinstance(image, (tuple, list)):
                    image = image[0]  # ignore serial number (for compatibility with pre 2.2.9 versions)
                width = self.width(t)
                if width is None:
                    width = image.size[0]

                height = width * image.size[1] / image.size[0]
                if not self.screen_coordinates:
                    width *= self.env._scale
                    height *= self.env._scale

                angle = self.angle(t)

                anchor = self.anchor(t)
                if self.screen_coordinates:
                    qx = x
                    qy = y
                else:
                    qx = (x - self.env._x0) * self.env._scale
                    qy = (y - self.env._y0) * self.env._scale
                    offsetx = offsetx * self.env._scale
                    offsety = offsety * self.env._scale

                self._image_ident = (image, width, height, angle)
                if self._image_ident != self._image_ident_prev:
                    im1 = image.resize(
                        (int(width), int(height)), Image.ANTIALIAS)
                    self.imwidth, self.imheight = im1.size

                    self._image = im1.rotate(angle, expand=1)

                anchor_to_dis = {
                    'ne': (-0.5, -0.5),
                    'n': (0, -0.5),
                    'nw': (0.5, -0.5),
                    'e': (-0.5, 0),
                    'center': (0, 0),
                    'c': (0, 0),
                    'w': (0.5, 0),
                    'se': (-0.5, 0.5),
                    's': (0, 0.5),
                    'sw': (0.5, 0.5)}
                dx, dy = anchor_to_dis[anchor.lower()]
                dx = dx * self.imwidth + offsetx
                dy = dy * self.imheight + offsety
                cosa = math.cos(angle * math.pi / 180)
                sina = math.sin(angle * math.pi / 180)
                ex = dx * cosa - dy * sina
                ey = dx * sina + dy * cosa
                imrwidth, imrheight = self._image.size

                self._image_x = qx + ex - imrwidth / 2
                self._image_y = qy + ey - imrheight / 2

            elif self.type == 'text':
                textcolor = self.env.colorspec_to_tuple(self.textcolor(t))
                fontsize = self.fontsize(t)
                angle = self.angle(t)
                anchor = self.anchor(t)
                fontname = self.font(t)

                text = self.text(t)
                if self.screen_coordinates:
                    qx = x
                    qy = y
                else:
                    qx = (x - self.env._x0) * self.env._scale
                    qy = (y - self.env._y0) * self.env._scale
                    fontsize = fontsize * self.env._scale
                    offsetx = offsetx * self.env._scale
                    offsety = offsety * self.env._scale

                self._image_ident = (
                    text, fontname, fontsize, angle, textcolor)
                if self._image_ident != self._image_ident_prev:
                    font, heightA = getfont(fontname, fontsize)
                    if text == '':  # this code is a workaround for a bug in PIL >= 4.2.1
                        im = Image.new(
                            'RGBA', (0, 0), (0, 0, 0, 0))
                    else:
                        width, height = font.getsize(text)
                        im = Image.new(
                            'RGBA', (int(width + 0.1 * fontsize), int(height)), (0, 0, 0, 0))
                        imwidth, imheight = im.size
                        draw = ImageDraw.Draw(im)
                        draw.text(xy=(0.1 * fontsize, 0), text=text, font=font, fill=textcolor)
                        # this code is to correct a bug in the rendering of text,
                        # leaving a kind of shadow around the text
                        del draw
                        textcolor3 = textcolor[0:3]
                        if textcolor3 != (0, 0, 0):  # black is ok
                            for y in range(imheight):
                                for x in range(imwidth):
                                    c = im.getpixel((x, y))
                                    if not c[0:3] in (textcolor3, (0, 0, 0)):
                                        im.putpixel((x, y), (textcolor3[0], textcolor3[1], textcolor3[2], c[3]))
                        # end of code to correct bug

                    self.imwidth, self.imheight = im.size
                    self.heightA = heightA

                    self._image = im.rotate(angle, expand=1)

                anchor_to_dis = {
                    'ne': (-0.5, 0),
                    'n': (0, 0),
                    'nw': (0.5, 0),
                    'e': (-0.5, 0.5),
                    'center': (0, 0.5),
                    'c': (0, 0.5),
                    'w': (0.5, 0.5),
                    'se': (-0.5, 1),
                    's': (0, 1),
                    'sw': (0.5, 1)}

                dx, dy = anchor_to_dis[anchor.lower()]
                dx = dx * self.imwidth + offsetx - 0.1 * fontsize
                dy = -0.5 * self.imheight + dy * self.heightA + offsety
                cosa = math.cos(angle * math.pi / 180)
                sina = math.sin(angle * math.pi / 180)
                ex = dx * cosa - dy * sina
                ey = dx * sina + dy * cosa
                imrwidth, imrheight = self._image.size
                self._image_x = qx + ex - imrwidth / 2
                self._image_y = qy + ey - imrheight / 2
            else:
                self._image_visible = False  # should never occur

        else:
            self._image_visible = False

    def remove_background(self, im):
        pixels = im.load()
        background = pixels[0, 0]
        imagewidth, imageheight = im.size
        for y in range(imageheight):
            for x in range(imagewidth):
                if abs(pixels[x, y][0] - background[0]) < 10:
                    if abs(pixels[x, y][1] - background[1]) < 10:
                        if abs(pixels[x, y][2] - background[2]) < 10:
                            pixels[x, y] = (255, 255, 255, 0)


class AnimateButton(object):
    '''
    defines a button

    Parameters
    ----------
    x : int
        x-coordinate of centre of the button in screen coordinates (default 0)

    y : int
        y-coordinate of centre of the button in screen coordinates (default 0)

    width : int
        width of button in screen coordinates (default 80)

    height : int
        height of button in screen coordinates (default 30)

    linewidth : int
        width of contour in screen coordinates (default 0=no contour)

    fillcolor : colorspec
        color of the interior (foreground_color)

    linecolor : colorspec
        color of contour (default foreground_color)

    color : colorspec
        color of the text (default background_color)

    text : str or function
        text of the button (default null string) |n|
        if text is an argumentless function, this will be called each time;
        the button is shown/updated

    font : str
        font of the text (default Helvetica)

    fontsize : int
        fontsize of the text (default 15)

    action :  function
        action to take when button is pressed |n|
        executed when the button is pressed (default None)
        the function should have no arguments |n|

    xy_anchor : str
        specifies where x and y are relative to |n|
        possible values are (default: sw) |n|:
        ``nw    n    ne`` |n|
        ``w     c     e`` |n|
        ``sw    s    se``

    env : Environment
        environment where the component is defined |n|
        if omitted, default_env will be used

    Note
    ----
    On CPython/PyPy platforms, the tkinter functionality is used.
    On Pythonista, this is emulated by salabim
    '''

    def __init__(self, x=0, y=0, width=80, height=30,
                 linewidth=0, fillcolor='fg',
                 linecolor='fg', color='bg', text='', font='',
                 fontsize=15, action=None, env=omitted, xy_anchor='sw'):

        self.env = g.default_env if env is omitted else env
        self.type = 'button'
        self.t0 = -inf
        self.t1 = inf
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.sequence = self.env.serialize()
        self.x = x - width / 2
        self.y = y - height / 2
        self.width = width
        self.height = height
        self.fillcolor = self.env.colorspec_to_tuple(fillcolor)
        self.linecolor = self.env.colorspec_to_tuple(linecolor)
        self.color = self.env.colorspec_to_tuple(color)
        self.linewidth = linewidth
        self.font = font
        self.fontsize = fontsize
        self.text0 = text
        self.lasttext = '*'
        self.action = action
        self.xy_anchor = xy_anchor

        self.env.ui_objects.append(self)
        self.installed = False

    def text(self):
        return self.text0

    def install(self):
        x = self.x + self.env.xy_anchor_to_x(self.xy_anchor, screen_coordinates=True)
        y = self.y + self.env.xy_anchor_to_y(self.xy_anchor, screen_coordinates=True)

        self.button = tkinter.Button(
            self.env.root, text=self.lasttext, command=self.action, anchor=tkinter.CENTER)
        self.button.configure(
            width=int(2.2 * self.width / self.fontsize),
            foreground=self.env.colorspec_to_hex(self.color, False),
            background=self.env.colorspec_to_hex(self.fillcolor, False),
            relief=tkinter.FLAT)
        self.button_window = g.canvas.create_window(
            x + self.width, self.env._height - y - self.height,
            anchor=tkinter.NE, window=self.button)
        self.installed = True

    def remove(self):
        '''
        removes the button object. |n|
        the ui object is removed from the ui queue,
        so effectively ending this ui
        '''
        if self in self.env.ui_objects:
            self.env.ui_objects.remove(self)
        if self.installed:
            if not Pythonista:
                self.button.destroy()
            self.installed = False


class AnimateSlider(object):
    '''
    defines a slider

    Parameters
    ----------
    x : int
        x-coordinate of centre of the slider in screen coordinates (default 0)

    y : int
        y-coordinate of centre of the slider in screen coordinates (default 0)

    vmin : float
        minimum value of the slider (default 0)

    vmax : float
        maximum value of the slider (default 0)

    v : float
        initial value of the slider (default 0) |n|
        should be between vmin and vmax

    resolution : float
        step size of value (default 1)

    width : float
        width of slider in screen coordinates (default 100)

    height : float
        height of slider in screen coordinates (default 20)

    linewidth : float
        width of contour in screen coordinate (default 0 = no contour)

    linecolor : colorspec
        color of contour (default foreground_color)

    labelcolor : colorspec
        color of the label (default foreground_color)

    label : str
        label if the slider (default null string) |n|
        if label is an argumentless function, this function
        will be used to display as label, otherwise the
        label plus the current value of the slider will be shown

    font : str
         font of the text (default Helvetica)

    fontsize : int
         fontsize of the text (default 12)

    action : function
         function executed when the slider value is changed (default None) |n|
         the function should one arguments, being the new value |n|
         if None (default), no action

    xy_anchor : str
        specifies where x and y are relative to |n|
        possible values are (default: sw) |n|:
        ``nw    n    ne`` |n|
        ``w     c     e`` |n|
        ``sw    s    se``

    env : Environment
        environment where the component is defined |n|
        if omitted, default_env will be used

    Note
    ----
    The current value of the slider is the v attibute of the slider. |n|
    On CPython/PyPy platforms, the tkinter functionality is used. |n|
    On Pythonista, this is emulated by salabim
    '''

    def __init__(self, layer=0, x=0, y=0, width=100, height=20,
                 vmin=0, vmax=10, v=omitted, resolution=1,
                 linecolor='fg', labelcolor='fg', label='',
                 font='', fontsize=12, action=None, xy_anchor='sw', env=omitted):

        self.env = g.default_env if env is omitted else env
        n = round((vmax - vmin) / resolution) + 1
        self.vmin = vmin
        self.vmax = vmin + (n - 1) * resolution
        self._v = vmin if v is omitted else v
        self.xdelta = width / n
        self.resolution = resolution

        self.type = 'slider'
        self.t0 = -inf
        self.t1 = inf
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.sequence = self.env.serialize()
        self.x = x - width / 2
        self.y = y - height / 2
        self.width = width
        self.height = height
        self.linecolor = self.env.colorspec_to_tuple(linecolor)
        self.labelcolor = self.env.colorspec_to_tuple(labelcolor)
        self.font = font
        self.fontsize = fontsize
        self.label = label
        self.action = action
        self.installed = False
        self.xy_anchor = xy_anchor

        if Pythonista:
            self.y = self.y - height * 1.5

        self.env.ui_objects.append(self)

    def v(self, value=omitted):
        '''
        value |n|

        Parameters
        ----------
        value: float
            new value |n|
            if omitted, no change

        Returns
        -------
        Current value of the slider : float
        '''
        if value is not omitted:
            if Pythonista:
                self._v = value
            else:
                if self._animate:
                    self.slider.set(value)
                else:
                    self._v = value

        if Pythonista:
            return self._v
        else:
            if self._animate:
                return self.slider.get()
            else:
                return self._v

    def install(self):
        x = self.x + self.env.xy_anchor_to_x(self.xy_anchor, screen_coordinates=True)
        y = self.y + self.env.xy_anchor_to_y(self.xy_anchor, screen_coordinates=True)
        self.slider = tkinter.Scale(
            self.env.root,
            from_=self.vmin, to=self.vmax,
            orient=tkinter.HORIZONTAL,
            label=self.label,
            resolution=self.resolution,
            command=self.action)
        self.slider.window = g.canvas.create_window(
            x, self.env._height - y,
            anchor=tkinter.NW, window=self.slider)
        self.slider.config(
            font=(self.font, int(self.fontsize * 0.8)),
            foreground=self.env.colorspec_to_hex('fg', False),
            background=self.env.colorspec_to_hex('bg', False),
            highlightbackground=self.env.colorspec_to_hex('bg', False))
        self.slider.set(self._v)
        self.installed = True

    def remove(self):
        '''
        removes the slider object |n|
        The ui object is removed from the ui queue,
        so effectively ending this ui
        '''
        if self in self.env.ui_objects:
            self.env.ui_objects.remove(self)
        if self.installed:
            if not Pythonista:
                self.button.quit()
            self.installed = False


class Component(object):
    '''Component object

    A salabim component is used as component (primarily for queueing)
    or as a component with a process |n|
    Usually, a component will be defined as a subclass of Component.

    Parameters
    ----------
    name : str
        name of the component. |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    at : float
        schedule time |n|
        if omitted, now is used

    delay : float
        schedule with a delay |n|
        if omitted, no delay

    urgent : bool
        urgency indicator |n|
        if False (default), the component will be scheduled
        behind all other components scheduled
        for the same time |n|
        if True, the component will be scheduled
        in front of all components scheduled
        for the same time

    process : str
        name of process to be started. |n|
        if omitted, it will try to start self.process() |n|
        if None, no process will be started even if self.process() exists,
        i.e. become a data component. |n|
        note that the function *must* be a generator,
        i.e. contains at least one yield.

    suppress_trace : bool
        suppress_trace indicator |n|
        if True, this component will be excluded from the trace |n|
        If False (default), the component will be traced |n|
        Can be queried or set later with the suppress_trace method.

    suppress_pause_at_step : bool
        suppress_pause_at_step indicator |n|
        if True, if this component becomes current, do not pause when stepping |n|
        If False (default), the component will be paused when stepping |n|
        Can be queried or set later with the suppress_pause_at_step method.

    mode : str preferred
        mode |n|
        will be used in trace and can be used in animations |n|
        if omitted, the mode will be None. |n|
        also mode_time will be set to now.

    env : Environment
        environment where the component is defined |n|
        if omitted, default_env will be used
    '''

    def __init__(self, name=omitted, at=omitted, delay=omitted, urgent=omitted,
      process=omitted, suppress_trace=False, suppress_pause_at_step=False, mode=None, env=omitted, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        _set_name(name, self.env._nameserializeComponent, self)
        self._qmembers = {}
        self._process = None
        self._status = data
        self._requests = collections.defaultdict(int)
        self._claims = collections.defaultdict(int)
        self._waits = []
        self._on_event_list = False
        self._scheduled_time = inf
        self._failed = False
        self._creation_time = self.env._now
        self._suppress_trace = suppress_trace
        self._suppress_pause_at_step = suppress_pause_at_step
        self._mode = mode
        self._mode_time = self.env._now
        self._aos = {}

        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now

        if process is omitted:
            if self.hasprocess():
                p = self.process
            else:
                p = None
        else:
            if process is None:
                p = None
            else:
                try:
                    p = eval('self.' + process)
                except:
                    raise SalabimError('self.' + process + ' not found')

        if p is None:
            if at is not omitted:
                raise SalabimError('at is not allowed for a data component')
            if delay is not omitted:
                raise SalabimError('delay is not allowed for a data component')
            if urgent is not omitted:
                raise SalabimError('urgent is not allowed for a data component')
            if self.env._trace:
                if self._name == 'main':
                    self.env.print_trace('', '', self.name() +
                        ' create', _modetxt(self._mode))
                else:
                    self.env.print_trace('', '', self.name() +
                       ' create data component', _modetxt(self._mode))
        else:
            self.env.print_trace('', '', self.name() +
                ' create', _modetxt(self._mode))
            if not inspect.isgeneratorfunction(p):
                raise SalabimError(process, 'has no yield statement')

            if kwargs:
                try:
                    parameters = inspect.signature(p).parameters
                except AttributeError:
                    parameters = inspect.getargspec(p)[0]  # pre Python 3.4
                kwargs_p = {}
                for kwarg in list(kwargs.keys()):
                    if kwarg in parameters:
                        kwargs_p[kwarg] = kwargs[kwarg]
                        del kwargs[kwarg]  # here kwargs consumes the used arguments
                self._process = p(**kwargs_p)
            else:
                self._process = p()
            extra = 'process=' + self._process.__name__
            if urgent is omitted:
                urgent = False
            if delay is omitted:
                delay = 0
            if at is omitted:
                scheduled_time = self.env._now + delay
            else:
                scheduled_time = at + self.env._offset + delay

            self._reschedule(scheduled_time, urgent, 'activate', extra=extra)

        self.setup(**kwargs)

    def animation_objects(self, q):
        '''
        defines how to display a component when animate a queue

        Parameters
        ----------
        q : Queue
            queue to be visualized. This may be ignored.

        Returns
        -------
        List or tuple containg |n|
            size_x : how much to displace the next component in x-direction, if applicable |n|
            size_y : how much to displace the next component in y-direction, if applicable |n|
            animation objects0 : instance of Animate class |n|
            ... |n|
            default behaviour: |n|
            green square of size 40 (displacements 50), with the sequence number centered in white.

        Note
        ----
        If you override this method, be sure to use the same header. |n|
        '''
        size_x = 50
        size_y = 50
        ao1 = Animate(rectangle0=(-20, -20, 20, 20), linewidth0=0, fillcolor0=self.env._foreground_color)
        ao2 = Animate(text=str(self.sequence_number()), textcolor0=self.env._background_color, anchor='c')
        return (size_x, size_y, ao1, ao2)

    def _remove_from_aos(self, q):
        if q in self._aos:
            for ao in self._aos[q][2:]:
                ao.remove()
            del self._aos[q]

    def setup(self):
        '''
        called immediately after initialization of a component.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments will be passed

        Example
        -------
            class Car(sim.Component):
                def setup(self, color):
                    self.color = color

                def process(self):
                    ...

            redcar=Car(color='red') |n|
            bluecar=Car(color='blue')
        '''
        pass

    def __repr__(self):
        return objectclass_to_str(self) + ' (' + self.name() + ')'

    def print_info(self):
        '''
        prints information about the queue
        '''
        print(objectclass_to_str(self) + ' ' + hex(id(self)))
        print('  name=' + self.name())
        print('  class=' + str(type(self)).split('.')[-1].split("'")[0])
        print('  suppress_trace=' + str(self._suppress_trace))
        print('  suppress_pause_at_step=' + str(self._suppress_pause_at_step))
        print('  status=' + self._status())
        print('  mode=' + _modetxt(self._mode).strip())
        print('  mode_time=' + time_to_string(self._mode_time))
        print('  creation_time=' + time_to_string(self._creation_time))
        print('  scheduled_time=' +
            time_to_string(self._scheduled_time))
        if len(self._qmembers) > 0:
            print('  member of queue(s):')
            for q in sorted(self._qmembers, key=lambda obj: obj.name().lower()):
                print('    ' + pad(q.name(), 20) + ' enter_time=' +
                    time_to_string(self._qmembers[q].enter_time - self.env._offset) +
                    ' priority=' + str(self._qmembers[q].priority))
        if len(self._requests) > 0:
            print('  requesting resource(s):')

            for r in sorted(list(self._requests),
              key=lambda obj: obj.name().lower()):
                print('    ' + pad(r.name(), 20) + ' quantity=' +
                    str(self._requests[r]))
        if len(self._claims) > 0:
            print('  claiming resource(s):')

            for r in sorted(list(self._claims), key=lambda obj: obj.name().lower()):
                print('    ' + pad(r.name(), 20) +
                    ' quantity=' + str(self._claims[r]))
        if len(self._waits) > 0:
            if self._wait_all:
                print('  waiting for all of state(s):')
            else:
                print('  waiting for any of state(s):')
            for s, value, _ in self._waits:
                print('    ' + pad(s.name(), 20) +
                    ' value=' + str(value))

    def hasprocess(self):
        if hasattr(self, 'process'):
            if inspect.isgeneratorfunction(self.process):
                return True
            else:
                raise SalabimError('process has no yield statement')
        return False

    def _push(self, t, urgent):
        self.env._seq += 1
        if urgent:
            seq = -self.env._seq
        else:
            seq = self.env._seq
        self._on_event_list = True
        heapq.heappush(self.env._event_list, (t, seq, self))

    def _remove(self):
        if self._on_event_list:
            for i in range(len(self.env._event_list)):
                if self.env._event_list[i][2] == self:
                    self.env._event_list[i] = self.env._event_list[0]
                    self.env._event_list.pop(0)
                    heapq.heapify(self.env._event_list)
                    self._on_event_list = False
                    return
            raise SalabimError('remove error', self.name())
        if self.status == standby:
            if self in self.env._standby_list:
                self.env._standby_list(self)
            if self in self.env._pending_standby_list:
                self.env._pending_standby_list(self)

    def _check_fail(self):
        if self._requests:
            if self.env._trace:
                self.env.print_trace('', '', self.name(), 'request failed')
            for r in list(self._requests.keys()):
                self._leave(r._requesters)
                if r._requesters._length == 0:
                    r._minq = inf
            self._requests = collections.defaultdict(int)
            self._failed = True

        if self._waits:
            if self.env._trace:
                self.env.print_trace('', '', self.name(), 'wait failed')
            for state, _, _ in self._waits:
                if self in state._waiters:  # there might be more values for this state
                    self._leave(state._waiters)
            self._waits = []
            self._failed = True

    def _reschedule(self, scheduled_time, urgent, caller, extra=''):
        if scheduled_time < self.env._now:
            raise SalabimError(
                'scheduled time ({:0.3f}) before now ({:0.3f})'.
                format(scheduled_time, self.env._now))
        self._scheduled_time = scheduled_time
        if scheduled_time != inf:
            self._push(scheduled_time, urgent)
        self._status = scheduled
        if self.env._trace:
            self.env.print_trace(
                '', '', self.name() + ' ' + caller,
                merge_blanks(
                    'scheduled for {:10.3f}'.format(scheduled_time - self.env._offset) +
                    _urgenttxt(urgent) + '@' + self.lineno_txt(),
                    _modetxt(self._mode),
                    extra))

    def activate(self, at=omitted, delay=0, urgent=False, process=omitted,
      keep_request=False, keep_wait=False, mode=omitted, **kwargs):
        '''
        activate component

        Parameters
        ----------
        at : float
           schedule time |n|
           if omitted, now is used |n|
           inf is allowed

        delay : float
           schedule with a delay |n|
           if omitted, no delay

        urgent : bool
            urgency indicator |n|
            if False (default), the component will be scheduled
            behind all other components scheduled
            for the same time |n|
            if True, the component will be scheduled
            in front of all components scheduled
            for the same time

        process : str
            name of process to be started. |n|
            if omitted, process will not be changed |n|
            if the component is a data component, the
            generator function process will be used as the default process. |n|
            note that the function *must* be a generator,
            i.e. contains at least one yield.

        keep_request : bool
            this affects only components that are requesting. |n|
            if True, the requests will be kept and thus the status will remain requesting |n|
            if False (the default), the request(s) will be canceled and the status will become scheduled

        keep_wait : bool
            this affects only components that are waiting. |n|
            if True, the waits will be kept and thus the status will remain waiting |n|
            if False (the default), the wait(s) will be canceled and the status will become scheduled

        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        if to be applied for the current component, use yield self.activate(). |n|
        if both at and delay are specified, the component becomes current at the sum
        of the two values.
        '''
        p = None
        if process is omitted:
            if self._status == data:
                if self.hasprocess():
                    p = self.process
                else:
                    raise SalabimError('no process for data component')
        else:
            try:
                p = eval('self.' + process)
            except:
                raise SalabimError('self.' + process + ' not found')

        if p is None:
            extra = ''
        else:
            if not inspect.isgeneratorfunction(p):
                raise SalabimError(process, 'has no yield statement')

            if kwargs:
                try:
                    parameters = inspect.signature(p).parameters
                except AttributeError:
                    parameters = inspect.getargspec(p)[0]  # pre Python 3.4

                for kwarg in kwargs:
                    if kwarg not in parameters:
                        raise TypeError("unexpected keyword argument '" + kwarg + "'")
            self._process = p(**kwargs)
            extra = 'process=' + self._process.__name__

        if self._status != current:
            self._remove()
            if p is None:
                if not (keep_request or keep_wait):
                    self._check_fail()
            else:
                self._check_fail()

        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now

        if at is omitted:
            scheduled_time = self.env._now + delay
        else:
            scheduled_time = at + self.env._offset + delay

        self._reschedule(scheduled_time, urgent, 'activate', extra=extra)

    def hold(self, duration=omitted, till=omitted, urgent=False, mode=omitted):
        '''
        hold the component

        Parameters
        ----------
        duration : float
           specifies the duration |n|
           if omitted, 0 is used |n|
           inf is allowed

        till : float
           specifies at what time the component will become current |n|
           if omitted, now is used |n|
           inf is allowed

        urgent : bool
            urgency indicator |n|
            if False (default), the component will be scheduled
            behind all other components scheduled
            for the same time |n|
            if True, the component will be scheduled
            in front of all components scheduled
            for the same time

        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        if to be used for the current component, use ``yield self.hold(...)``. |n|

        if both duration and till are specified, the component will become current at the sum of
        these two.
        '''
        if self._status != passive:
            if self.status != current:
                self._checkisnotdata()
                self._remove()
                self._check_fail()
        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now

        if till is omitted:
            if duration is omitted:
                scheduled_time = self.env._now
            else:
                scheduled_time = self.env._now + duration
        else:
            if duration is omitted:
                scheduled_time = till + self.env._offset
            else:
                raise SalabimError('both duration and till specified')
        self._reschedule(scheduled_time, urgent, 'hold')

    def passivate(self, mode=omitted):
        '''
        passivate the component

        Parameters
        ----------
        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing is specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        if to be used for the current component (nearly always the case), use ``yield self.passivate(...)``.
        '''
        if self._status == current:
            self._remaining_duration = 0
        else:
            self._checkisnotdata()
            self._remove()
            self._check_fail()
            self._remaining_duration = self._scheduled_time - self.env._now
        self._scheduled_time = inf
        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now
        if self.env._trace:
            self.env.print_trace('', '', self.name() + ' passivate', merge_blanks(_modetxt(self._mode)))
        self._status = passive

    def cancel(self, mode=omitted):
        '''
        cancel component (makes the component data)

        Parameters
        ----------
        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        if to be used for the current component, use ``yield self.cancel(...)``.
        '''
        if self._status != current:
            self._checkisnotdata()
            self._remove()
            self._check_fail()
        self._process = None
        self._scheduled_time = inf
        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now
        if self.env._trace:
            self.env.print_trace('', '', 'cancel ' +
                self.name() + ' ' + _modetxt(self._mode))
        self._status = data
        for ao in self.env.an_objects[:]:
            if ao.parent == self:
                self.env.an_objects.remove(ao)

    def standby(self, mode=omitted):
        '''
        puts the component in standby mode

        Parameters
        ----------
        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        Not allowed for data components or main.

        if to be used for the current component
        (which will be nearly always the case),
        use ``yield self.standby(...)``.
        '''
        if self._status != current:
            self._checkisnotdata()
            self._checkisnotmain()
            self._remove()
            self._check_fail()
        self._scheduled_time = self.env._now
        self.env._standbylist.append(self)
        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now
        if self.env._trace:
            if self.env._buffered_trace:
                self.env._buffered_trace = False
            else:
                self.env.print_trace('', '', 'standby', _modetxt(self._mode))
        self._status = standby

    def request(self, *args, **kwargs):
        '''
        request from a resource or resources

        Parameters
        ----------
        args : sequence of items where each item can be:
            - resource, where quantity=1, priority=tail of requesters queue
            - tuples/list containing a resource, a quantity and optionally a priority.
                if the priority is not specified, the request
                for the resource be added to the tail of
                the requesters queue |n|

        fail_at : float
            time out |n|
            if the request is not honored before fail_at,
            the request will be cancelled and the
            parameter failed will be set. |n|
            if not specified, the request will not time out.

        fail_delay : float
            time out |n|
            if the request is not honored before now+fail_delay,
            the request will be cancelled and the
            parameter failed will be set. |n|
            if not specified, the request will not time out.

        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        Not allowed for data components or main.

        If to be used for the current component
        (which will be nearly always the case),
        use ``yield self.request(...)``.

        If the same resource is specified more that once, the quantities are summed |n|

        The requested quantity may exceed the current capacity of a resource |n|

        The parameter failed will be reset by a calling request or wait

        Example
        -------
        ``yield self.request(r1)`` |n|
        --> requests 1 from r1 |n|
        ``yield self.request(r1,r2)`` |n|
        --> requests 1 from r1 and 1 from r2 |n|
        ``yield self.request(r1,(r2,2),(r3,3,100))`` |n|
        --> requests 1 from r1, 2 from r2 and 3 from r3 with priority 100 |n|
        ``yield self.request((r1,1),(r2,2))`` |n|
        --> requests 1 from r1, 2 from r2 |n|
        '''
        fail_at = kwargs.pop('fail_at', omitted)
        fail_delay = kwargs.pop('fail_delay', omitted)
        mode = kwargs.pop('mode', omitted)
        if kwargs:
            raise TypeError("request() got an expected keyword argument '" + tuple(kwargs.keys())[0] + "'")

        if self._status != current:
            self._checkisnotdata()
            self._checkisnotmain()
            self._remove()
            self._check_fail()
        if fail_at is omitted:
            if fail_delay is omitted:
                scheduled_time = inf
            else:
                if fail_delay == inf:
                    scheduled_time = inf
                else:
                    scheduled_time = self.env._now + fail_delay
        else:
            if fail_delay is omitted:
                scheduled_time = fail_at + self.env._offset
            else:
                raise SalabimError('both fail_at and fail_delay specified')

        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now

        self._failed = False

        for arg in args:
            q = 1
            priority = None
            if isinstance(arg, Resource):
                r = arg
            elif isinstance(arg, (tuple, list)):
                r = arg[0]
                if len(arg) >= 2:
                    q = arg[1]
                if len(arg) >= 3:
                    priority = arg[2]
            else:
                raise SalabimError('incorrect specifier', arg)

            if q <= 0:
                raise SalabimError('quantity ' + str(q) + ' <=0')
            self._requests[r] += q  # is same resource is specified several times, just add them up
            addstring = ''
            if priority is None:
                self._enter(r._requesters)
            else:
                addstring = addstring + ' priority=' + str(priority)
                self._enter_sorted(r._requesters, priority)
            if self.env._trace:
                self.env.print_trace(
                    '', '', self.name(),
                    'request for ' + str(q) + ' from ' + r.name() + addstring +
                    ' ' + _modetxt(self._mode))

        for r, q in self._requests.items():
            if q < r._minq:
                r._minq = q

        self._tryrequest()

        if self._requests:
            self._reschedule(scheduled_time, False, 'request')

    def _tryrequest(self):
        honored = True
        for r in self._requests:
            if self._requests[r] > (r._capacity - r._claimed_quantity + 1e-8):
                honored = False
                break
        if honored:
            for r in list(self._requests):
                r._claimed_quantity += self._requests[r]

                self._leave(r._requesters)
                if not r._anonymous:
                    self._claims[r] += self._requests[r]
                    self._enter(r._claimers)
                if r._requesters._length == 0:
                    r._minq = inf
                r.claimed_quantity.tally(r._claimed_quantity)
                r.available_quantity.tally(r._capacity - r._claimed_quantity)
            self._requests = collections.defaultdict(int)
            self._remove()
            self._reschedule(self.env._now, False, 'request honor')

    def _release(self, r, q=None):
        if r not in self._claims:
            raise SalabimError(self.name() +
                ' not claiming from resource ' + r.name())
        if q is None:
            q = self._claims[r]
        if q > self._claims[r]:
            q = self._claims[r]
        r._claimed_quantity -= q
        self._claims[r] -= q
        if self._claims[r] < 1e-8:
            self._leave(r._claimers)
            if r._claimers._length == 0:
                r._claimed_quantity = 0  # to avoid rounding problems
            del self._claims[r]
        r.claimed_quantity.tally(r._claimed_quantity)
        r.available_quantity.tally(r._capacity - r._claimed_quantity)
        if self.env._trace:
            self.env.print_trace('', '', self.name(),
                'release ' + str(q) + ' from ' + r.name())
        r._tryrequest()

    def release(self, *args):
        '''
        release a quantity from a resource or resources

        Parameters
        ----------
        args : sequence of items, where each items can be
            - a resources, where quantity=current claimed quantity
            - a tuple/list containing a resource and the quantity to be released

        Note
        ----
        It is not possible to release from an anonymous resource, this way.
        Use Resource.release() in that case.

        Example
        -------
        yield self.request(r1,(r2,2),(r3,3,100)) |n|
        --> requests 1 from r1, 2 from r2 and 3 from r3 with priority 100 |n|

        c1.release |n|
        --> releases 1 from r1, 2 from r2 and 3 from r3 |n|

        yield self.request(r1,(r2,2),(r3,3,100)) |n|
        c1.release((r2,1)) |n|
        --> releases 1 from r2 |n|

        yield self.request(r1,(r2,2),(r3,3,100)) |n|
        c1.release((r2,1),r3) |n|
        --> releases 2 from r2,and 3 from r3
        '''
        if args:
            for arg in args:
                q = None
                if isinstance(arg, Resource):
                    r = arg
                elif isinstance(arg, (tuple, list)):
                    r = arg[0]
                    if len(arg) >= 2:
                        q = arg[1]
                else:
                    raise SalabimError('incorrect specifier' + arg)
                if r._anonymous:
                    raise SalabimError(
                        'not possible to release anonymous resources ' + r.name())
                self._release(r, q)
        else:
            for r in list(self._claims.keys()):
                self._release(r)

    def wait(self, *args, **kwargs):
        '''
        wait for any or all of the given state values are met

        Parameters
        ----------
        args : sequence of items, where each item can be
            - a state, where value=True, priority=tail of waiters queue)
            - a tuple/list containing |n|
                state, a value and optionally a priority. |n|
                if the priority is not specified, this component will
                be added to the tail of
                the waiters queue |n|

        fail_at : float
            time out |n|
            if the wait is not honored before fail_at,
            the wait will be cancelled and the
            parameter failed will be set. |n|
            if not specified, the wait will not time out.

        fail_delay : float
            time out |n|
            if the wait is not honored before now+fail_delay,
            the request will be cancelled and the
            parameter failed will be set. |n|
            if not specified, the wait will not time out.

        all : bool
            if False (default), continue, if any of the given state/values is met |n|
            if True, continue if all of the given state/values are met

        mode : str preferred
            mode |n|
            will be used in trace and can be used in animations |n|
            if nothing specified, the mode will be unchanged. |n|
            also mode_time will be set to now, if mode is set.

        Note
        ----
        Not allowed for data components or main.

        If to be used for the current component
        (which will be nearly always the case),
        use ``yield self.wait(...)``.

        It is allowed to wait for more than one value of a state |n|
        the parameter failed will be reset by a calling wait

        If you want to check for all components to meet a value (and clause),
        use Component.wait(..., all=True)

        The value may be specified in three different ways:

        * constant, that value is just compared to state.value() |n|
          yield self.wait((light,'red'))
        * an expression, containg one or more $-signs
          the $ is replaced by state.value(), each time the condition is tested. |n|
          self refers to the component under test, state refers to the state
          under test. |n|
          yield self.wait((light,'$ in ("red","yellow")')) |n|
          yield self.wait((level,'$<30')) |n|
        * a function. In that case the parameter should function that
          should accept three arguments: the value, the component under test and the
          state under test. |n|
          usually the function will be a lambda function, but that's not
          a requirement. |n|
          yield self.wait((light,lambda t, comp, state: t in ('red','yellow'))) |n|
          yield self.wait((level,lambda t, comp, state: t < 30)) |n|

        Example
        -------
        ``yield self.wait(s1)`` |n|
        --> waits for s1.value()==True |n|
        ``yield self.wait(s1,s2)`` |n|
        --> waits for s1.value()==True or s2.value==True |n|
        ``yield self.wait((s1,False,100),(s2,'on'),s3)`` |n|
        --> waits for s1.value()==False or s2.value=='on' or s3.value()==True |n|
        s1 is at the tail of waiters, because of the set priority |n|
        ``yield self.wait(s1,s2,all=True)`` |n|
        --> waits for s1.value()==True and s2.value==True |n|
        '''
        fail_at = kwargs.pop('fail_at', omitted)
        fail_delay = kwargs.pop('fail_delay', omitted)
        all = kwargs.pop('all', False)
        mode = kwargs.pop('mode', omitted)
        if kwargs:
            raise TypeError("wait() got an expected keyword argument '" + tuple(kwargs.keys())[0] + "'")

        if self._status != current:
            self._checkisnotdata()
            self._checkisnotmain()
            self._remove()
            self._check_fail()

        self._wait_all = all
        self._fail = False

        if fail_at is omitted:
            if fail_delay is omitted:
                scheduled_time = inf
            else:
                if fail_delay == inf:
                    scheduled_time = inf
                else:
                    scheduled_time = self.env._now + fail_delay
        else:
            if fail_delay is omitted:
                scheduled_time = fail_at + self._env._offset
            else:
                raise SalabimError('both fail_at and fail_delay specified')

        if mode is not omitted:
            self._mode = mode
            self._mode_time = self.env._now

        for arg in args:
            value = True
            priority = None
            if isinstance(arg, State):
                state = arg
            elif isinstance(arg, (tuple, list)):
                state = arg[0]
                if len(arg) >= 2:
                    value = arg[1]
                if len(arg) >= 3:
                    priority = arg[2]
            else:
                raise SalabimError('incorrect specifier', args)

            for (statex, _, _) in self._waits:
                if statex == state:
                    break
            else:
                if priority is None:
                    self._enter(state._waiters)
                else:
                    self._enter_sorted(state._waiters, priority)
            if inspect.isfunction(value):
                self._waits.append((state, value, 2))
            elif '$' in str(value):
                self._waits.append((state, value, 1))
            else:
                self._waits.append((state, value, 0))

        if not self._waits:
            raise SalabimError('no states specified')
        self._trywait()

        if self._waits:
            self._reschedule(scheduled_time, False, 'wait')

    def _trywait(self):
        if self._wait_all:
            honored = True
            for state, value, valuetype in self._waits:
                if valuetype == 0:
                    if value != state._value:
                        honored = False
                        break
                elif valuetype == 1:
                    if eval(value.replace('$', 'state._value')):
                        honored = False
                        break
                elif valuetype == 2:
                    if not value(state._value, self, state):
                        honored = False
                        break

        else:
            honored = False
            for state, value, valuetype in self._waits:
                if valuetype == 0:
                    if value == state._value:
                        honored = True
                        break
                elif valuetype == 1:
                    if eval(value.replace('$', str(state._value))):
                        honored = True
                        break
                elif valuetype == 2:
                    if value(state._value, self, state):
                        honored = True
                        break

        if honored:
            for s, _, _ in self._waits:
                if self in s._waiters:  # there might be more values for this state
                    self._leave(s._waiters)
            self._waits = []
            self._remove()
            self._reschedule(self.env._now, False, 'wait honor')

        return honored

    def claimed_quantity(self, resource):
        '''
        Parameters
        ----------
        resource : Resoure
            resource to be queried

        Returns
        -------
        the claimed quantity from a resource : float or int
            if the resource is not claimed, 0 will be returned
        '''
        return self._claims.get(resource, 0)

    def claimed_resources(self):
        '''
        Returns
        -------
        list of claimed resources : list
        '''
        return self._claims.keys()

    def requested_resources(self):
        '''
        Returns
        -------
        list of requested resources : list
        '''
        return self._requests.keys()

    def requested_quantity(self, resource):
        '''
        Parameters
        ----------
        resource : Resoure
            resource to be queried

        Returns
        -------
        the requested (not yet honored) quantity from a resource : float or int
            if there is no request for the resource, 0 will be returned
        '''
        return self._requests.get(resource, 0)

    def failed(self):
        '''
        Returns
        -------
        True, if the latest request/wait has failed (either by timeout or external) : bool
        False, otherwise
        '''
        return self._failed

    def name(self):
        '''
        Returns
        -------
        Name of the component : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the component (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the component : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dotcomma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def running_process(self):
        '''
        Returns
        -------
        name of the running process : str
            if data component, None
        '''
        if self._process is None:
            return None
        else:
            return self._process.__name__

    def suppress_trace(self, value=omitted):
        '''
        Parameters
        ----------
        value: bool
            new suppress_trace value |n|
            if omitted, no change

        Returns
        -------
        suppress_trace : bool
            components with the suppress_status of True, will be ignored in the trace
        '''
        if value is not omitted:
            self._suppress_trace = value
        return self._suppress_trace

    def suppress_pause_at_step(self, value=omitted):
        '''
        Parameters
        ----------
        value: bool
            new suppress_trace value |n|
            if omitted, no change

        Returns
        -------
        suppress_pause_at_step : bool
            components with the suppress_pause_at_step of True, will be ignored in a step
        '''
        if value is not omitted:
            self._suppress_pause_at_step = value
        return self._suppress_pause_at_step

    def mode(self, value=omitted):
        '''
        Parameters
        ----------
        value: any, str recommended
            new mode |n|
            if omitted, no change |n|
            mode_time will be set if a new mode is specified

        Returns
        -------
        mode of the component : any, usually str
            the mode is useful for tracing and animations. |n|
            Usually the mode will be set in a call to passivate, hold, activate, request or standby.
        '''
        if value is not omitted:
            self._mode_time = self.env._now
            self._mode = value

        return self._mode

    def ispassive(self):
        '''
        Returns
        -------
        True if status is passive, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return self._status == passive

    def iscurrent(self):
        '''
        Returns
        -------
        True if status is current, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return self._status == current

    def isrequesting(self):
        '''
        Returns
        -------
        True if status is requesting, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return bool(self._requests)

    def iswaiting(self):
        '''
        Returns
        -------
        True if status is waiting, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return bool(self._waits)

    def isscheduled(self):
        '''
        Returns
        -------
        True if status is scheduled, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return (self._status == scheduled) and (not self._requests) and (not self._waits)

    def isstandby(self):
        '''
        Returns
        -------
        True if status is standby, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True
        '''
        return self._status == standby

    def isdata(self):
        '''
        Returns
        -------
        True if status is data, False otherwise : bool

        Note
        ----
        Be sure to always include the parentheses, otherwise the result will be always True!
        '''
        return self._status == data

    def queues(self):
        '''
        Returns
        -------
        set of queues where the component belongs to
        '''
        return set(self._qmembers)

    def count(self, q=omitted):
        '''
        queue count

        Parameters
        ----------
        q : Queue
            queue to check or |n|
            if omitted, the number of queues where the component is in

        Returns
        -------
        1 if component is in q, 0 otherwise : int
            |n|
            if q is omitted, the number of queues where the component is in
        '''
        if q is omitted:
            return len(self._qmembers)
        else:
            return 1 if self in q else 0

    def index(self, q):
        '''
        Parameters
        ----------
        q : Queue
            queue to be queried

        Returns
        -------
        index of component in q : int
            if component belongs to q |n|
            -1 if component does not belong to q
        '''
        m1 = self._member(q)
        if m1 is None:
            return -1
        else:
            mx = q._head.successor
            index = 0
            while mx != m1:
                mx = mx.successor
                index += 1
            return index

    def _enter(self, q):
        savetrace = self.env._trace
        self.env._trace = False
        self.enter(q)
        self.env._trace = savetrace

    def enter(self, q):
        '''
        enters a queue at the tail

        Parameters
        ----------
        q : Queue
            queue to enter

        Note
        ----
        the priority will be set to
        the priority of the tail component of the queue, if any
        or 0 if queue is empty
        '''
        self._checknotinqueue(q)
        priority = q._tail.predecessor.priority
        Qmember().insert_in_front_of(q._tail, self, q, priority)

    def enter_at_head(self, q):
        '''
        enters a queue at the head

        Parameters
        ----------
        q : Queue
            queue to enter

        Note
        ----
        the priority will be set to
        the priority of the head component of the queue, if any
        or 0 if queue is empty
        '''

        self._checknotinqueue(q)
        priority = q._head.successor.priority
        Qmember().insert_in_front_of(q._head.successor, self, q, priority)

    def enter_in_front_of(self, q, poscomponent):
        '''
        enters a queue in front of a component

        Parameters
        ----------
        q : Queue
            queue to enter

        poscomponent : Component
            component to be entered in front of

        Note
        ----
        the priority will be set to the priority of poscomponent
        '''

        self._checknotinqueue(q)
        m2 = poscomponent._checkinqueue(q)
        priority = m2.priority
        Qmember().insert_in_front_of(m2, self, q, priority)

    def enter_behind(self, q, poscomponent):
        '''
        enters a queue behind a component

        Parameters
        ----------
        q : Queue
            queue to enter

        poscomponent : Component
            component to be entered behind

        Note
        ----
        the priority will be set to the priority of poscomponent
        '''

        self._checknotinqueue(q)
        m1 = poscomponent._checkinqueue(q)
        priority = m1.priority
        Qmember().insert_in_front_of(m1.successor, self, q, priority)

    def enter_sorted(self, q, priority):
        '''
        enters a queue, according to the priority

        Parameters
        ----------
        q : Queue
            queue to enter

        priority: float
            priority in the queue

        Note
        ----
        The component is placed just before the first component with a priority > given priority
        '''

        self._checknotinqueue(q)
        m2 = q._head.successor
        while (m2 != q._tail) and (m2.priority <= priority):
            m2 = m2.successor
        Qmember().insert_in_front_of(m2, self, q, priority)

    def _enter_sorted(self, q, priority):
        savetrace = self.env._trace
        self.env._trace = False
        self.enter_sorted(q, priority)
        self.env._trace = savetrace

    def _leave(self, q):
        savetrace = self.env._trace
        self.env._trace = False
        self.leave(q)
        self.env._trace = savetrace

    def leave(self, q):
        '''
        leave queue

        Parameters
        ----------
        q : Queue
            queue to leave

        Note
        ----
        statistics are updated accordingly
        '''

        mx = self._checkinqueue(q)
        m1 = mx.predecessor
        m2 = mx.successor
        m1.successor = m2
        m2.predecessor = m1
        mx.component = None
        # signal for components method that member is not in the queue
        q._length -= 1
        del self._qmembers[q]
        if self.env._trace:
            self.env.print_trace('', '', self.name(), 'leave ' + q.name())
        length_of_stay = self.env._now - mx.enter_time
        q.length_of_stay.tally(length_of_stay)
        q.length.tally(q._length)
        if q._animate_on:
            self._remove_from_aos(q)
            q._animate_update()

    def priority(self, q, priority=omitted):
        '''
        gets/sets the priority of a component in a queue

        Parameters
        ----------
        q : Queue
            queue where the component belongs to

        priority : float
            priority in queue |n|
            if omitted, no change

        Returns
        -------
        the priority of the component in the queue : float

        Note
        ----
        if you change the priority, the order of the queue may change
        '''

        mx = self._checkinqueue(q)
        if priority is not omitted:
            if priority != mx.priority:
                # leave.sort is not possible, because statistics will be affected
                mx.predecessor.successor = mx.successor
                mx.successor.predecessor = mx.predecessor

                m2 = q._head.successor
                while (m2 != q._tail) and (m2.priority <= priority):
                    m2 = m2.successor

                m1 = m2.predecessor
                m1.successor = mx
                m2.predecessor = mx
                mx.predecessor = m1
                mx.successor = m2
                mx.priority = priority
                for iter in q._iter_touched:
                    q._iter_touched[iter] = True
        return mx.priority

    def successor(self, q):
        '''
        Parameters
        ----------
        q : Queue
            queue where the component belongs to

        Returns
        -------
        the successor of the component in the queue: Component
            if component is not at the tail. |n|
            returns None if component is at the tail.
        '''

        mx = self._checkinqueue(q)
        return mx.successor.component

    def predecessor(self, q):
        '''
        Parameters
        ----------
        q : Queue
            queue where the component belongs to

        Returns : Component
            predecessor of the component in the queue
            if component is not at the head. |n|
            returns None if component is at the head.
        '''

        mx = self._checkinqueue(q)
        return mx.predecessor.component

    def enter_time(self, q):
        '''
        Parameters
        ----------
        q : Queue
            queue where component belongs to

        Returns
        -------
        time the component entered the queue : float
        '''
        mx = self._checkinqueue(q)
        return mx.enter_time - self.env._offset

    def creation_time(self):
        '''
        Returns
        -------
        time the component was created : float
        '''
        return self._creation_time - self.env._offset

    def scheduled_time(self):
        '''
        Returns
        -------
        time the component scheduled for, if it is scheduled : float
            returns inf otherwise
        '''
        return self._scheduled_time - self.env._offset

    def remaining_duration(self):
        '''
        Returns
        -------
        remaining duration : float
            if passive, remaining time at time of passivate |n|
            if scheduled, remaing time till scheduled time |n|
            if requesting or waiting, time till fail_at time |n|
            else: 0

        Note
        ----
        This method is very handy for interrupting a process and then resuming it,
        after some (breakdown) time
        '''
        if self._status == passive:
            return self._remaining_duration
        elif self._status == scheduled:
            return self._scheduled_time - self.env._now
        else:
            return 0

    def mode_time(self):
        '''
        Returns
        -------
        time the component got it's latest mode : float
            For a new component this is
            the time the component was created. |n|
            this function is particularly useful for animations.
        '''
        return self._mode_time - self.env._offset

    def status(self):
        '''
        returns the status of a component

        possible values are
            - data
            - passive
            - scheduled
            - requesting
            - waiting
            - current
            - standby
        '''

        if len(self._requests) > 0:
            return requesting
        if len(self._waits) > 0:
            return waiting
        return self._status

    def _member(self, q):
        return self._qmembers.get(q, None)

    def _checknotinqueue(self, q):
        mx = self._member(q)
        if mx is None:
            pass
        else:
            raise SalabimError(
                self.name() + ' is already member of ' + q.name())

    def _checkinqueue(self, q):
        mx = self._member(q)
        if mx is None:
            raise SalabimError(self.name() + ' is not member of ' + q.name())
        else:
            return mx

    def _checkisnotdata(self):
        if self._status == data:
            raise SalabimError(self.name() + ' data component not allowed')

    def _checkisnotmain(self):
        if self == self.env._main:
            raise SalabimError(self.name() + ' main component not allowed')

    def lineno_txt(self):
        plus = '+'
        if self == self.env._main:
            frame = self.frame
        else:
            frame = self._process.gi_frame
            if frame.f_lasti == -1:  # checks whether generator is created
                plus = ' '

        return self.env._frame_to_lineno(frame) + plus


class _Distribution():

    def bounded_sample(self, lowerbound=-inf, upperbound=inf, fail_value=omitted, number_of_retries=100):
        '''
        Parameters
        ----------
        lowerbound : float
            sample values < lowerbound will be rejected (at most 100 retries) |n|
            if omitted, no lowerbound check

        upperbound : float
            sample values > upperbound will be rejected (at most 100 retries) |n|
            if omitted, no upperbound check

        fail_value : float
            value to be used if. after number_of_tries retries, sample is still not within bounds |n|
            default: lowerbound, if specified, otherwise upperbound

        number_of_tries : int
            number of tries before fail_value is returned |n|
            default: 100

        Returns
        -------
        Bounded sample of a distribution : depending on distribution type (usually float)

        Note
        ----
        If, after number_of_tries retries, the sampled value is still not within the given bounds,
        fail_value  will be returned |n|
        Samples that cannot be converted (only possible with Pdf) to float are assumed to be within the bounds.
        '''
        if (lowerbound == -inf) and (upperbound == inf):
            return self.sample()

        if lowerbound is omitted:
            lowerbound = -inf
        if upperbound is omitted:
            upperbound = inf

        if lowerbound > upperbound:
            raise SalabimError('lowerbound > upperbound')

        if number_of_retries <= 0:
            raise SalabimError('number_of_tries <= 0')

        if fail_value is omitted:
            if lowerbound == -inf:
                fail_value = upperbound
            else:
                fail_value = lowerbound

        for _ in range(number_of_retries):
            sample = self.sample()
            try:
                samplefloat = float(sample)
            except ValueError:
                return sample  # a value that cannot be converted to a float is sampled is assumed to be correct

            if (samplefloat >= lowerbound) and (samplefloat <= upperbound):
                return sample

        return fail_value

    def __call__(self, *args):
        return self.sample(*args)


class Exponential(_Distribution):
    '''
    exponential distribution

    Parameters
    ----------
    mean : float
        mean of the distribtion (beta)|n|
        if omitted, the rate is used |n|
        must be >0

    rate : float
        rate of the distribution (lambda)|n|
        if omitted, the mean is used |n|
        must be >0

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    Either mean or rate has to be specified, not both
    '''

    def __init__(self, mean=omitted, rate=omitted, randomstream=omitted):
        if mean is omitted:
            if rate is omitted:
                raise SalabimError('neither mean nor rate are specified')
            else:
                if rate <= 0:
                    raise SalabimError('rate<=0')
                self._mean = 1 / rate
        else:
            if rate is omitted:
                if mean <= 0:
                    raise SalabimError('mean<=0')
                self._mean = mean
            else:
                raise SalabimError('both mean and rate are specified')

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

    def __repr__(self):
        return('Exponential')

    def print_info(self):
        '''
        prints information about the distribution
        '''
        print('Exponential distribution ' + hex(id(self)))
        print('  mean=' + str(self._mean))
        print('  rate (lambda)=' + str(1 / self._mean))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        return self.randomstream.expovariate(1 / (self._mean))

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Normal(_Distribution):
    '''
    normal distribution

    Parameters
    ----------
    mean : float
        mean of the distribution

    standard_deviation : float
        standard deviation of the distribution |n|
        if omitted, coefficient_of_variation, is used to specify the variation
        if neither standard_devation nor coefficient_of_variation is given, 0 is used,
        thus effectively a contant distribution |n|
        must be >=0

    coefficient_of_variation : float
        coefficient of variation of the distribution |n|
        if omitted, standard_deviation is used to specify variation |n|
        the resulting standard_deviation must be >=0

    use_gauss : bool
        if False (default), use the random.normalvariate method |n|
        if True, use the random.gauss method |n|
        the documentation for random states that the gauss method should be slightly faster,
        although that statement is doubtful.

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''

    def __init__(self, mean, standard_deviation=omitted, coefficient_of_variation=omitted,
        use_gauss=False, randomstream=omitted):
        self._use_gauss = use_gauss
        self._mean = mean
        if standard_deviation is omitted:
            if coefficient_of_variation is omitted:
                self._standard_deviation = 0
            else:
                if mean == 0:
                    raise SalabimError('coefficient_of_variation not allowed with mean = 0')
                self._standard_deviation = coefficient_of_variation * mean
        else:
            if coefficient_of_variation is omitted:
                self._standard_deviation = standard_deviation
            else:
                raise SalabimError('both standard_deviation and coefficient_of_variation specified')
        if self._standard_deviation < 0:
            raise SalabimError('standard_deviation < 0')
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

    def __repr__(self):
        return 'Normal'

    def print_info(self):
        print('Normal distribution ' + hex(id(self)))
        print('  mean=' + str(self._mean))
        print('  standard_deviation=' + str(self._standard_deviation))
        if self._mean == 0:
            print('  coefficient of variation= N/A')
        else:
            print('  coefficient_of_variation=' + str(self._standard_deviation / self._mean))
        if self._use_gauss:
            print('  use_gauss=True')
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        if self._use_gauss:
            return self.randomstream.gauss(self._mean, self._standard_deviation)
        else:
            return self.randomstream.normalvariate(self._mean, self._standard_deviation)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class IntUniform(_Distribution):
    '''
    integer uniform distribution, i.e. sample integer values between lowerbound and upperbound (inclusive)

    Parameters
    ----------
    lowerbound : int
        lowerbound of the distribution

    upperbound : int
        upperbound of the distribution |n|
        if omitted, lowerbound will be used |n|
        must be >= lowerbound

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    In contrast to range, the upperbound is included.

    Example
    -------
    die = sim.IntUniform(1,6)
    for _ in range(10):
        print (die())

    This will print 10 throws of a die.
    '''

    def __init__(self, lowerbound, upperbound=omitted, randomstream=omitted):
        self._lowerbound = lowerbound
        if upperbound is omitted:
            self._upperbound = lowerbound
        else:
            self._upperbound = upperbound
        if self._lowerbound > self._upperbound:
            raise SalabimError('lowerbound>upperbound')
        if lowerbound != int(lowerbound):
            raise SalabimError('lowerbound not integer')
        if upperbound != int(upperbound):
            raise SalabimError('upperbound not integer')

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream
        self._mean = (self._lowerbound + self._upperbound) / 2

    def __repr__(self):
        return 'IntUniform'

    def print_info(self):
        print('IntUniform distribution ' + hex(id(self)))
        print('  lowerbound=' + str(self._lowerbound))
        print('  upperbound=' + str(self._upperbound))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution: int
        '''
        return self.randomstream.randint(self._lowerbound, self._upperbound)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Uniform(_Distribution):
    '''
    uniform distribution

    Parameters
    ----------
    lowerbound : float
        lowerbound of the distribution

    upperbound : float
        upperbound of the distribution |n|
        if omitted, lowerbound will be used |n|
        must be >= lowerbound

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''

    def __init__(self, lowerbound, upperbound=omitted, randomstream=omitted):
        self._lowerbound = lowerbound
        if upperbound is omitted:
            self._upperbound = lowerbound
        else:
            self._upperbound = upperbound
        if self._lowerbound > self._upperbound:
            raise SalabimError('lowerbound>upperbound')
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream
        self._mean = (self._lowerbound + self._upperbound) / 2

    def __repr__(self):
        return 'Uniform'

    def print_info(self):
        print('Uniform distribution ' + hex(id(self)))
        print('  lowerbound=' + str(self._lowerbound))
        print('  upperbound=' + str(self._upperbound))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution: float
        '''
        return self.randomstream.uniform(self._lowerbound, self._upperbound)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Triangular(_Distribution):
    '''
    triangular distribution

    Parameters
    ----------
    low : float
        lowerbound of the distribution

    high : float
        upperbound of the distribution |n|
        if omitted, low will be used, thus effectively a constant distribution |n|
        high must be >= low

    mode : float
        mode of the distribution |n|
        if omitted, the average of low and high will be used, thus a symmetric triangular distribution |n|
        mode must be between low and high

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''

    def __init__(self, low, high=omitted, mode=omitted, randomstream=omitted):
        self._low = low
        if high is omitted:
            self._high = low
        else:
            self._high = high
        if mode is omitted:
            self._mode = (self._high + self._low) / 2
        else:
            self._mode = mode
        if self._low > self._high:
            raise SalabimError('low>high')
        if self._low > self._mode:
            raise SalabimError('low>mode')
        if self._high < self._mode:
            raise SalabimError('high<mode')
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream
        self._mean = (self._low + self._mode + self._high) / 3

    def __repr__(self):
        return 'Triangular'

    def print_info(self):
        print('Triangular distribution ' + hex(id(self)))
        print('  low=' + str(self._low))
        print('  high=' + str(self._high))
        print('  mode=' + str(self._mode))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribtion : float
        '''
        return self.randomstream.triangular(self._low, self._high, self._mode)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Constant(_Distribution):
    '''
    constant distribution

    Parameters
    ----------
    value : float
        value to be returned in sample

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed |n|
        Note that this is only for compatibility with other distributions

    '''

    def __init__(self, value, randomstream=omitted):
        self._value = value
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream
        self._mean = value

    def __repr__(self):
        return 'Constant'

    def print_info(self):
        print('Constant distribution ' + hex(id(self)))
        print('  value=' + str(self._value))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        sample of the distribution (= the specified constant) : float
        '''
        return(self._value)

    def mean(self):
        '''
        Returns
        -------
        mean of the distribution (= the specified constant) : float
        '''
        return self._mean


class Poisson(_Distribution):
    '''
    Poisson distribution

    Parameters
    ----------
    mean: float
        mean (lambda) of the distribution

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    The run time of this function increases when mean (lambda) increases. |n|
    It is not recommended to use mean (lambda) > 100
    '''

    def __init__(self, mean, randomstream=omitted):
        if mean <= 0:
            raise SalabimError('mean (lambda) <=0')

        self._mean = mean

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

    def __repr__(self):
        return 'Poisson'

    def print_info(self):
        print('Poissonl distribution ' + hex(id(self)))
        print('  mean (lambda)' + str(self._lambda_))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : int
        '''
        t = math.exp(-self._mean)
        s = t
        k = 0

        u = self.randomstream.random()
        last_s = inf
        while s < u:
            k += 1
            t *= self._mean / k
            s += t
            if last_s == s:  # avoid infinite loops
                break
            last_s = s
        return k

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Weibull(_Distribution):
    '''
    weibull distribution

    Parameters
    ----------
    scale: float
        scale of the distribution (alpha or k)

    shape: float
        shape of the distribution (beta or lambda)|n|
        should be >0

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''

    def __init__(self, scale, shape, randomstream=omitted):
        self._scale = scale
        if shape <= 0:
            raise SalabimError('shape<=0')

        self._shape = shape
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        self._mean = self._scale * math.gamma((1 / self._shape) + 1)

    def __repr__(self):
        return 'Weibull'

    def print_info(self):
        print('Weibull distribution ' + hex(id(self)))
        print('  scale (alpha or k)=' + str(self._scale))
        print('  shape (beta or lambda)=' + str(self._shape))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        return self.randomstream.weibullvariate(self._scale, self._shape)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Gamma(_Distribution):
    '''
    gamma distribution

    Parameters
    ----------
    shape: float
        shape of the distribution (k) |n|
        should be >0

    scale: float
        scale of the distribution (teta) |n|
        should be >0

    rate : float
        rate of the distribution (beta) |n|
        should be >0

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    Either scale or rate has to be specified, not both.
    '''

    def __init__(self, shape, scale=omitted, rate=omitted, randomstream=omitted):
        if shape <= 0:
            raise SalabimError('shape<=0')
        self._shape = shape
        if rate is omitted:
            if scale is omitted:
                raise SalabimError('neither scale nor rate specified')
            else:
                if scale <= 0:
                    raise SalabimError('scale<=0')
                self._scale = scale
        else:
            if scale is omitted:
                if rate <= 0:
                    raise SalabimError('rate<=0')
                self._scale = 1 / rate
            else:
                raise SalabimError('both scale and rate specified')

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        self._mean = self._shape * self._scale

    def __repr__(self):
        return 'Gamma'

    def print_info(self):
        print('Gamma distribution ' + hex(id(self)))
        print('  shape (k)=' + str(self._shape))
        print('  scale (teta)=' + str(self._scale))
        print('  rate (beta)=' + str(1 / self._scale))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        return self.randomstream.gammavariate(self._shape, self._scale)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Beta(_Distribution):
    '''
    beta distribution

    Parameters
    ----------
    alpha: float
        alpha shape of the distribution |n|
        should be >0

    beta: float
        beta shape of the distribution |n|
        should be >0

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''

    def __init__(self, alpha, beta, randomstream=omitted):
        if alpha <= 0:
            raise SalabimError('alpha<=0')
        self._alpha = alpha
        if beta <= 0:
            raise SalabimError('beta<>=0')
        self._beta = beta

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        self._mean = self._alpha / (self._alpha + self._beta)

    def __repr__(self):
        return 'Beta'

    def print_info(self):
        print('Beta distribution ' + hex(id(self)))
        print('  alpha=' + str(self._alpha))
        print('  beta=' + str(self._beta))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        return self.randomstream.betavariate(self._alpha, self._beta)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Erlang(_Distribution):
    '''
    erlang distribution

    Parameters
    ----------
    shape: int
        shape of the distribution (k) |n|
        should be >0

    rate: float
        rate parameter (lambda) |n|
        if omitted, the scale is used |n|
        should be >0

    scale: float
        scale of the distribution (mu) |n|
        if omitted, the rate is used |n|
        should be >0

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    Either rate or scale has to be specified, not both.
    '''

    def __init__(self, shape, rate=omitted, scale=omitted, randomstream=omitted):
        if int(shape) != shape:
            raise SalabimError('shape not integer')
        if shape <= 0:
            raise SalabimError('shape <=0')
        self._shape = shape
        if rate is omitted:
            if scale is omitted:
                raise SalabimError('neither rate nor scale specified')
            else:
                if scale <= 0:
                    raise SalabimError('scale<=0')
                self._rate = 1 / scale
        else:
            if scale is omitted:
                if rate <= 0:
                    raise SalabimError('rate<=0')
                self._rate = rate
            else:
                raise SalabimError('both rate and scale specified')

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        self._mean = self._shape / self._rate

    def __repr__(self):
        return 'Erlang'

    def print_info(self):
        print('Erlang distribution ' + hex(id(self)))
        print('  shape (k)=' + str(self._shape))
        print('  rate (lambda)=' + str(self._rate))
        print('  scale (mu)=' + str(1 / self._rate))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        return self.randomstream.gammavariate(self._shape, 1 / self._rate)

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Cdf(_Distribution):
    '''
    Cumulative distribution function

    Parameters
    ----------
    spec : list or tuple
        list with x-values and corresponding cumulative density
        (x1,c1,x2,c2, ...xn,cn) |n|
        Requirements:

            x1<=x2<= ...<=xn |n|
            c1<=c2<=cn |n|
            c1=0 |n|
            cn>0 |n|
            all cumulative densities are auto scaled according to cn,
            so no need to set cn to 1 or 100.

    randomstream: randomstream
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it defines a new stream with the specified seed

    '''

    def __init__(self, spec, randomstream=omitted):
        self._x = []
        self._cum = []
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        lastcum = 0
        lastx = -inf
        spec = list(spec)
        if not spec:
            raise SalabimError('no arguments specified')
        if spec[1] != 0:
            raise SalabimError('first cumulative value should be 0')
        while len(spec) > 0:
            x = spec.pop(0)
            if not spec:
                raise SalabimError('uneven number of parameters specified')
            if x < lastx:
                raise SalabimError(
                    'x value {} is smaller than previous value {}'.format(x, lastx))
            cum = spec.pop(0)
            if cum < lastcum:
                raise SalabimError('cumulative value {} is smaller than previous value {}'
                    .format(cum, lastcum))
            self._x.append(x)
            self._cum.append(cum)
            lastx = x
            lastcum = cum
        if lastcum == 0:
            raise SalabimError('last cumulative value should be > 0')
        self._cum = [x / lastcum for x in self._cum]
        self._mean = 0
        for i in range(len(self._cum) - 1):
            self._mean +=\
                ((self._x[i] + self._x[i + 1]) / 2) * \
                (self._cum[i + 1] - self._cum[i])

    def __repr__(self):
        return 'Cdf'

    def print_info(self):
        print('Cdf distribution ' + hex(id(self)))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : float
        '''
        r = self.randomstream.random()
        for i in range(len(self._cum)):
            if r < self._cum[i]:
                return interpolate(r, self._cum[i - 1], self._cum[i], self._x[i - 1], self._x[i])
        return self._x[i]

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class Pdf(_Distribution):
    '''
    Probability distribution function

    Parameters
    ----------
    spec : list or tuple
        either

        -   if no probabilities specified |n|:
            list with x-values and corresponding probability
            (x0, p0, x1, p1, ...xn,pn) |n|
        -   if probabilities is specified |n|:
            list with x-values

    probabilities : list, tuple or float
        if omitted, spec contains the probabilities |n|
        the list (p0, p1, ...pn) contains the probabilities of the corresponding
        x-values from spec. |n|
        alternatively, if a float is given (e.g. 1), all x-values
        have equal probability. The value is not important.

    randomstream : randomstream
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed

    Note
    ----
    p0+p1=...+pn>0 |n|
    all densities are auto scaled according to the sum of p0 to pn,
    so no need to have p0 to pn add up to 1 or 100. |n|
    The x-values can be any type. |n|
    If it is a salabim distribution, not the distribution,
    but a sample will be returned when calling sample.
    '''

    def __init__(self, spec, probabilities=omitted, randomstream=omitted):
        self._x = [0]  # just a place holder
        self._cum = [0]
        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream

        sump = 0
        sumxp = 0
        hasmean = True
        if probabilities is omitted:
            spec = list(spec)

            if not spec:
                raise SalabimError('no arguments specified')
            while len(spec) > 0:
                x = spec.pop(0)
                if not spec:
                    raise SalabimError(
                        'uneven number of parameters specified')
                self._x.append(x)
                p = spec.pop(0)
                sump += p
                self._cum.append(sump)
                if isinstance(x, _Distribution):
                    x = x._mean
                try:
                    sumxp += float(x) * p
                except:
                    hasmean = False
        else:
            spec = list(spec)
            if isinstance(probabilities, (list, tuple)):
                probabilities = list(probabilities)
            else:
                probabilities = len(spec) * [1]
            if len(spec) != len(probabilities):
                raise SalabimError(
                    'length of x-values does not match length of probabilities')

            while len(spec) > 0:
                x = spec.pop(0)
                self._x.append(x)
                p = probabilities.pop(0)
                sump += p
                self._cum.append(sump)
                if isinstance(x, _Distribution):
                    x = x._mean
                try:
                    sumxp += float(x) * p
                except:
                    hasmean = False

        if sump == 0:
            raise SalabimError('at least one probability should be >0')

        self._cum = [x / sump for x in self._cum]
        if hasmean:
            self._mean = sumxp / sump
        else:
            self._mean = nan

    def __repr__(self):
        return 'Pdf'

    def print_info(self):
        print('Pdf distribution ' + hex(id(self)))
        print('  randomstream=' + hex(id(self.randomstream)))

    def sample(self):
        '''
        Returns
        -------
        Sample of the distribution : any (usually float)
        '''
        r = self.randomstream.random()
        for cum, x in zip(self._cum, self._x):
            if r <= cum:
                if isinstance(x, _Distribution):
                    return x.sample()
                return x

    def mean(self):
        '''
        Returns
        -------
        mean of the distribution : float
            if the mean can't be calculated (if not all x-values are scalars or distributions),
            nan will be returned.
        '''
        return self._mean


class Distribution(_Distribution):
    '''
    Generate a distribution from a string

    Parameters
    ----------
    spec : str
        - string containing a valid salabim distribution, where only the first
          letters are relevant and casing is not important. Note that Erlang,
          Cdf and Poisson require at least two letters
          (Er, Cd and Po)
        - string containing one float (c1), resulting in Constant(c1)
        - string containing two floats seperated by a comma (c1,c2),
          resulting in a Uniform(c1,c2)
        - string containing three floats, separated by commas (c1,c2,c3),
          resulting in a Triangular(c1,c2,c3)

    randomstream : randomstream
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed |n|

    Note
    ----
    The randomstream in the specifying string is ignored. |n|
    It is possible to use expressions in the specification, as long these
    are valid within the context of the salabim module, which usually implies
    a global variable of the salabim package.

    Examples
    --------
    Uniform(13)  ==> Uniform(13) |n|
    Uni(12,15)   ==> Uniform(12,15) |n|
    UNIF(12,15)  ==> Uniform(12,15) |n|
    N(12,3)      ==> Normal(12,3) |n|
    Tri(10,20).  ==> Triangular(10,20,15) |n|
    10.          ==> Constant(10) |n|
    12,15        ==> Uniform(12,15) |n|
    (12,15)      ==> Uniform(12,15) |n|
    Exp(a)       ==> Exponential(100), provided sim.a=100 |n|
    E(2)         ==> Exponential(2)
    Er(2,3)      ==> Erlang(2,3)
    '''

    def __init__(self, spec, randomstream=omitted):

        spec_orig = spec

        sp = spec.split('(')
        pre = sp[0].upper().strip()

        # here we have either a string starting with a ( of no ( at all
        if (pre == '') or not('(' in spec):
            spec = spec.replace(')', '')  # get rid of closing parenthesis
            spec = spec.replace('(', '')  # get rid of starting parenthesis
            sp = spec.split(',')
            if len(sp) == 1:
                c1 = sp[0]
                spec = 'Constant({})'.format(c1)
            elif len(sp) == 2:
                c1 = sp[0]
                c2 = sp[1]
                spec = 'Uniform({},{})'.format(c1, c2)
            elif len(sp) == 3:
                c1 = sp[0]
                c2 = sp[1]
                c3 = sp[2]
                spec = 'Triangular({},{},{})'.format(c1, c2, c3)
            else:
                raise SalabimError('incorrect specifier', spec_orig)

        else:
            for distype in ('Uniform', 'Constant', 'Triangular', 'Exponential', 'Normal',
              'Cdf', 'Pdf', 'Weibull', 'Gamma', 'Erlang', 'Beta', 'IntUniform', 'Poisson'):
                if pre == distype.upper()[:len(pre)]:
                    sp[0] = distype
                    spec = '('.join(sp)
                    break

        d = eval(spec)

        if randomstream is omitted:
            self.randomstream = random
        else:
            randomstream_check(randomstream)
            self.randomstream = randomstream
        self._distribution = d
        self._mean = d._mean

    def __repr__(self):
        return self._distribution.__repr__()

    def print_info(self):
        self._distribution.print_info()

    def sample(self):
        '''
        Returns
        -------
        Sample of the  distribution : any (usually float)
        '''
        self._distribution.randomstream = self.randomstream
        return self._distribution.sample()

    def mean(self):
        '''
        Returns
        -------
        Mean of the distribution : float
        '''
        return self._mean


class State(object):
    '''
    State

    Parameters
    ----------
    name : str
        name of the state |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    value : any, preferably printable
        initial value of the state |n|
        if omitted, False

    monitor : bool
        if True (default) , the waiters queue and the value are monitored |n|
        if False, monitoring is disabled.

    type : str
        specifies how the state values are monitored. Using a
        int, uint of float type results in less memory usage and better
        performance. Note that you should avoid the number not to use
        as this is used to indicate 'off'

        -  'any' (default) stores values in a list. This allows for
           non numeric values. In calculations the values are
           forced to a numeric value (0 if not possible) do not use -inf
        -  'bool' bool (False, True). Actually integer >= 0 <= 254 1 byte do not use 255
        -  'int8' integer >= -127 <= 127 1 byte do not use -128
        -  'uint8' integer >= 0 <= 254 1 byte do not use 255
        -  'int16' integer >= -32767 <= 32767 2 bytes do not use -32768
        -  'uint16' integer >= 0 <= 65534 2 bytes do not use 65535
        -  'int32' integer >= -2147483647 <= 2147483647 4 bytes do not use -2147483648
        -  'uint32' integer >= 0 <= 4294967294 4 bytes do not use 4294967295
        -  'int64' integer >= -9223372036854775807 <= 9223372036854775807 8 bytes do not use -9223372036854775808
        -  'uint64' integer >= 0 <= 18446744073709551614 8 bytes do not use 18446744073709551615
        -  'float' float 8 bytes do not use -inf

    animation_objects : list or tuple
        overrides the deafult animation_object method |n|
        the method should have a header like |n|
        ``def animation_objects(self, value):`` |n|
        and should return a list or tuple of animation objects, which
        will be used when the state changes value. |n|
        The default method displays a square of size 40. If the value
        is a valid color, that will be the color of the square. Otherwise,
        the square will be black with the value displayed in white in
        the centre.

    env : Environment
        environment to be used |n|
        if omitted, default_env is used
    '''
    def __init__(self, name=omitted, value=False, type='any',
      monitor=True, animation_objects=omitted, env=omitted, *args, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        _set_name(name, self.env._nameserializeState, self)
        self._value = value
        self._animate_on = False
        self._aos = []
        self._waiters = Queue(
            name='waiters of ' + self.name(),
            monitor=monitor, env=self.env, _isinternal=True)
        self.value = MonitorTimestamp(
            name='Value of ' + self.name(),
            initial_tally=value, monitor=monitor, type=type, env=self.env)
        if animation_objects is not omitted:
            self.animation_objects = animation_objects.__get__(self, State)
        if self.env._trace:
            self.env.print_trace(
                '', '', self.name() + ' create',
                'value= ' + str(self._value))
        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of a state.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments will be passed
        '''
        pass

    def animate(self, x=500, y=100, on=True):
        '''
        turns on/off animation for the state

        Parameters
        ----------
        x : float
            x-position of the animation|n|
            default: 500

        y : float
            y-position of the animation |n|
            default: 100

        on : bool
            if True (default) do animate the state. If False, do not animate.
        '''

        self._animate_on = on
        if on:
            self._animate_x = x
            self._animate_y = y
            self._animate_update()
        else:
            self._remove_from_aos()

    def _remove_from_aos(self):
        for ao in self._aos:
            ao.remove()
        self._aos = []

    def animation_objects(self, value):
        if str(value).lower() in colornames():
            ao1 = Animate(rectangle0=(-20, -20, 20, 20), fillcolor0=value, linewidth0=0)
            return (ao1,)
        else:
            ao1 = Animate(rectangle0=(-20, -20, 20, 20), fillcolor0=self.env._foreground_color, linewidth0=0)
            ao2 = Animate(text=str(value), textcolor0=self.env._background_color, anchor='c')
            return ao1, ao2

    def _animate_update(self):
        self._remove_from_aos()
        for ao in self.animation_objects(self._value):
            ao.x0 = self._animate_x
            ao.y0 = self._animate_y
            self._aos.append(ao)

    def __repr__(self):
        return objectclass_to_str(self) + ' (' + self.name() + ')'

    def print_info(self):
        print(objectclass_to_str(self) + ' ' + hex(id(self)))
        print('  name=' + self.name())
        print('  value=' + str(self._value))
        if self._waiters:
            print('  waiting component(s):')
            mx = self._waiters._head.successor
            while mx != self._waiters._tail:
                c = mx.component
                mx = mx.successor
                values = ''
                for s, value, valuetype in c._waits:
                    if s == self:
                        if values != '':
                            values = values + ', '
                        values = values + str(value)

                print('    ' + pad(c.name(), 20), ' value(s): ' + values)
        else:
            print('  no waiting components')

    def __call__(self):
        return self._value

    def get(self):
        '''
        get value of the state

        Returns
        -------
        value of the state : any
            Instead of this method, the state can also be called directly, like |n|

            level = sim.State('level') |n|
            ... |n|
            print(level()) |n|
            print(level.get())  # identical |n|
        '''
        return self._value

    def set(self, value=True):
        '''
        set the value of the state

        Parameters
        ----------
        value : any (preferably printable)
            if omitted, True |n|
            if there is a change, the waiters queue will be checked
            to see whether there are waiting components to be honored

        Note
        ----
        This method is identical to reset, except the default value is True.
        '''
        if self.env._trace:
            self.env.print_trace('', '', self.name() + ' set', 'value = ' + str(value))
        if self._value != value:
            self._value = value
            self.value.tally(value)
            if self._animate_on:
                self._animate_update()
            self._trywait()

    def reset(self, value=False):
        '''
        reset the value of the state

        Parameters
        ----------
        value : any (preferably printable)
            if omitted, False |n|
            if there is a change, the waiters queue will be checked
            to see whether there are waiting components to be honored

        Note
        ----
        This method is identical to set, except the default value is False.
        '''
        if self.env._trace:
            self.env.print_trace('', '', self.name() + ' reset', 'value = ' + str(value))
        if self._value != value:
            self._value = value
            self.value.tally(value)
            if self._animate_on:
                self._animate_update()
            self._trywait()

    def trigger(self, value=True, value_after=omitted, max=inf):
        '''
        triggers the value of the state

        Parameters
        ----------
        value : any (preferably printable)
            if omitted, True |n|

        value_after : any (preferably printable)
            after the trigger, this will be the new value. |n|
            if omitted, return to the the before the trigger.

        max : int
            maximum number of components to be honored for the trigger value |n|
            default: inf

        Note
        ----
            The value of the state will be set to value, then at most
            max waiting components for this state  will be honored and next
            the value will be set to value_after and again checked for possible
            honors.
        '''
        if value_after is omitted:
            value_after = self._value
        if self.env._trace:
            self.env.print_trace('', '', self.name() + ' trigger',
                ' value = ' + str(value) + ' --> ' + str(value_after) +
                ' allow ' + str(max) + ' components')
        self._value = value
        self.value.tally(value)  # strictly speaking, not required
        self._trywait(max)
        self._value = value_after
        self.value.tally(value_after)
        if self._animate_on:
            self._animate_update()
        self._trywait()

    def _trywait(self, max=inf):
        mx = self._waiters._head.successor
        while mx != self._waiters._tail:
            c = mx.component
            mx = mx.successor
            if c._trywait():
                max -= 1
                if max == 0:
                    return

    def monitor(self, value=omitted):
        '''
        enables/disables the state monitors and timestamped monitors

        Parameters
        ----------
        value : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if not specified, no change

        Note
        ----
        it is possible to individually control requesters().monitor(),
            value.monitor()
        '''
        self.requesters().monitor(value)
        self.value.monitor(value)

    def reset_monitors(self, monitor=omitted):
        '''
        resets the timestamped monitor for the state's value and the monitors of the waiters queue

        Parameters
        ----------
        monitor : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, no change of monitoring state

        '''
        self._waiters.reset_monitors(monitor)
        self.value.reset()

    def _get_value(self):
        return self._value

    def name(self):
        '''
        Returns
        -------
        Name of the state : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the state (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the state : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number

    def print_statistics(self):
        '''
        prints a summary of statistics of the state
        '''
        print('Statistics of {} at {}'.format(self.name(), fn(self.env._now - self.env._offset, 13, 3)))
        self.waiters().length.print_statistics(show_header=False, show_legend=True, do_indent=True)
        print()
        self.waiters().length_of_stay.print_statistics(show_header=False, show_legend=False, do_indent=True)
        print()
        self.value.print_statistics(show_header=False, show_legend=False, do_indent=True)

    def waiters(self):
        '''
        Returns
        -------
        queue containing all components waiting for this state : Queue
        '''
        return self._waiters


class Resource(object):
    '''
    Resource

    Parameters
    ----------
    name : str
        name of the resource |n|
        if the name ends with a period (.),
        auto serializing will be applied |n|
        if the name end with a comma,
        auto serializing starting at 1 will be applied |n|
        if omitted, the name will be derived from the class
        it is defined in (lowercased)

    capacity : float
        capacity of the resouce |n|
        if omitted, 1

    anonymous : bool
        anonymous specifier |n|
        if True, claims are not related to any component. This is useful
        if the resource is actually just a level. |n|
        if False, claims belong to a component.

    monitor : bool
        if True (default) , the requesters queue, the claimers queue,
        the capacity, the available_quantity and the claimed_quantity are monitored |n|
        if False, monitoring is disabled.

    env : Environment
        environment to be used |n|
        if omitted, default_env is used
    '''

    def __init__(self, name=omitted, capacity=1,
                 anonymous=False, monitor=True, env=omitted, *args, **kwargs):
        if env is omitted:
            self.env = g.default_env
        else:
            self.env = env
        self._capacity = capacity
        _set_name(name, self.env._nameserializeResource, self)
        self._requesters = Queue(
            name='requesters of ' + self.name(),
            monitor=monitor, env=self.env, _isinternal=True)
        self._claimers = Queue(
            name='claimers of ' + self.name(),
            monitor=monitor, env=self.env, _isinternal=True)
        self._claimed_quantity = 0
        self._anonymous = anonymous
        self._minq = inf
        self.capacity = MonitorTimestamp(
            'Capacity of ' + self.name(),
            initial_tally=capacity, monitor=monitor, type='float', env=self.env)
        self.claimed_quantity = MonitorTimestamp(
            'Claimed quantity of ' + self.name(),
            initial_tally=0, monitor=monitor, type='float', env=self.env)
        self.available_quantity = MonitorTimestamp(
            'Available quantity of ' + self.name(),
            initial_tally=capacity, monitor=monitor, type='float', env=self.env)
        if self.env._trace:
            self.env.print_trace(
                '', '', self.name() + ' create',
                'capacity=' + str(self._capacity) + (' anonymous' if self._anonymous else ''))
        self.setup(*args, **kwargs)

    def setup(self):
        '''
        called immediately after initialization of a resource.

        by default this is a dummy method, but it can be overridden.

        only keyword arguments are passed
        '''
        pass

    def reset_monitors(self, monitor=omitted):
        '''
        resets the resource monitors  and timestamped monitors

        Parameters
        ----------
        monitor : bool
            if True, monitoring will be on. |n|
            if False, monitoring is disabled |n|
            if omitted, no change of monitoring state

        Note
        ----
            it is possible to reset individual monitoring with
            claimers().reset_monitors(),
            requesters().reset_monitors,
            capacity.reset(),
            available_quantity.reset() or
            claimed_quantity.reset()
        '''

        self.requesters().reset_monitors(monitor)
        self.claimers().reset_monitors(monitor)
        self.capacity.reset(monitor)
        self.available_quantity.reset(monitor)
        self.claimed_quantity.reset(monitor)

    def print_statistics(self):
        '''
        prints a summary of statistics of a resource
        '''
        print('Statistics of {} at {:13.3f}'.format(self.name(), self.env._now - self.env._offset))
        show_legend = True
        for q in [self.requesters(), self.claimers()]:
            q.length.print_statistics(show_header=False, show_legend=show_legend, do_indent=True)
            show_legend = False
            print()
            q.length_of_stay.print_statistics(show_header=False, show_legend=show_legend, do_indent=True)
            print()

        for m in [self.capacity, self.available_quantity, self.claimed_quantity]:
            m.print_statistics(show_header=False, show_legend=show_legend, do_indent=True)
            print()

    def monitor(self, value):
        '''
        enables/disables the resource monitors  and timestamped monitors

        Parameters
        ----------
        value : bool
            if True, monitoring is enabled |n|
            if False, monitoring is disabled |n|

        Note
        ----
        it is possible to individually control monitoring with claimers().monitor()
        and requesters().monitor(), capacity.monitor(), available_quantity.monitor)
        or claimed_quantity.monitor()
        '''
        self.requesters().monitor(value)
        self.claimers().monitor(value)
        self.capacity.monitor(value)
        self.available_quantity.monitor(value)
        self.claimed_quantity.monitor(value)

    def __repr__(self):
        return objectclass_to_str(self) + ' (' + self.name() + ')'

    def print_info(self):
        print(objectclass_to_str(self) + ' ' + hex(id(self)))
        print('  name=' + self.name())
        print('  capacity=' + str(self._capacity))
        if self._requesters:
            print('  requesting component(s):')
            mx = self._requesters._head.successor
            while mx != self._requesters._tail:
                c = mx.component
                mx = mx.successor
                print('    ' + pad(c.name(), 20) +
                    ' quantity=' + str(c._requests[self]))
        else:
            print('  no requesting components')

        print('  claimed_quantity=' + str(self._claimed_quantity))
        if self._claimed_quantity >= 0:
            if self._anonymous:
                print('  not claimed by any components,' +
                    ' because the resource is anonymous')
            else:
                print('  claimed by:')
                mx = self._claimers._head.successor
                while mx != self._claimers._tail:
                    c = mx.component
                    mx = mx.successor
                    print('    ' + pad(c.name(), 20) +
                        ' quantity=' + str(c._claims[self]))

    def _tryrequest(self):
        mx = self._requesters._head.successor
        while mx != self._requesters._tail:
            if self._minq > (self._capacity - self._claimed_quantity + 1e-8):
                break  # inpossible to honor any more requests
            c = mx.component
            mx = mx.successor
            c._tryrequest()

    def release(self, quantity=omitted):
        '''
        releases all claims or a specified quantity

        Parameters
        ----------
        quantity : float
            quantity to be released |n|
            if not specified, the resource will be emptied completely |n|
            for non-anonymous resources, all components claiming from this resource
            will be released.

        Note
        ----
        quantity may not be specified for a non-anomymous resoure
        '''

        if self._anonymous:
            if quantity is omitted:
                q = self._claimed_quantity
            else:
                q = quantity

            self._claimed_quantity -= q
            if self._claimed_quantity < 1e-8:
                self._claimed_quantity = 0
            self.claimed_quantity.tally(self._claimed_quantity)
            self.available_quantity.tally(self._capacity - self._claimed_quantity)
            self._tryrequest()

        else:
            if quantity is not omitted:
                raise SalabimError(
                    'no quantity allowed for non-anonymous resource')

            mx = self._claimers._head.successor
            while mx != self._tail:
                c = mx.component
                mx = mx.successor
                c.release(self)

    def requesters(self):
        '''
        Return
        ------
        queue containing all components with not yet honored requests: Queue
        '''
        return self._requesters

    def claimers(self):
        '''
        Returns
        -------
        queue with all components claiming from the resource: Queue
            will be an empty queue for an anonymous resource
        '''
        return self._claimers

    def set_capacity(self, cap):
        '''
        Parameters
        ----------
        cap : float or int
            capacity of the resource |n|
            this may lead to honoring one or more requests. |n|
            if omitted, no change
        '''
        self._capacity = cap
        self.capacity.tally(self._capacity)
        self.available_quantity.tally(self._capacity - self._claimed_quantity)
        self._tryrequest()

    def name(self):
        '''
        Returns
        -------
        Name of the resource : str
        '''
        return self._name

    def base_name(self):
        '''
        Returns
        -------
        base name of the resource (the name used at initialization): str
        '''
        return self._base_name

    def sequence_number(self):
        '''
        Returns
        -------
        sequence_number of the resource : int
            (the sequence number at initialization) |n|
            normally this will be the integer value of a serialized name,
            but also non serialized names (without a dot or a comma at the end)
            will be numbered)
        '''
        return self._sequence_number


def colornames():
    '''
    available colornames
    
    Returns
    -------
    dict with name of color as key, #rrggbb or #rrggbbaa as value : dict
    '''
    if not hasattr(colornames, 'cached'):
        colornames.cached = pickle.loads(b'(dp0\nVfuchsia\np1\nV#FF00FF\np2\nsV\np3\nV#00000000\np4\nsVtransparent\np5\ng4\nsVpalevioletred\np6\nV#DB7093\np7\nsVskyblue\np8\nV#87CEEB\np9\nsVpaleturquoise\np10\nV#AFEEEE\np11\nsVcadetblue\np12\nV#5F9EA0\np13\nsVorangered\np14\nV#FF4500\np15\nsVsteelblue\np16\nV#4682B4\np17\nsVdimgray\np18\nV#696969\np19\nsVdarkseagreen\np20\nV#8FBC8F\np21\nsV60%gray\np22\nV#999999\np23\nsVroyalblue\np24\nV#4169E1\np25\nsVmediumblue\np26\nV#0000CD\np27\nsVgoldenrod\np28\nV#DAA520\np29\nsVmediumvioletred\np30\nV#C71585\np31\nsVblueviolet\np32\nV#8A2BE2\np33\nsVgainsboro\np34\nV#DCDCDC\np35\nsVdarkred\np36\nV#8B0000\np37\nsVrosybrown\np38\nV#BC8F8F\np39\nsVgold\np40\nV#FFD700\np41\nsVcoral\np42\nV#FF7F50\np43\nsVwhite\np44\nV#FFFFFF\np45\nsVdarkcyan\np46\nV#008B8B\np47\nsVblack\np48\nV#000000\np49\nsVorchid\np50\nV#DA70D6\np51\nsVmediumturquoise\np52\nV#48D1CC\np53\nsVlightgreen\np54\nV#90EE90\np55\nsVlime\np56\nV#00FF00\np57\nsVpapayawhip\np58\nV#FFEFD5\np59\nsVchocolate\np60\nV#D2691E\np61\nsV40%gray\np62\nV#666666\np63\nsVoldlace\np64\nV#FDF5E6\np65\nsVdarkblue\np66\nV#00008B\np67\nsVsilver\np68\nV#C0C0C0\np69\nsVaquamarine\np70\nV#7FFFD4\np71\nsVlightcoral\np72\nV#F08080\np73\nsVcyan\np74\nV#00FFFF\np75\nsVdodgerblue\np76\nV#1E90FF\np77\nsV10%gray\np78\nV#191919\np79\nsVmidnightblue\np80\nV#191970\np81\nsVgreen\np82\nV#008000\np83\nsVlightsalmon\np84\nV#FFA07A\np85\nsVazure\np86\nV#F0FFFF\np87\nsVred\np88\nV#FF0000\np89\nsVlightpink\np90\nV#FFB6C1\np91\nsVwhitesmoke\np92\nV#F5F5F5\np93\nsVyellow\np94\nV#FFFF00\np95\nsVlawngreen\np96\nV#7CFC00\np97\nsVmagenta\np98\ng2\nsVlightsteelblue\np99\nV#B0C4DE\np100\nsVolivedrab\np101\nV#6B8E23\np102\nsVlightslategray\np103\nV#778899\np104\nsVslategray\np105\nV#708090\np106\nsVlightblue\np107\nV#ADD8E6\np108\nsVmoccasin\np109\nV#FFE4B5\np110\nsVmediumspringgreen\np111\nV#00FA9A\np112\nsVlightgray\np113\nV#D3D3D3\np114\nsVseashell\np115\nV#FFF5EE\np116\nsVdarkkhaki\np117\nV#BDB76B\np118\nsVslateblue\np119\nV#6A5ACD\np120\nsVaqua\np121\ng75\nsVpalegoldenrod\np122\nV#EEE8AA\np123\nsVdeeppink\np124\nV#FF1493\np125\nsVdarkgreen\np126\nV#006400\np127\nsVblanchedalmond\np128\nV#FFEBCD\np129\nsVturquoise\np130\nV#40E0D0\np131\nsVnavy\np132\nV#000080\np133\nsVtomato\np134\nV#FF6347\np135\nsVyellowgreen\np136\nV#9ACD32\np137\nsVpeachpuff\np138\nV#FFDAB9\np139\nsV30%gray\np140\nV#464646\np141\nsVpink\np142\nV#FFC0CB\np143\nsVpalegreen\np144\nV#98FB98\np145\nsVlightskyblue\np146\nV#87CEFA\np147\nsVchartreuse\np148\nV#7FFF00\np149\nsVmediumorchid\np150\nV#BA55D3\np151\nsVolive\np152\nV#808000\np153\nsVdarkorange\np154\nV#FF8C00\np155\nsVbeige\np156\nV#F5F5DC\np157\nsVforestgreen\np158\nV#228B22\np159\nsVmediumpurple\np160\nV#9370DB\np161\nsVmintcream\np162\nV#F5FFFA\np163\nsVhotpink\np164\nV#FF69B4\np165\nsVdarkgoldenrod\np166\nV#B8860B\np167\nsVpowderblue\np168\nV#B0E0E6\np169\nsVhoneydew\np170\nV#F0FFF0\np171\nsVsalmon\np172\nV#FA8072\np173\nsVsnow\np174\nV#FFFAFA\np175\nsVmistyrose\np176\nV#FFE4E1\np177\nsVkhaki\np178\nV#F0E68C\np179\nsVmediumaquamarine\np180\nV#66CDAA\np181\nsVdarksalmon\np182\nV#E9967A\np183\nsValiceblue\np184\nV#F0F8FF\np185\nsVdarkturquoise\np186\nV#00CED1\np187\nsVlightyellow\np188\nV#FFFFE0\np189\nsVwheat\np190\nV#F5DEB3\np191\nsVlightseagreen\np192\nV#20B2AA\np193\nsVlightcyan\np194\nV#E0FFFF\np195\nsVantiquewhite\np196\nV#FAEBD7\np197\nsVsaddlebrown\np198\nV#8B4513\np199\nsVmediumseagreen\np200\nV#3CB371\np201\nsV70%gray\np202\nV#B2B2B2\np203\nsVsienna\np204\nV#A0522D\np205\nsVcornflowerblue\np206\nV#6495ED\np207\nsVseagreen\np208\nV#2E8B57\np209\nsVfloralwhite\np210\nV#FFFAF0\np211\nsVivory\np212\nV#FFFFF0\np213\nsVcornsilk\np214\nV#FFF8DC\np215\nsVindianred\np216\nV#CD5C5C\np217\nsVplum\np218\nV#DDA0DD\np219\nsV90%gray\np220\nV#E6E6E6\np221\nsVgreenyellow\np222\nV#ADFF2F\np223\nsVteal\np224\nV#008080\np225\nsVbrown\np226\nV#A52A2A\np227\nsVdarkslategray\np228\nV#2F4F4F\np229\nsVpurple\np230\nV#800080\np231\nsVviolet\np232\nV#EE82EE\np233\nsVdeepskyblue\np234\nV#00BFFF\np235\nsVghostwhite\np236\nV#F8F8FF\np237\nsVburlywood\np238\nV#DEB887\np239\nsVblue\np240\nV#0000FF\np241\nsVcrimson\np242\nV#DC143C\np243\nsVindigo\np244\nV#4B0082\np245\nsV20%gray\np246\nV#333333\np247\nsVdarkmagenta\np248\nV#8B008B\np249\nsV80%gray\np250\nV#CCCCCC\np251\nsVlightgoldenrodyellow\np252\nV#FAFAD2\np253\nsVtan\np254\nV#D2B48C\np255\nsVlimegreen\np256\nV#32CD32\np257\nsVlemonchiffon\np258\nV#FFFACD\np259\nsVbisque\np260\nV#FFE4C4\np261\nsVfirebrick\np262\nV#B22222\np263\nsVnavajowhite\np264\nV#FFDEAD\np265\nsVnone\np266\ng4\nsVmaroon\np267\nV#800000\np268\nsV50%gray\np269\nV#7F7F7F\np270\nsVdarkgray\np271\nV#A9A9A9\np272\nsVorange\np273\nV#FFA500\np274\nsVlavenderblush\np275\nV#FFF0F5\np276\nsVdarkorchid\np277\nV#9932CC\np278\nsVlavender\np279\nV#E6E6FA\np280\nsVspringgreen\np281\nV#00FF7F\np282\nsVthistle\np283\nV#D8BFD8\np284\nsVlinen\np285\nV#FAF0E6\np286\nsVdarkolivegreen\np287\nV#556B2F\np288\nsVdarkslateblue\np289\nV#483D8B\np290\nsVgray\np291\nV#808080\np292\nsVdarkviolet\np293\nV#9400D3\np294\nsVperu\np295\nV#CD853F\np296\nsVsandybrown\np297\nV#FAA460\np298\nsVmediumslateblue\np299\nV#7B68EE\np300\ns.')  # NOQA
    return colornames.cached


def salabim_logo_red_white_200():
    #  picture created from salabim logo red white 200.png
    from PIL import Image
    import io
    import base64
    if not hasattr(salabim_logo_red_white_200, 'cached'):
        salabim_logo_red_white_200.cached = Image.open(io.BytesIO(base64.b64decode(''
           ' '  # NOQA
           ''.encode('ascii')))).convert('RGBA')
    return salabim_logo_red_white_200.cached


def salabim_logo_red_black_200():
    #  picture created from salabim logo red black 200.png
    from PIL import Image
    import io
    import base64
    if not hasattr(salabim_logo_red_black_200, 'cached'):
        salabim_logo_red_black_200.cached = Image.open(io.BytesIO(base64.b64decode(''
           ' '  # NOQA
           ''.encode('ascii')))).convert('RGBA')
    return salabim_logo_red_black_200.cached


def hex_to_rgb(v):
    if v == '':
        return(0, 0, 0, 0)
    if v[0] == '#':
        v = v[1:]
    if len(v) == 6:
        return int(v[:2], 16), int(v[2:4], 16), int(v[4:6], 16)
    if len(v) == 8:
        return int(v[:2], 16), int(v[2:4], 16), int(v[4:6], 16), int(v[6:8], 16)
    raise SalabimError('Incorrect value' + str(v))


def spec_to_image(spec):
    '''
    convert an image specification to an image

    Parameters
    ----------
    image : str or PIL.Image.Image
        if str: filename of file to be loaded |n|
        if '': dummy image will be returned |n|
        if PIL.Image.Image: return this image untranslated

    Returns
    -------
    image : PIL.Image.Image
    '''
    if isinstance(spec, str):
        if spec == '':
            im = Image.new('RGBA', (0, 0), (0, 0, 0, 0))
        else:
            im = Image.open(spec)
            im = im.convert('RGBA')
            return im
    else:
        return spec


def _i(p, v0, v1):
    if v0 == v1:
        return v0  # avoid rounding problems
    return (1 - p) * v0 + p * v1


def interpolate(t, t0, t1, v0, v1):
    '''
    does linear interpolation

    Parameters
    ----------
    t : float
        value to be interpolated from

    t0: float
        f(t0)=v0

    t1: float
        f(t1)=v1

    v0: float, list or tuple
        f(t0)=v0

    v1: float, list or tuple
        f(t1)=v1 |n|
        if list or tuple, len(v0) should equal len(v1)

    Returns
    -------
    linear interpolation between v0 and v1 based on t between t0 and t : float or tuple

    Note
    ----
    Note that no extrapolation is done, so if t<t0 ==> v0  and t>t1 ==> v1 |n|
    This function is heavily used during animation.
    '''
    if v0 == v1:
        return v0
    if (v0 is None) or (v1 is None):
        return None
    if t1 == inf:
        return v0
    if t0 == t1:
        return v1
    if t0 > t1:
        (t0, t1) = (t1, t0)
        (v0, v1) = (v1, v0)
    if t <= t0:
        return v0
    if t >= t1:
        return v1
    p = (0.0 + t - t0) / (t1 - t0)
    if isinstance(v0, (list, tuple)):
        return tuple((_i(p, x0, x1) for x0, x1 in zip(v0, v1)))
    else:
        return _i(p, v0, v1)


def _set_name(name, _nameserialize, object):
    if name is omitted:
        name = objectclass_to_str(object).lower() + '.'
    elif len(name) <= 1:
        if name == '':
            name = objectclass_to_str(object).lower()
        elif name == '.':
            name = objectclass_to_str(object).lower() + '.'
        elif name == ',':
            name = objectclass_to_str(object).lower() + ','

    object._base_name = name

    if name in _nameserialize:
        sequence_number = _nameserialize[name] + 1
        _nameserialize[name] = sequence_number

    else:
        if name.endswith(','):
            _nameserialize[name] = 1
            sequence_number = 1
        else:
            _nameserialize[name] = 0
            sequence_number = 0

    if name.endswith('.'):
        object._name = name + str(sequence_number)
    elif name.endswith(','):
        object._name = name[:-1] + '.' + str(sequence_number)
    else:
        object._name = name
    object._sequence_number = sequence_number


def _decode_name(name):
    if isinstance(name, tuple):
        return name[0] + name[1].name()
    else:
        return name


def pad(txt, n):
    if n <= 0:
        return ''
    else:
        return txt.ljust(n)[:n]


def rpad(txt, n):
    return txt.rjust(n)[:n]


def fn(x, l, d):
    if math.isnan(x):
        return ('{:' + str(l) + 's}').format('')
    if x >= 10**(l - d - 1):
        return ('{:' + str(l) + '.' + str(l - d - 3) + 'e}').format(x)
    if x == int(x):
        return ('{:' + str(l - d - 1) + 'd}{:' + str(d + 1) + 's}').format(int(x), '')
    return ('{:' + str(l) + '.' + str(d) + 'f}').format(x)


def randomstream_check(randomstream):
    if not isinstance(randomstream, random.Random):
        raise SalabimError('randomstream is not an instance of random.Random()')


def type_to_typecode_off(type):
    lookup = {
        'bool': ('B', 255),
        'int8': ('b', -128),
        'uint8': ('B', 255),
        'int16': ('h', -32768),
        'uint16': ('H', 65535),
        'int32': ('i', -2147483648),
        'uint32': ('I', 4294967295),
        'int64': ('l', -9223372036854775808),
        'uint64': ('L', 18446744073709551615),
        'float': ('d', -inf),
        'double': ('d', -inf),
        'any': ('', -inf)
        }
    return lookup[type]


def list_to_array(l):
    float_result = array.array('d')
    int_result = array.array('l')
    int_ok = True
    for v in l:
        try:
            vfloat = float(v)
        except:
            vfloat = 0
        float_result.append(vfloat)
        if int_ok:
            try:
                vint = int(v)
            except:
                vint = 0
            if vint == vfloat:
                try:
                    int_result.append(vint)  # this may fail in case of a too large value
                except:
                    int_ok = False
            else:
                int_ok = False

    if int_ok:
        return int_result
    else:
        return float_result


def merge_blanks(*l):
    '''
    merges all non blank elements of l, separated by a blank

    Parameters
    ----------
    *l : elements to be merged : str

    Returns
    -------
    string with merged elements of l : str
    '''
    return ' '.join(x for x in l if x)


def normalize(s):
    res = ''
    for c in s.upper():
        if (c.isalpha() or c.isdigit()):
            res = res + c
    return res


def time_to_string(t):
    if t == inf:
        s = 'inf'
    else:
        s = '{:10.3f}'.format(t)
    return rpad(s, 10)


def _urgenttxt(urgent):
    if urgent:
        return '!'
    else:
        return ' '


def _modetxt(mode):
    if mode is None:
        return ''
    else:
        return 'mode=' + str(mode)


def objectclass_to_str(object):
    return str(type(object)).split('.')[-1].split("'")[0]


def _get_caller_frame():
    stack = inspect.stack()
    filename0 = inspect.getframeinfo(stack[0][0]).filename
    for i in range(len(inspect.stack())):
        frame = stack[i][0]
        if filename0 != inspect.getframeinfo(frame).filename:
            break
    return frame


def de_none(l):
    result = []
    for index, item in enumerate(l):
        if item is None:
            result.append(l[index - 2])
        else:
            result.append(item)
    return result


def data():
    return 'data'


def current():
    return 'current'


def standby():
    return 'standby'


def passive():
    return 'passive'


def scheduled():
    return 'scheduled'


def requesting():
    return 'requesting'


def waiting():
    return 'waiting'


def random_seed(seed, randomstream=omitted):
    '''
    Parameters
    ----------
    seed : hashable object, usually int
        the seed for random, equivalent to random.seed() |n|
        if None, a purely random value (based on the current time) will be used
        (not reproducable) |n|

    randomstream: randomstream
        randomstream to be used |n|
        if omitted, random will be used |n|
        if used as random.Random(12299)
        it assigns a new stream with the specified seed
    '''
    if randomstream is omitted:
        randomstream = random
    randomstream.seed(seed)


def _std_fonts():
    # the names of the standard fonts are generated by ttf fontdict.py on the standard development machine
    if not hasattr(_std_fonts, 'cached'):
        _std_fonts.cached = pickle.loads(b'(dp0\nVHuxley_Titling\np1\nVHuxley Titling\np2\nsVGlock___\np3\nVGlockenspiel\np4\nsVPENLIIT_\np5\nVPenultimateLightItal\np6\nsVERASMD\np7\nVEras Medium ITC\np8\nsVNirmala\np9\nVNirmala UI\np10\nsVebrimabd\np11\nVEbrima Bold\np12\nsVostrich-dashed\np13\nVOstrich Sans Dashed Medium\np14\nsVLato-Hairline\np15\nVLato Hairline\np16\nsVLTYPEO\np17\nVLucida Sans Typewriter Oblique\np18\nsVbnmachine\np19\nVBN Machine\np20\nsVLTYPEB\np21\nVLucida Sans Typewriter Bold\np22\nsVBOOKOSI\np23\nVBookman Old Style Italic\np24\nsVEmmett__\np25\nVEmmett\np26\nsVCURLZ___\np27\nVCurlz MT\np28\nsVhandmeds\np29\nVHand Me Down S (BRK)\np30\nsVsegoesc\np31\nVSegoe Script\np32\nsVTCM_____\np33\nVTw Cen MT\np34\nsVJosefinSlab-ThinItalic\np35\nVJosefin Slab Thin Italic\np36\nsVSTENCIL\np37\nVStencil\np38\nsVsanss___\np39\nVSansSerif\np40\nsVBOD_CI\np41\nVBodoni MT Condensed Italic\np42\nsVGreek_i\np43\nVGreek Diner Inline TT\np44\nsVHTOWERT\np45\nVHigh Tower Text\np46\nsVTCCB____\np47\nVTw Cen MT Condensed Bold\np48\nsVCools___\np49\nVCoolsville\np50\nsVbnjinx\np51\nVBN Jinx\np52\nsVFREESCPT\np53\nVFreestyle Script\np54\nsVGARA\np55\nVGaramond\np56\nsVDejaVuSansMono\np57\nVDejaVu Sans Mono Book\np58\nsVCALVIN__\np59\nVCalvin\np60\nsVGIL_____\np61\nVGill Sans MT\np62\nsVCandaraz\np63\nVCandara Bold Italic\np64\nsVVollkorn-Bold\np65\nVVollkorn Bold\np66\nsVariblk\np67\nVArial Black\np68\nsVGOTHIC\np69\nVCentury Gothic\np70\nsVMAIAN\np71\nVMaiandra GD\np72\nsVBSSYM7\np73\nVBookshelf Symbol 7\np74\nsVAcme____\np75\nVAcmeFont\np76\nsVDetente_\np77\nVDetente\np78\nsVCandarai\np79\nVCandara Italic\np80\nsVFTLTLT\np81\nVFootlight MT Light\np82\nsVGILC____\np83\nVGill Sans MT Condensed\np84\nsVLFAXD\np85\nVLucida Fax Demibold\np86\nsVNIAGSOL\np87\nVNiagara Solid\np88\nsVLFAXI\np89\nVLucida Fax Italic\np90\nsVCandarab\np91\nVCandara Bold\np92\nsVFRSCRIPT\np93\nVFrench Script MT\np94\nsVLBRITE\np95\nVLucida Bright\np96\nsVFRABK\np97\nVFranklin Gothic Book\np98\nsVostrich-bold\np99\nVOstrich Sans Bold\np100\nsVTCCM____\np101\nVTw Cen MT Condensed\np102\nsVcorbelz\np103\nVCorbel Bold Italic\np104\nsVTCMI____\np105\nVTw Cen MT Italic\np106\nsVethnocen\np107\nVEthnocentric\np108\nsVVINERITC\np109\nVViner Hand ITC\np110\nsVROCKB\np111\nVRockwell Bold\np112\nsVconsola\np113\nVConsolas\np114\nsVcorbeli\np115\nVCorbel Italic\np116\nsVPENUL___\np117\nVPenultimate\np118\nsVMAGNETOB\np119\nVMagneto Bold\np120\nsVisocp___\np121\nVISOCP\np122\nsVQUIVEIT_\np123\nVQuiverItal\np124\nsVARLRDBD\np125\nVArial Rounded MT Bold\np126\nsVJosefinSlab-SemiBold\np127\nVJosefin Slab SemiBold\np128\nsVntailub\np129\nVMicrosoft New Tai Lue Bold\np130\nsVflubber\np131\nVFlubber\np132\nsVBASKVILL\np133\nVBaskerville Old Face\np134\nsVGILB____\np135\nVGill Sans MT Bold\np136\nsVPERTILI\np137\nVPerpetua Titling MT Light\np138\nsVLato-HairlineItalic\np139\nVLato Hairline Italic\np140\nsVComfortaa-Light\np141\nVComfortaa Light\np142\nsVtrebucit\np143\nVTrebuchet MS Italic\np144\nsVmalgunbd\np145\nVMalgun Gothic Bold\np146\nsVITCBLKAD\np147\nVBlackadder ITC\np148\nsVsansso__\np149\nVSansSerif Oblique\np150\nsVCALISTBI\np151\nVCalisto MT Bold Italic\np152\nsVsyastro_\np153\nVSyastro\np154\nsVSamsungIF_Md\np155\nVSamsung InterFace Medium\np156\nsVHombre__\np157\nVHombre\np158\nsVseguiemj\np159\nVSegoe UI Emoji\np160\nsVFRAHVIT\np161\nVFranklin Gothic Heavy Italic\np162\nsVJUICE___\np163\nVJuice ITC\np164\nsVFRAMDCN\np165\nVFranklin Gothic Medium Cond\np166\nsVseguisb\np167\nVSegoe UI Semibold\np168\nsVconsolai\np169\nVConsolas Italic\np170\nsVGLECB\np171\nVGloucester MT Extra Condensed\np172\nsVframd\np173\nVFranklin Gothic Medium\np174\nsVSCHLBKI\np175\nVCentury Schoolbook Italic\np176\nsVCENTAUR\np177\nVCentaur\np178\nsVromantic\np179\nVRomantic\np180\nsVBOD_CB\np181\nVBodoni MT Condensed Bold\np182\nsVverdana\np183\nVVerdana\np184\nsVTangerine_Regular\np185\nVTangerine\np186\nsVseguili\np187\nVSegoe UI Light Italic\np188\nsVNunito-Regular\np189\nVNunito\np190\nsVSCHLBKB\np191\nVCentury Schoolbook Bold\np192\nsVGOTHICB\np193\nVCentury Gothic Bold\np194\nsVpalai\np195\nVPalatino Linotype Italic\np196\nsVBKANT\np197\nVBook Antiqua\np198\nsVLato-Italic\np199\nVLato Italic\np200\nsVPERBI___\np201\nVPerpetua Bold Italic\np202\nsVGOTHICI\np203\nVCentury Gothic Italic\np204\nsVROCKBI\np205\nVRockwell Bold Italic\np206\nsVLTYPEBO\np207\nVLucida Sans Typewriter Bold Oblique\np208\nsVAmeth___\np209\nVAmethyst\np210\nsVyearsupplyoffairycakes\np211\nVYear supply of fairy cakes\np212\nsVGILBI___\np213\nVGill Sans MT Bold Italic\np214\nsVBOOKOS\np215\nVBookman Old Style\np216\nsVVollkorn-Italic\np217\nVVollkorn Italic\np218\nsVswiss\np219\nVSwis721 BT Roman\np220\nsVcomsc\np221\nVCommercialScript BT\np222\nsVchinyen\np223\nVChinyen Normal\np224\nsVeurr____\np225\nVEuroRoman\np226\nsVROCK\np227\nVRockwell\np228\nsVPERTIBD\np229\nVPerpetua Titling MT Bold\np230\nsVCHILLER\np231\nVChiller\np232\nsVtechb___\np233\nVTechnicBold\np234\nsVLato-Light\np235\nVLato Light\np236\nsVOUTLOOK\np237\nVMS Outlook\np238\nsVmtproxy6\np239\nVProxy 6\np240\nsVdutcheb\np241\nVDutch801 XBd BT Extra Bold\np242\nsVgadugib\np243\nVGadugi Bold\np244\nsVBOD_CR\np245\nVBodoni MT Condensed\np246\nsVmtproxy7\np247\nVProxy 7\np248\nsVnobile_bold\np249\nVNobile Bold\np250\nsVELEPHNT\np251\nVElephant\np252\nsVCOPRGTL\np253\nVCopperplate Gothic Light\np254\nsVMTCORSVA\np255\nVMonotype Corsiva\np256\nsVconsolaz\np257\nVConsolas Bold Italic\np258\nsVBOOKOSBI\np259\nVBookman Old Style Bold Italic\np260\nsVtrebuc\np261\nVTrebuchet MS\np262\nsVcomici\np263\nVComic Sans MS Italic\np264\nsVJosefinSlab-BoldItalic\np265\nVJosefin Slab Bold Italic\np266\nsVMycalc__\np267\nVMycalc\np268\nsVmarlett\np269\nVMarlett\np270\nsVsymeteo_\np271\nVSymeteo\np272\nsVcandles_\np273\nVCandles\np274\nsVbobcat\np275\nVBobcat Normal\np276\nsVLSANSDI\np277\nVLucida Sans Demibold Italic\np278\nsVINFROMAN\np279\nVInformal Roman\np280\nsVsf movie poster2\np281\nVSF Movie Poster\np282\nsVcomicz\np283\nVComic Sans MS Bold Italic\np284\nsVcracj___\np285\nVCracked Johnnie\np286\nsVcourbd\np287\nVCourier New Bold\np288\nsVItali___\np289\nVItalianate\np290\nsVITCEDSCR\np291\nVEdwardian Script ITC\np292\nsVcourbi\np293\nVCourier New Bold Italic\np294\nsVcalibrili\np295\nVCalibri Light Italic\np296\nsVgazzarelli\np297\nVGazzarelli\np298\nsVGabriola\np299\nVGabriola\np300\nsVVollkorn-BoldItalic\np301\nVVollkorn Bold Italic\np302\nsVromant__\np303\nVRomanT\np304\nsVisoct3__\np305\nVISOCT3\np306\nsVsegoeuib\np307\nVSegoe UI Bold\np308\nsVtimesbd\np309\nVTimes New Roman Bold\np310\nsVgoodtime\np311\nVGood Times\np312\nsVsegoeuii\np313\nVSegoe UI Italic\np314\nsVBOD_BLAR\np315\nVBodoni MT Black\np316\nsVhimalaya\np317\nVMicrosoft Himalaya\np318\nsVsegoeuil\np319\nVSegoe UI Light\np320\nsVPermanentMarker\np321\nVPermanent Marker\np322\nsVBOD_BLAI\np323\nVBodoni MT Black Italic\np324\nsVTCBI____\np325\nVTw Cen MT Bold Italic\np326\nsVarial\np327\nVArial\np328\nsVBrand___\np329\nVBrandish\np330\nsVsegoeuiz\np331\nVSegoe UI Bold Italic\np332\nsVswisscb\np333\nVSwis721 Cn BT Bold\np334\nsVPAPYRUS\np335\nVPapyrus\np336\nsVANTIC___\np337\nVAnticFont\np338\nsVGIGI\np339\nVGigi\np340\nsVENGR\np341\nVEngravers MT\np342\nsVsegmdl2\np343\nVSegoe MDL2 Assets\np344\nsVBRLNSDB\np345\nVBerlin Sans FB Demi Bold\np346\nsVLato-BoldItalic\np347\nVLato Bold Italic\np348\nsVholomdl2\np349\nVHoloLens MDL2 Assets\np350\nsVBRITANIC\np351\nVBritannic Bold\np352\nsVNirmalaB\np353\nVNirmala UI Bold\np354\nsVVollkorn-Regular\np355\nVVollkorn\np356\nsVStephen_\np357\nVStephen\np358\nsVbabyk___\np359\nVBaby Kruffy\np360\nsVHARVEST_\np361\nVHarvest\np362\nsVKUNSTLER\np363\nVKunstler Script\np364\nsVstylu\np365\nVStylus BT Roman\np366\nsVWINGDNG3\np367\nVWingdings 3\np368\nsVWINGDNG2\np369\nVWingdings 2\np370\nsVlucon\np371\nVLucida Console\np372\nsVCandara\np373\nVCandara\np374\nsVBERNHC\np375\nVBernard MT Condensed\np376\nsVtechnic_\np377\nVTechnic\np378\nsVLimou___\np379\nVLimousine\np380\nsVTCB_____\np381\nVTw Cen MT Bold\np382\nsVPirate__\np383\nVPirate\np384\nsVFrnkvent\np385\nVFrankfurter Venetian TT\np386\nsVromand__\np387\nVRomanD\np388\nsVLTYPE\np389\nVLucida Sans Typewriter\np390\nsVSHOWG\np391\nVShowcard Gothic\np392\nsVMOD20\np393\nVModern No. 20\np394\nsVostrich-rounded\np395\nVOstrich Sans Rounded Medium\np396\nsVJosefinSlab-Italic\np397\nVJosefin Slab Italic\np398\nsVneon2\np399\nVNeon Lights\np400\nsVpalabi\np401\nVPalatino Linotype Bold Italic\np402\nsVwoodcut\np403\nVWoodcut\np404\nsVToledo__\np405\nVToledo\np406\nsVverdanai\np407\nVVerdana Italic\np408\nsVSamsungIF_Rg\np409\nVSamsung InterFace\np410\nsVtrebucbd\np411\nVTrebuchet MS Bold\np412\nsVPALSCRI\np413\nVPalace Script MT\np414\nsVComfortaa-Regular\np415\nVComfortaa\np416\nsVmicross\np417\nVMicrosoft Sans Serif\np418\nsVseguisli\np419\nVSegoe UI Semilight Italic\np420\nsVtaile\np421\nVMicrosoft Tai Le\np422\nsVcour\np423\nVCourier New\np424\nsVparryhotter\np425\nVParry Hotter\np426\nsVgreekc__\np427\nVGreekC\np428\nsVRAGE\np429\nVRage Italic\np430\nsVMATURASC\np431\nVMatura MT Script Capitals\np432\nsVBASTION_\np433\nVBastion\np434\nsVREFSAN\np435\nVMS Reference Sans Serif\np436\nsVterminat\np437\nVTerminator Two\np438\nsVmmrtextb\np439\nVMyanmar Text Bold\np440\nsVgothici_\np441\nVGothicI\np442\nsVmonotxt_\np443\nVMonotxt\np444\nsVcorbelb\np445\nVCorbel Bold\np446\nsVVALKEN__\np447\nVValken\np448\nsVRowdyhe_\np449\nVRowdyHeavy\np450\nsVLato-Black\np451\nVLato Black\np452\nsVswisski\np453\nVSwis721 Blk BT Black Italic\np454\nsVcouri\np455\nVCourier New Italic\np456\nsVMTEXTRA\np457\nVMT Extra\np458\nsVsanssbo_\np459\nVSansSerif BoldOblique\np460\nsVl_10646\np461\nVLucida Sans Unicode\np462\nsVLato-BlackItalic\np463\nVLato Black Italic\np464\nsVseguibli\np465\nVSegoe UI Black Italic\np466\nsVGeotype\np467\nVGeotype TT\np468\nsVxfiles\np469\nVX-Files\np470\nsVjavatext\np471\nVJavanese Text\np472\nsVseguisym\np473\nVSegoe UI Symbol\np474\nsVverdanaz\np475\nVVerdana Bold Italic\np476\nsVGILI____\np477\nVGill Sans MT Italic\np478\nsVALGER\np479\nVAlgerian\np480\nsVAGENCYR\np481\nVAgency FB\np482\nsVnobile\np483\nVNobile\np484\nsVHaxton\np485\nVHaxton Logos TT\np486\nsVswissbo\np487\nVSwis721 BdOul BT Bold\np488\nsVBELLI\np489\nVBell MT Italic\np490\nsVBROADW\np491\nVBroadway\np492\nsVsegoepr\np493\nVSegoe Print\np494\nsVGILLUBCD\np495\nVGill Sans Ultra Bold Condensed\np496\nsVverdanab\np497\nVVerdana Bold\np498\nsVSalina__\np499\nVSalina\np500\nsVAGENCYB\np501\nVAgency FB Bold\np502\nsVAutumn__\np503\nVAutumn\np504\nsVGOUDOS\np505\nVGoudy Old Style\np506\nsVconstanz\np507\nVConstantia Bold Italic\np508\nsVPOORICH\np509\nVPoor Richard\np510\nsVPRISTINA\np511\nVPristina\np512\nsVLATINWD\np513\nVWide Latin\np514\nsVromanc__\np515\nVRomanC\np516\nsVLeelawUI\np517\nVLeelawadee UI\np518\nsVitalict_\np519\nVItalicT\np520\nsVostrich-regular\np521\nVOstrich Sans Medium\np522\nsVmonosbi\np523\nVMonospac821 BT Bold Italic\np524\nsVcambriai\np525\nVCambria Italic\np526\nsVisocp2__\np527\nVISOCP2\np528\nsVltromatic\np529\nVLetterOMatic!\np530\nsVbgothm\np531\nVBankGothic Md BT Medium\np532\nsVbgothl\np533\nVBankGothic Lt BT Light\np534\nsVSwkeys1\np535\nVSWGamekeys MT\np536\nsVCENSCBK\np537\nVCentury Schoolbook\np538\nsVgothicg_\np539\nVGothicG\np540\nsValmosnow\np541\nVAlmonte Snow\np542\nsVTangerine_Bold\np543\nVTangerine Bold\np544\nsVswisseb\np545\nVSwis721 Ex BT Bold\np546\nsVCOLONNA\np547\nVColonna MT\np548\nsVsupef___\np549\nVSuperFrench\np550\nsVTCCEB\np551\nVTw Cen MT Condensed Extra Bold\np552\nsVsylfaen\np553\nVSylfaen\np554\nsVcomicbd\np555\nVComic Sans MS Bold\np556\nsVRoland__\np557\nVRoland\np558\nsVELEPHNTI\np559\nVElephant Italic\np560\nsVmmrtext\np561\nVMyanmar Text\np562\nsVsymap___\np563\nVSymap\np564\nsVswissko\np565\nVSwis721 BlkOul BT Black\np566\nsVswissck\np567\nVSwis721 BlkCn BT Black\np568\nsVWhimsy\np569\nVWhimsy TT\np570\nsVsanssb__\np571\nVSansSerif Bold\np572\nsVtaileb\np573\nVMicrosoft Tai Le Bold\np574\nsVcomic\np575\nVComic Sans MS\np576\nsVGLSNECB\np577\nVGill Sans MT Ext Condensed Bold\np578\nsVColbert_\np579\nVColbert\np580\nsVJOKERMAN\np581\nVJokerman\np582\nsVARIALNB\np583\nVArial Narrow Bold\np584\nsVDOMIN___\np585\nVDominican\np586\nsVBRUSHSCI\np587\nVBrush Script MT Italic\np588\nsVCALLI___\np589\nVCalligraphic\np590\nsVFRADM\np591\nVFranklin Gothic Demi\np592\nsVJosefinSlab-LightItalic\np593\nVJosefin Slab Light Italic\np594\nsVsimplex_\np595\nVSimplex\np596\nsVphagspab\np597\nVMicrosoft PhagsPa Bold\np598\nsVswissek\np599\nVSwis721 BlkEx BT Black\np600\nsVscripts_\np601\nVScriptS\np602\nsVswisscl\np603\nVSwis721 LtCn BT Light\np604\nsVCASTELAR\np605\nVCastellar\np606\nsVdutchi\np607\nVDutch801 Rm BT Italic\np608\nsVnasaliza\np609\nVNasalization Medium\np610\nsVariali\np611\nVArial Italic\np612\nsVOpinehe_\np613\nVOpineHeavy\np614\nsVPLAYBILL\np615\nVPlaybill\np616\nsVROCCB___\np617\nVRockwell Condensed Bold\np618\nsVCALIST\np619\nVCalisto MT\np620\nsVCALISTB\np621\nVCalisto MT Bold\np622\nsVHATTEN\np623\nVHaettenschweiler\np624\nsVntailu\np625\nVMicrosoft New Tai Lue\np626\nsVCALISTI\np627\nVCalisto MT Italic\np628\nsVsegoeprb\np629\nVSegoe Print Bold\np630\nsVDAYTON__\np631\nVDayton\np632\nsVswissel\np633\nVSwis721 LtEx BT Light\np634\nsVmael____\np635\nVMael\np636\nsVisoct2__\np637\nVISOCT2\np638\nsVBorea___\np639\nVBorealis\np640\nsVwingding\np641\nVWingdings\np642\nsVONYX\np643\nVOnyx\np644\nsVmonosi\np645\nVMonospac821 BT Italic\np646\nsVtimesi\np647\nVTimes New Roman Italic\np648\nsVostrich-light\np649\nVOstrich Sans Condensed Light\np650\nsVseguihis\np651\nVSegoe UI Historic\np652\nsVNovem___\np653\nVNovember\np654\nsVOCRAEXT\np655\nVOCR A Extended\np656\nsVostrich-black\np657\nVOstrich Sans Black\np658\nsVnarrow\np659\nVPR Celtic Narrow Normal\np660\nsVitalic__\np661\nVItalic\np662\nsVmonosb\np663\nVMonospac821 BT Bold\np664\nsVPERB____\np665\nVPerpetua Bold\np666\nsVCreteRound-Regular\np667\nVCrete Round\np668\nsVcalibri\np669\nVCalibri\np670\nsVSCRIPTBL\np671\nVScript MT Bold\np672\nsVComfortaa-Bold\np673\nVComfortaa Bold\np674\nsVARIALN\np675\nVArial Narrow\np676\nsVHARNGTON\np677\nVHarrington\np678\nsVJosefinSlab-Bold\np679\nVJosefin Slab Bold\np680\nsVVIVALDII\np681\nVVivaldi Italic\np682\nsVhollh___\np683\nVHollywood Hills\np684\nsVBOD_R\np685\nVBodoni MT\np686\nsVSkinny__\np687\nVSkinny\np688\nsVLBRITED\np689\nVLucida Bright Demibold\np690\nsVframdit\np691\nVFranklin Gothic Medium Italic\np692\nsVsymusic_\np693\nVSymusic\np694\nsVgadugi\np695\nVGadugi\np696\nsVswissbi\np697\nVSwis721 BT Bold Italic\np698\nsVBOD_B\np699\nVBodoni MT Bold\np700\nsVERASDEMI\np701\nVEras Demi ITC\np702\nsVWaverly_\np703\nVWaverly\np704\nsVcompi\np705\nVCommercialPi BT\np706\nsVBOD_I\np707\nVBodoni MT Italic\np708\nsVconstan\np709\nVConstantia\np710\nsVARIALNBI\np711\nVArial Narrow Bold Italic\np712\nsVarialbi\np713\nVArial Bold Italic\np714\nsVJosefinSlab-Light\np715\nVJosefin Slab Light\np716\nsVBOD_CBI\np717\nVBodoni MT Condensed Bold Italic\np718\nsVwebdings\np719\nVWebdings\np720\nsVRAVIE\np721\nVRavie\np722\nsVROCC____\np723\nVRockwell Condensed\np724\nsVFELIXTI\np725\nVFelix Titling\np726\nsVRussrite\np727\nVRussel Write TT\np728\nsVisocteur\np729\nVISOCTEUR\np730\nsVLSANSD\np731\nVLucida Sans Demibold Roman\np732\nsVmalgun\np733\nVMalgun Gothic\np734\nsVheavyhea2\np735\nVHeavy Heap\np736\nsVGOUDYSTO\np737\nVGoudy Stout\np738\nsVVLADIMIR\np739\nVVladimir Script\np740\nsVARIALUNI\np741\nVArial Unicode MS\np742\nsVJosefinSlab-Thin\np743\nVJosefin Slab Thin\np744\nsVFRADMCN\np745\nVFranklin Gothic Demi Cond\np746\nsVBlackout-2am\np747\nVBlackout 2 AM\np748\nsVpalab\np749\nVPalatino Linotype Bold\np750\nsVDejaVuSansMono-Oblique\np751\nVDejaVu Sans Mono Oblique\np752\nsVANTQUABI\np753\nVBook Antiqua Bold Italic\np754\nsVswissc\np755\nVSwis721 Cn BT Roman\np756\nsVSPLASH__\np757\nVSplash\np758\nsVNIAGENG\np759\nVNiagara Engraved\np760\nsVCOPRGTB\np761\nVCopperplate Gothic Bold\np762\nsVBruss___\np763\nVBrussels\np764\nsVconsolab\np765\nVConsolas Bold\np766\nsVGOTHICBI\np767\nVCentury Gothic Bold Italic\np768\nsVmtproxy4\np769\nVProxy 4\np770\nsVmtproxy5\np771\nVProxy 5\np772\nsVromai___\np773\nVRomantic Italic\np774\nsVFRABKIT\np775\nVFranklin Gothic Book Italic\np776\nsVBELL\np777\nVBell MT\np778\nsVmtproxy1\np779\nVProxy 1\np780\nsVmtproxy2\np781\nVProxy 2\np782\nsVmtproxy3\np783\nVProxy 3\np784\nsVLCALLIG\np785\nVLucida Calligraphy Italic\np786\nsVphagspa\np787\nVMicrosoft PhagsPa\np788\nsVANTQUAI\np789\nVBook Antiqua Italic\np790\nsVmtproxy8\np791\nVProxy 8\np792\nsVmtproxy9\np793\nVProxy 9\np794\nsVLato-Bold\np795\nVLato Bold\np796\nsVtxt_____\np797\nVTxt\np798\nsVconstanb\np799\nVConstantia Bold\np800\nsVERASBD\np801\nVEras Bold ITC\np802\nsVLato-LightItalic\np803\nVLato Light Italic\np804\nsVRONDALO_\np805\nVRondalo\np806\nsVconstani\np807\nVConstantia Italic\np808\nsVBRLNSB\np809\nVBerlin Sans FB Bold\np810\nsVgeorgiaz\np811\nVGeorgia Bold Italic\np812\nsVgothice_\np813\nVGothicE\np814\nsVcalibriz\np815\nVCalibri Bold Italic\np816\nsVgeorgiab\np817\nVGeorgia Bold\np818\nsVLeelaUIb\np819\nVLeelawadee UI Bold\np820\nsVtimesbi\np821\nVTimes New Roman Bold Italic\np822\nsVPERI____\np823\nVPerpetua Italic\np824\nsVromab___\np825\nVRomantic Bold\np826\nsVBRLNSR\np827\nVBerlin Sans FB\np828\nsVBELLB\np829\nVBell MT Bold\np830\nsVgeorgiai\np831\nVGeorgia Italic\np832\nsVNirmalaS\np833\nVNirmala UI Semilight\np834\nsVdutchb\np835\nVDutch801 Rm BT Bold\np836\nsVdigifit\np837\nVDigifit Normal\np838\nsVROCKEB\np839\nVRockwell Extra Bold\np840\nsVgdt_____\np841\nVGDT\np842\nsVmonbaiti\np843\nVMongolian Baiti\np844\nsVsegoescb\np845\nVSegoe Script Bold\np846\nsVsymath__\np847\nVSymath\np848\nsVisoct___\np849\nVISOCT\np850\nsVTarzan__\np851\nVTarzan\np852\nsVsnowdrft\np853\nVSnowdrift\np854\nsVHTOWERTI\np855\nVHigh Tower Text Italic\np856\nsVCENTURY\np857\nVCentury\np858\nsVmalgunsl\np859\nVMalgun Gothic Semilight\np860\nsVseguibl\np861\nVSegoe UI Black\np862\nsVCreteRound-Italic\np863\nVCrete Round Italic\np864\nsVAlfredo_\np865\nVAlfredo\np866\nsVCOMMONS_\np867\nVCommons\np868\nsVLFAX\np869\nVLucida Fax\np870\nsVLBRITEI\np871\nVLucida Bright Italic\np872\nsVFRAHV\np873\nVFranklin Gothic Heavy\np874\nsVisocteui\np875\nVISOCTEUR Italic\np876\nsVManorly_\np877\nVManorly\np878\nsVBolstbo_\np879\nVBolsterBold Bold\np880\nsVsegoeui\np881\nVSegoe UI\np882\nsVNunito-Light\np883\nVNunito Light\np884\nsVIMPRISHA\np885\nVImprint MT Shadow\np886\nsVgeorgia\np887\nVGeorgia\np888\nsV18cents\np889\nV18thCentury\np890\nsVMOONB___\np891\nVMoonbeam\np892\nsVPER_____\np893\nVPerpetua\np894\nsVHansen__\np895\nVHansen\np896\nsVLato-Regular\np897\nVLato\np898\nsVBOUTON_International_symbols\np899\nVBOUTON International Symbols\np900\nsVCOOPBL\np901\nVCooper Black\np902\nsVmonos\np903\nVMonospac821 BT Roman\np904\nsVtahoma\np905\nVTahoma\np906\nsVcityb___\np907\nVCityBlueprint\np908\nsVswisscbi\np909\nVSwis721 Cn BT Bold Italic\np910\nsVEnliven_\np911\nVEnliven\np912\nsVLeelUIsl\np913\nVLeelawadee UI Semilight\np914\nsVCALIFR\np915\nVCalifornian FB\np916\nsVumath\np917\nVUniversalMath1 BT\np918\nsVswisscbo\np919\nVSwis721 BdCnOul BT Bold Outline\np920\nsVcomplex_\np921\nVComplex\np922\nsVBOOKOSB\np923\nVBookman Old Style Bold\np924\nsVMartina_\np925\nVMartina\np926\nsVromans__\np927\nVRomanS\np928\nsVmvboli\np929\nVMV Boli\np930\nsVCALIFI\np931\nVCalifornian FB Italic\np932\nsVGARABD\np933\nVGaramond Bold\np934\nsVebrima\np935\nVEbrima\np936\nsVTEMPSITC\np937\nVTempus Sans ITC\np938\nsVCALIFB\np939\nVCalifornian FB Bold\np940\nsVitalicc_\np941\nVItalicC\np942\nsVisocp3__\np943\nVISOCP3\np944\nsVscriptc_\np945\nVScriptC\np946\nsValiee13\np947\nVAlien Encounters\np948\nsVnobile_italic\np949\nVNobile Italic\np950\nsVGARAIT\np951\nVGaramond Italic\np952\nsVswissli\np953\nVSwis721 Lt BT Light Italic\np954\nsVCabinSketch-Bold\np955\nVCabinSketch Bold\np956\nsVcorbel\np957\nVCorbel\np958\nsVseguisbi\np959\nVSegoe UI Semibold Italic\np960\nsVSCHLBKBI\np961\nVCentury Schoolbook Bold Italic\np962\nsVasimov\np963\nVAsimov\np964\nsVLFAXDI\np965\nVLucida Fax Demibold Italic\np966\nsVBRADHITC\np967\nVBradley Hand ITC\np968\nsVswisscki\np969\nVSwis721 BlkCn BT Black Italic\np970\nsVGILSANUB\np971\nVGill Sans Ultra Bold\np972\nsVHARLOWSI\np973\nVHarlow Solid Italic Italic\np974\nsVHARVEIT_\np975\nVHarvestItal\np976\nsVcambriab\np977\nVCambria Bold\np978\nsVswissci\np979\nVSwis721 Cn BT Italic\np980\nsVcounb___\np981\nVCountryBlueprint\np982\nsVNotram__\np983\nVNotram\np984\nsVPENULLI_\np985\nVPenultimateLight\np986\nsVtahomabd\np987\nVTahoma Bold\np988\nsVMISTRAL\np989\nVMistral\np990\nsVpala\np991\nVPalatino Linotype\np992\nsVOLDENGL\np993\nVOld English Text MT\np994\nsVinductio\np995\nVInduction Normal\np996\nsVJosefinSlab-SemiBoldItalic\np997\nVJosefin Slab SemiBold Italic\np998\nsVMinerva_\np999\nVMinerva\np1000\nsVsymbol\np1001\nVSymbol\np1002\nsVcambriaz\np1003\nVCambria Bold Italic\np1004\nsVtrebucbi\np1005\nVTrebuchet MS Bold Italic\np1006\nsVtimes\np1007\nVTimes New Roman\np1008\nsVERASLGHT\np1009\nVEras Light ITC\np1010\nsVSteppes\np1011\nVSteppes TT\np1012\nsVREFSPCL\np1013\nVMS Reference Specialty\np1014\nsVPARCHM\np1015\nVParchment\np1016\nsVDejaVuSansMono-Bold\np1017\nVDejaVu Sans Mono Bold\np1018\nsVswisscli\np1019\nVSwis721 LtCn BT Light Italic\np1020\nsVLSANS\np1021\nVLucida Sans\np1022\nsVPhrasme_\np1023\nVPhrasticMedium\np1024\nsVDejaVuSansMono-BoldOblique\np1025\nVDejaVu Sans Mono Bold Oblique\np1026\nsVarialbd\np1027\nVArial Bold\np1028\nsVSNAP____\np1029\nVSnap ITC\np1030\nsVArchitectsDaughter\np1031\nVArchitects Daughter\np1032\nsVCorpo___\np1033\nVCorporate\np1034\nsVeurro___\np1035\nVEuroRoman Oblique\np1036\nsVimpact\np1037\nVImpact\np1038\nsVlittlelo\np1039\nVLittleLordFontleroy\np1040\nsVsimsunb\np1041\nVSimSun-ExtB\np1042\nsVARIALNI\np1043\nVArial Narrow Italic\np1044\nsVdutchbi\np1045\nVDutch801 Rm BT Bold Italic\np1046\nsVcalibrii\np1047\nVCalibri Italic\np1048\nsVDeneane_\np1049\nVDeneane\np1050\nsVFRADMIT\np1051\nVFranklin Gothic Demi Italic\np1052\nsVANTQUAB\np1053\nVBook Antiqua Bold\np1054\nsVcalibril\np1055\nVCalibri Light\np1056\nsVisocpeui\np1057\nVISOCPEUR Italic\np1058\nsVpanroman\np1059\nVPanRoman\np1060\nsVMelodbo_\np1061\nVMelodBold Bold\np1062\nsVcalibrib\np1063\nVCalibri Bold\np1064\nsVdistant galaxy 2\np1065\nVDistant Galaxy\np1066\nsVPacifico\np1067\nVPacifico\np1068\nsVnobile_bold_italic\np1069\nVNobile Bold Italic\np1070\nsVmsyi\np1071\nVMicrosoft Yi Baiti\np1072\nsVBOD_PSTC\np1073\nVBodoni MT Poster Compressed\np1074\nsVLSANSI\np1075\nVLucida Sans Italic\np1076\nsVcreerg__\np1077\nVCreepygirl\np1078\nsVsegoeuisl\np1079\nVSegoe UI Semilight\np1080\nsVvinet\np1081\nVVineta BT\np1082\nsVisocpeur\np1083\nVISOCPEUR\np1084\nsVtechl___\np1085\nVTechnicLite\np1086\nsVswissb\np1087\nVSwis721 BT Bold\np1088\nsVCLARE___\np1089\nVClarendon\np1090\nsVdutch\np1091\nVDutch801 Rm BT Roman\np1092\nsVLBRITEDI\np1093\nVLucida Bright Demibold Italic\np1094\nsVswisse\np1095\nVSwis721 Ex BT Roman\np1096\nsVswissk\np1097\nVSwis721 Blk BT Black\np1098\nsVswissi\np1099\nVSwis721 BT Italic\np1100\nsVfingerpop2\np1101\nVFingerpop\np1102\nsVswissl\np1103\nVSwis721 Lt BT Light\np1104\nsVBAUHS93\np1105\nVBauhaus 93\np1106\nsVVivian__\np1107\nVVivian\np1108\nsVgreeks__\np1109\nVGreekS\np1110\nsVGOUDOSI\np1111\nVGoudy Old Style Italic\np1112\nsVBOD_BI\np1113\nVBodoni MT Bold Italic\np1114\nsVLHANDW\np1115\nVLucida Handwriting Italic\np1116\nsVITCKRIST\np1117\nVKristen ITC\np1118\nsVBALTH___\np1119\nVBalthazar\np1120\nsVFORTE\np1121\nVForte\np1122\nsVJosefinSlab-Regular\np1123\nVJosefin Slab\np1124\nsVROCKI\np1125\nVRockwell Italic\np1126\nsVGOUDOSB\np1127\nVGoudy Old Style Bold\np1128\nsVLEELAWAD\np1129\nVLeelawadee\np1130\nsVLEELAWDB\np1131\nVLeelawadee Bold\np1132\nsVmarlett_0\np1133\nVMarlett\np1134\nsVmplus-1m-bold\np1135\nVM+ 1m bold\np1136\nsVmplus-1m-light\np1137\nVM+ 1m light\np1138\nsVmplus-1m-medium\np1139\nVM+ 1m medium\np1140\nsVmplus-1m-regular\np1141\nVM+ 1m\np1142\nsVmplus-1m-thin\np1143\nVM+ 1m thin\np1144\nsVMSUIGHUB\np1145\nVMicrosoft Uighur Bold\np1146\nsVMSUIGHUR\np1147\nVMicrosoft Uighur\np1148\nsVSamsungIF_Md_0\np1149\nVSamsung InterFace Medium\np1150\nsVSamsungIF_Rg_0\np1151\nVSamsung InterFace\np1152\nsVbahnschrift\np1153\nVBahnschrift\np1154\nsVBowlbyOneSC-Regular\np1155\nVBowlby One SC\np1156\nsVCabinSketch-Regular\np1157\nVCabin Sketch\np1158\nsVCookie-Regular\np1159\nVCookie\np1160\nsVCourgette-Regular\np1161\nVCourgette\np1162\nsVdead\np1163\nVDead Kansas\np1164\nsVDoppioOne-Regular\np1165\nVDoppio One\np1166\nsVeuphorig\np1167\nVEuphorigenic\np1168\nsVGreatVibes-Regular\np1169\nVGreat Vibes\np1170\nsVKalam-Bold\np1171\nVKalam Bold\np1172\nsVKalam-Light\np1173\nVKalam Light\np1174\nsVKalam-Regular\np1175\nVKalam\np1176\nsVLemon-Regular\np1177\nVLemon\np1178\nsVLimelight-Regular\np1179\nVLimelight\np1180\nsVMegrim\np1181\nVMegrim Medium\np1182\nsVMontserratSubrayada-Bold\np1183\nVMontserrat Subrayada Bold\np1184\nsVNotoSans-Regular\np1185\nVNoto Sans\np1186\nsVRussoOne-Regular\np1187\nVRusso One\np1188\nsVSigmarOne-Regular\np1189\nVSigmar One\np1190\nsVYellowtail-Regular\np1191\nVYellowtail\np1192\ns.')  # NOQA
    return _std_fonts.cached


def fonts():
    if not hasattr(fonts, 'font_list'):
        fonts.font_list = []
        if Pythonista:
            UIFont = objc_util.ObjCClass('UIFont')
            for family in UIFont.familyNames():
                family = str(family)
                try:
                    ImageFont.truetype(family)
                    fonts.font_list.append(((family,), family))
                except:
                    pass

                for name in UIFont.fontNamesForFamilyName_(family):
                    name = str(name)
                    fonts.font_list.append(((name,), name))

        salabim_dir = os.path.dirname(__file__)
        cur_dir = os.getcwd()
        dirs = [salabim_dir]
        if cur_dir != salabim_dir:
            dirs.append(cur_dir)
        if Windows:
            dirs.append(r'c:\windows\fonts')

        for dir in dirs:
            for file in glob.glob(dir + os.sep + '*.ttf'):
                fn = os.path.basename(file).split('.')[0]
                if fn in _std_fonts():
                    fullname = _std_fonts()[fn]
                else:
                    f = ImageFont.truetype(file, 12)
                    if f is None:
                        fullname = ''
                    else:
                        if str(f.font.style).lower() == 'regular':
                            fullname = str(f.font.family)
                        else:
                            fullname = str(f.font.family) + ' ' + str(f.font.style)
                if fullname != '':
                    if fn.lower() == fullname.lower():
                        fonts.font_list.append(((fullname,), file))
                    else:
                        fonts.font_list.append(((fn, fullname), file))
    return fonts.font_list


def standardfonts():
    return {
        '': 'calibri',
        'std': 'calibri',
        'mono': 'DejaVuSansMono',
        'narrow': 'mplus-1m-regular'
    }


def getfont(fontname, fontsize):  # fontsize in screen_coordinates!
    if hasattr(getfont, 'lookup'):
        if (fontname, fontsize) in getfont.lookup:
            return getfont.lookup[(fontname, fontsize)]
    else:
        getfont.lookup = {}

    if isinstance(fontname, str):
        fontlist1 = [fontname]
    else:
        fontlist1 = list(fontname)

    fontlist1.extend(['calibri', 'arial'])

    fontlist = [standardfonts().get(f.lower(), f) for f in fontlist1]

    result = None

    for ifont in fontlist:

        try:
            result = ImageFont.truetype(font=ifont, size=int(fontsize))
            break
        except:
            pass

        filename = ''
        for fns, ifilename in fonts():
            for fn in fns:
                if normalize(fn) == normalize(ifont):
                    filename = ifilename
                    break
            if filename != '':
                break
        if filename != '':
            try:
                result = ImageFont.truetype(font=filename, size=int(fontsize))
                break
            except:
                pass

    if result is None:
        result = ImageFont.loaddefault()  # last resort

    heightA = result.getsize('A')[1]
    getfont.lookup[(fontname, fontsize)] = result, heightA
    return result, heightA


def show_fonts():
    '''
    show (print) all available fonts on this machine
    '''
    fontnames = []
    for fns, ifilename in fonts():
        for fn in fns:
            fontnames.append(fn)
    fontnames.extend(standardfonts().keys())
    last = ''
    for font in sorted(fontnames, key=normalize):
        if font != last:  # remove duplicates
            print(font)
            last = font


def show_colornames():
    '''
    show (print) all available color names and their value.
    '''
    names = sorted(colornames().keys())
    for name in names:
        print('{:22s}{}'.format(name, colornames()[name]))


def arrow_polygon(size):
    '''
    creates a polygon tuple with a centerd arrow for use with sim.Animate

    Parameters
    ----------
    size : float
        length of the arrow
    '''
    size /= 4
    return (-2 * size, -size, 0, -size, 0, -2 * size, 2 * size, 0, 0, 2 * size, 0, size, -2 * size, size)


def centered_rectangle(width, height):
    '''
    creates a rectangle tuple with a centered rectangle for use with sim.Animate

    Parameters
    ----------
    width : float
        width of the rectangle

    height : float
        height of the rectangle
    '''
    return -width / 2, -height / 2, width / 2, height / 2


def can_animate(try_only=True):
    '''
    Tests whether animation is supported.

    Parameters
    ----------
    try_only : bool
        if True (default), the function does not raise an error when the required modules cannot be imported |n|
        if False, the function will only return if the required modules could be imported.

    Returns
    -------
    True, if required modules could be imported, False otherwise : bool
    '''
    global Image
    global ImageDraw
    global ImageFont
    global ImageTk
    global tkinter
    try:
        import PIL  # NOQA
        from PIL import Image
        from PIL import ImageDraw
        from PIL import ImageFont
        if not Pythonista:
            from PIL import ImageTk
    except ImportError:
        if try_only:
            return False
        raise SalabimError('PIL is required for animation. Install with pip install Pillow or see salabim manual')

    if not Pythonista:
        try:
            import tkinter
        except ImportError:
            try:
                import Tkinter as tkinter
            except ImportError:
                if try_only:
                    return False
                raise SalabimError('tkinter is required for animation')
    return True


def can_video(try_only=True):
    '''
    Tests whether video is supported.

    Parameters
    ----------
    try_only : bool
        if True (default), the function does not raise an error when the required modules cannot be imported |n|
        if False, the function will only return if the required modules could be imported.

    Returns
    -------
    True, if required modules could be imported, False otherwise : bool
    '''
    global cv2
    global np
    if Pythonista:
        if try_only:
            return False
        raise SalabimError('video production not supported on Pythonista')
    else:
        try:
            import cv2
            import numpy as np
        except ImportError:
            if try_only:
                return False
            if platform.python_implementation == 'PyPy':
                raise SalabimError('video production is not supported under PyPy.')
            else:
                raise SalabimError('cv2 required for video production. Install with pip install opencv-python')
    return True


def default_env():
    '''
    Returns
    -------
    default environment : Environment
    '''
    return g.default_env


def reset():
    '''
    resets global variables

    used internally at import of salabim

    might be useful for REPLs or for Pythonista
    '''
    try:
        g.default_env.video_close()
    except:
        pass
        
    g.default_env = None
    g.animation_env = None
    g.animation_scene = None
    g.in_draw = False
    

reset()

if __name__ == '__main__':
    try:
        import salabim_test
    except:
        print('test.py not found')
    else:
        salabim_test.test()
