#  Copyright 2020 Modelling, Simulation and Design Lab (MSDL) at
#  McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

"""This file contains all the GPSS functionality that is used in GPSS2DEVS."""

PREFIX = "GPSS2DEVS_%i_"
PREFIX_GEN = PREFIX % 0   # GENERATE blocks
PREFIX_DLY = PREFIX % 1   # ADVANCE and UNLINK
PREFIX_CRL = PREFIX % 2   # Controller
PREFIX_RES = PREFIX % 3   # Facility, Storage, LogicSwitch, Table, Queue, Chain
PREFIX_HLD = PREFIX % 4   # Hold
PREFIX_BLK = PREFIX % 5   # All other blocks.

from pypdevsbbl.extra.rngstreams import createStream, U01, setGeneratorType, Generators
setGeneratorType(Generators.RANDOM)   # Use the standard random module to better simulate GPSS's RNG

RN1 = createStream("RN1", 1)
RN2 = createStream("RN2", 2)
RN3 = createStream("RN3", 3)
RN4 = createStream("RN4", 4)
RN5 = createStream("RN5", 5)
RN6 = createStream("RN6", 6)
RN7 = createStream("RN7", 7)
RN8 = createStream("RN8", 8)

from pypdevsbbl.extra.rngstreams.distributions import uniformInt
def dist(y, mu, dev=0):
    """Standard GPSS distribution to use. Returns a random variable in :math:`mu \pm dev`.

    Args:
        y (numeric):    A random variable in :meth:`[0, 1)`.
        mu (int):       The mean.
        dev (int):      The deviation around the mean.
    """
    if dev == 0:
        return mu
    return uniformInt(y, mu-dev, mu+dev)

class Transaction:
    """A GPSS Transaction."""
    def __init__(self, id, time=None, priority = 0):
        self.id = id
        self.queues = 0
        # Make sure the parameters are strings of type kPn!
        self.parameters = {
            "M1": time,
            "PR": priority
        }

    def __eq__(self, other):
        return self.id == other.id

    def __hash__(self):
        return int(self.id)

    def __lt__(self, other):
        if self.priority() != other.priority():
            return self.priority() > other.priority()
        return self.id < other.id

    def __repr__(self):
        return "Transaction(%i, %s)" % (self.id, str(self.parameters.copy()))

    def priority(self):
        return self.parameters["PR"]


# TODO: naming convention of GPSS2DEVS???
def SNA(name: str, model, transaction: Transaction = None, time=0.0):
    """A lookup function for SNAs."""
    # TODO: Nj
    if name.startswith("W$"):
        return len(getattr(model, PREFIX_HLD + name[2:]).state["transactions"])
    if name.startswith("F$"):
        return len(model.controller.state["facilities"][name[2:]])[0] > 0
    # TODO: FR$, FC$, FT$
    if name.startswith("FN$"):
        return model.functions[name](model, transaction)
    if name.startswith("Q$"):
        return getattr(model, PREFIX_RES + name[2:]).state["size"]
    if name.startswith("QA$"):
        return getattr(model, PREFIX_RES + name[3:]).summary()["avgc"]
    if name.startswith("QM$"):
        return getattr(model, PREFIX_RES + name[3:]).state["max"]
    if name.startswith("QC$"):
        return getattr(model, PREFIX_RES + name[3:]).state["entry"]
    if name.startswith("QZ$"):
        return getattr(model, PREFIX_RES + name[3:]).state["zeros"]
    if name.startswith("QT$"):
        queue = getattr(model, PREFIX_RES + name[3:])
        return queue.state["entry"] / queue.state["time"]
    if name.startswith("QX$"):
        queue = getattr(model, PREFIX_RES + name[3:])
        return (queue.state["entry"] - queue.state["zeros"]) / queue.state["time"]
    # TODO: Storages
    if name.startswith("S$"):
        return getattr(model, PREFIX_RES + name[2:]).state["N"]
    # TODO: System Attributes
    if name == "RN1":
        return int(U01(RN1) * 1000)
    if name == "RN2":
        return int(U01(RN2) * 1000)
    if name == "RN3":
        return int(U01(RN3) * 1000)
    if name == "RN4":
        return int(U01(RN4) * 1000)
    if name == "RN5":
        return int(U01(RN5) * 1000)
    if name == "RN6":
        return int(U01(RN6) * 1000)
    if name == "RN7":
        return int(U01(RN7) * 1000)
    if name == "RN8":
        return int(U01(RN8) * 1000)
    if name.startswith("TB$"):
        return getattr(model, PREFIX_RES + name[3:]).mean()
    if name.startswith("TC$"):
        return getattr(model, PREFIX_RES + name[3:]).count()
    if name.startswith("TD$"):
        return getattr(model, PREFIX_RES + name[3:]).std()
    if name.startswith("PF") or name.startswith("PH") or name.startswith("PB") or name.startswith("PL") \
            or name == "PR":
        return transaction.parameters[name]
    if name == "M1":
        return time - transaction.parameters["M1"]
    # TODO: MP$P$
    # TODO: User Chains
    if name.startswith("CH$"):
        return len(getattr(model, PREFIX_RES + name[3:]).state["chain"])
    if name.startswith("V$"):
        return model.variables[name]()
    if name.startswith("XH$") or name.startswith("X$"):
        return model.savevalues[name]

    # When not found, it is either float, int or str
    try:
        return int(name)
    except ValueError:
        pass
    try:
        return float(name)
    except ValueError:
        pass
    return name


# TODO: other function types
class Function:
    """Corresponds to GPSS's ``FUNCTION`` statement."""
    def __init__(self, sna, mapping: list, continuous=True):
        self.sna = sna
        self.mapping = mapping
        self.continuous = continuous

    @staticmethod
    def parse(mapping: str, size: int):
        """Parses the GPSS code.

        Args:
            mapping (str):  A mapping in the form of ``x1,y1/x2,y2/x3,y3...``
            size (int):     The size of the array.

        Returns:
            The mapping list that is required for the :meth:`__init__`.
        """
        res = [tuple([float(y) for y in x.split(",")]) for x in mapping.replace("\n", "").split("/")]
        assert len(res) == size and all([len(x) == 2 for x in res])
        return res

    def __call__(self, model, transaction):
        x = SNA(self.sna, model, transaction)
        if self.sna.startswith("RN"):
            x /= 1000
        if not self.continuous:
            return [m[1] for m in self.mapping if m[0] == x][0]
        for i in range(len(self.mapping)):
            px, py = self.mapping[i]
            if px == x:
                return py
            if x < px:
                if i == 0:
                    return py
                ox, oy = self.mapping[i - 1]
                return ((py - oy) / (px - ox)) * (x - ox) + oy
        return self.mapping[-1][1]


## ----------------------------------------------------------------------------------------- ##
from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
from pypdevs.infinity import INFINITY

# TODO: SNA: TIME!!

class _PT(AtomicDEVS):
    """Base class for simple pass through."""
    def __init__(self, name):
        super(_PT, self).__init__(name)
        self.state = { "transaction": None }

        self.input = self.addInPort("input")
        self.output = self.addOutPort("output")

    def timeAdvance(self):
        if self.state["transaction"] is not None:
            return 0.0
        return INFINITY

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return { self.output: self.state["transaction"] }
        return {}

    def intTransition(self):
        self.state["transaction"] = None
        return self.state

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        return self.state


class _AT(_PT):
    """Base class for all GPSS building blocks that just alter the transaction."""
    def __init__(self, name):
        super(_AT, self).__init__(name)
        self.state["time"] = 0.0

    def alter(self):
        """Changes the transaction."""
        raise NotImplementedError()

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
            self.alter()
        return self.state


class TERMINATE(_PT):
    def __init__(self, name, count='0'):
        super(TERMINATE, self).__init__(name)
        self.count = count
        self.state["update"] = False

    def timeAdvance(self):
        if self.state["update"]:
            return 0.0
        return INFINITY

    def outputFnc(self):
        if self.state["update"]:
            return { self.output: (self.state["transaction"], SNA(self.count, self.parent, self.state["transaction"])) }
        return {}

    def intTransition(self):
        self.state["transaction"] = None
        self.state["update"] = False
        return self.state

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
            self.state["update"] = True
        return self.state


class ASSIGN(_AT):
    def __init__(self, name, parameters, assign):
        super(ASSIGN, self).__init__(name)
        self.parameters = [parameters] if isinstance(parameters, str) else parameters
        self.assign = assign

    def alter(self):
        for param in self.parameters:
            P = self.state["transaction"].parameters.setdefault(param, 0)
            self.state["transaction"].parameters[param] = self.assign(P)


class MARK(_AT):
    def __init__(self, name, field="M1"):
        super(MARK, self).__init__(name)
        self.field = field

    def alter(self):
        # TODO: what if the field does not exist?
        if self.field in self.state["transaction"].parameters:
            self.state["transaction"].parameters[self.field] = self.state["time"]


class SEIZE(_PT):
    def __init__(self, name):
        super(SEIZE, self).__init__(name)
        self.state["facility"] = ([], False)

        self.facility = self.addInPort("facility")
        self.request = self.addOutPort("request")
        self.halting = self.addOutPort("halting")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            halting = not self.can_access(*self.state["facility"])
            res = {
                self.output: self.state["transaction"],
                self.halting: halting
            }
            if not halting:
                res[self.request] = self.state["transaction"]
            return res
        return {}

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        if self.facility in inputs:
            self.state["facility"] = inputs[self.facility]
        return self.state

    def can_access(self, name, trns, preempted):
        return len(trns) == 0


class RELEASE(_PT):
    def __init__(self, name):
        super(RELEASE, self).__init__(name)
        self.facility = self.addOutPort("facility")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return {
                self.output: self.state["transaction"],
                self.facility: self.state["transaction"]
            }
        return {}


class PREEMPT(SEIZE):
    def __init__(self, name, interrupt=False):
        super(PREEMPT, self).__init__(name)
        self.interrupt = interrupt

    def can_access(self, name, trns, preempted):
        if self.interrupt:  # INTERRUPT mode
            return not preempted
        else:               # PRIORITY  mode
            if not preempted:
                return True
            return trns[-1].priotity() < self.state["transaction"].priority()


class RETURN(_PT):
    def __init__(self, name):
        super(RETURN, self).__init__(name)
        self.state["facility"] = ([], False)
        self.facility = self.addInPort("facility")
        self.release = self.addOutPort("release")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            trns, p = self.state["facility"]
            assert self.state["transaction"] == trns[-1] and p
            return {
                self.output: self.state["transaction"],
                self.release: self.state["transaction"]
            }
        return {}

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        if self.facility in inputs:
            self.state["facility"] = inputs[self.facility]
        return self.state


class ENTER(_PT):
    def __init__(self, name, size=1):
        super(ENTER, self).__init__(name)
        self.state["storage"] = INFINITY
        self.size = size

        self.storage = self.addInPort("storage")
        self.request = self.addOutPort("request")
        self.halting = self.addOutPort("halting")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            halting = self.state["storage"] < self.size
            res = {
                self.output: self.state["transaction"],
                self.halting: halting
            }
            if not halting:
                res[self.request] = self.size
            return res
        return {}

    def extTransition(self, inputs):
        if self.storage in inputs:
            self.state["storage"] = inputs[self.storage]
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        return self.state


class LEAVE(_PT):
    def __init__(self, name, size=1):
        super(LEAVE, self).__init__(name)
        self.size = size
        self.storage = self.addOutPort("storage")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return {
                self.output: self.state["transaction"],
                self.storage: self.size
            }
        return {}


from pypdevs import logger
class QUEUE(_PT):
    def __init__(self, name, size=1):
        super(QUEUE, self).__init__(name)
        self.size = size
        self.queue = self.addOutPort("queue")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return {
                self.output: self.state["transaction"],
                self.queue: (self.state["transaction"], self.size)
            }
        return {}

    def extTransition(self, inputs):
        super().extTransition(inputs)
        self.state["transaction"].queues += 1
        if self.state["transaction"].queues > 5:
            logger.warn("The transaction was enqueued in more than 5 queues.")
        return self.state


class DEPART(QUEUE):
    def extTransition(self, inputs):
        _PT.extTransition(self, inputs)
        self.state["transaction"].queues -= 1
        if self.state["transaction"].queues < 0:
            logger.warn("The transaction has departed a queue it does not belong to.")
            self.state["transaction"].queues = 0
        return self.state


class TABULATE(_PT):
    def __init__(self, name, size=1):
        super(TABULATE, self).__init__(name)
        self.size = size
        self.table = self.addOutPort("table")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return {
                self.output: self.state["transaction"],
                self.table: (self.state["transaction"], self.size)
            }
        return {}


class LINK(_PT):
    def __init__(self, name, B, C=None):
        super(LINK, self).__init__(name)
        self.B = B
        self.C = None
        if C is not None:
            self.C = self.addOutPort("C")

        self.state["chain"] = []
        self.chain = self.addInPort("chain")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            if self.C is not None and len(self.state["chain"]) == 0:
                return { self.C: self.state["transaction"] }
            return { self.output: (self.state["transaction"], self.B) }
        return {}

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        if self.chain in inputs:
            self.state["chain"] = inputs[self.chain]
        return self.state


class UNLINK(AtomicDEVS):
    def __init__(self, name, count, D, E, F=None):
        super(UNLINK, self).__init__(name)
        self.count = count
        self.D = D
        self.E = E
        self.state = {
            "transaction": None,
            "chain": [],
            "unlink": []
        }

        self.chain = self.addInPort("chain")
        self.input = self.addInPort("input")

        self.B = self.addOutPort("B")
        self.output = self.addOutPort("output")
        self.unlink = self.addOutPort("unlink")

        self.F = None
        if F is not None:
            self.F = self.addOutPort("F")

    def timeAdvance(self):
        if self.state["transaction"] is not None or len(self.state["unlink"]) > 0:
            return 0.0
        return INFINITY

    def outputFnc(self): # The transaction must be handled BEFORE the unlinks
        if self.state["transaction"] is not None:
            if self.F is not None and len(self.state["unlink"]) == 0:
                return { self.F: self.state["transaction"] }
            return { self.output: self.state["transaction"] }
        if len(self.state["unlink"]) > 0:
            return {
                self.B: self.state["unlink"][0],
                self.unlink: self.state["unlink"][0]
            }
        return {}

    def intTransition(self):
        if self.state["transaction"] is not None:
            self.state["transaction"] = None
        else:
            self.state["unlink"].pop(0)
        return self.state

    def extTransition(self, inputs):
        if self.chain in inputs:
            self.state["chain"] = inputs[self.chain]
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
            match = []
            if self.D == "BACK" and self.E is None:
                match = [x[0] for x in reversed(self.state["chain"][-self.count:])]
            elif self.D is None and self.E is None:
                match = [x[0] for x in self.state["chain"][:self.count]]
            elif self.D in self.state["transaction"].parameters:
                if self.E is None:
                    param = self.state["transaction"].parameters[self.D]
                    match = [T[0] for T in self.state["chain"] if T[0].parameters[self.D] == param]
                else:
                    match = [T[0] for T in self.state["chain"] if T[0].parameters[self.D] == self.E]
            else: # TODO: boolean value?
                pass
            # TODO: what if unlink requests for the *same* transaction are scheduled?
            #       currently, it will throw an error
            # [self.state["unlink"].append(i) for i in match if i not in self.state["unlink"]]
            self.state["unlink"] += match
        return self.state


class LOGIC(_PT):
    def __init__(self, name, operator, switch):
        super().__init__(name)
        self.operator = operator
        self.switch = switch
        self.switches = self.addOutPort("switches")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            return {
                self.output: self.state["transaction"],
                self.switches: (SNA(self.switch, self.parent, self.state["transaction"]), self.operator)
            }
        return {}


class TEST(_PT):
    def __init__(self, name, A, operator, B):
        super().__init__(name)
        self.A = A
        self.operator = operator
        self.B = B
        self.C = self.addOutPort("C")

    def test(self):
        a = SNA(self.A, self.parent, self.state["transaction"])
        b = SNA(self.B, self.parent, self.state["transaction"])
        if self.operator == "L":
            return a < b
        if self.operator == "LE":
            return a <= b
        if self.operator == "E":
            return a == b
        if self.operator == "NE":
            return a != b
        if self.operator == "G":
            return a > b
        if self.operator == "GE":
            return a >= b

    def outputFnc(self):
        if self.state["transaction"] is not None:
            if self.test():
                port = self.output
            else:
                port = self.C
            return { port: self.state["transaction"] }
        return {}


# TODO: other GATE types
class GATE(_PT):
    def __init__(self, name, switch, B=None, status="S"):
        super().__init__(name)
        self.state["switches"] = {}
        self.status = status
        self.switch = switch
        self.switches = self.addInPort("switches")
        self.halting = self.addOutPort("halting")
        self.B = None
        if B is not None:
            self.B = self.addOutPort("B")

    def outputFnc(self):
        if self.state["transaction"] is not None:
            if self.status == self.state["switches"].get(SNA(self.switch, self.parent, self.state["transaction"]), "R"):
                return { self.output: self.state["transaction"] }
            else:
                if self.B is not None:
                    return { self.B: self.state["transaction"] }
                return {
                    self.output: self.state["transaction"],
                    self.halting: True
                }
        return {}

    def extTransition(self, inputs):
        if self.input in inputs:
            self.state["transaction"] = inputs[self.input]
        if self.switches in inputs:
            self.state["switches"] = inputs[self.switches]
        return self.state


from pypdevsbbl.generic.math import Random
from pypdevsbbl.generic.queues import Advance

class ADVANCE(CoupledDEVS):
    def __init__(self, name, seed=None, dist=lambda x: x, args=()):
        super().__init__(name)
        self._random = self.addSubModel(Random("Random", seed, dist, args))
        self._advance = self.addSubModel(Advance("Advance"))

        self.input = self.addInPort("input")
        self.pause = self.addInPort("pause")
        self.output = self.addOutPort("output")

        self.connectPorts(self.input, self._random.input)
        self.connectPorts(self._random.random, self._advance.delay)
        self.connectPorts(self._random.value, self._advance.input)
        self.connectPorts(self.pause, self._advance.pause)
        self.connectPorts(self._advance.output, self.output)


from pypdevsbbl.generic.routing import ChooseOutput, Pick

class TRANSFER_ALL(CoupledDEVS):
    def __init__(self, name, n=2):
        assert n > 1
        super().__init__(name)
        self.choose = self.addSubModel(ChooseOutput("2_Choose", n))
        self.pick = self.addSubModel(Pick("1_Pick", n, lif=True,
                                          fc=lambda x: (isinstance(x, tuple) and len(x[1]) > 0)
                                                       or (not isinstance(x, tuple) and x <= 0)))

        self.input = self.addInPort("input")
        self.claim = self.addInPort("claim")
        self.output = self.addOutPort("output")

        self.inputs = []
        self.outputs = []
        for i in range(n-1):
            self.inputs.append(self.addInPort("input-%i" % i))
            self.connectPorts(self.inputs[i], self.pick.inputs[i])

            self.outputs.append(self.addOutPort("output-%i"%i))
            self.connectPorts(self.choose.outputs[i], self.outputs[i])

        self.connectPorts(self.input, self.choose.input)
        self.connectPorts(self.claim, self.pick.claim)
        self.connectPorts(self.pick.output, self.choose.select)
        self.connectPorts(self.choose.outputs[n-1], self.output)


## ----------------------------------------------------------------------------------------- ##

class _AU(AtomicDEVS):
    """Helper class for all components that sends their updates."""
    def __init__(self, name):
        super(_AU, self).__init__(name)
        self.state = {
            "changed": True
        }

        self.enter = self.addInPort("enter")
        self.leave = self.addInPort("leave")
        self.updates = self.addOutPort("updates")

    def timeAdvance(self):
        if self.state["changed"]:
            return 0.0
        return INFINITY

    def intTransition(self):
        self.state["changed"] = False
        return self.state


class Facility(_AU):
    def __init__(self, name):
        super(Facility, self).__init__(name)
        self.state["transactions"] = []
        self.state["preempted"] = False

        # STATISTICS
        self.clear()

        self.preempt = self.addInPort("preempt")

    def clear(self):
        self.state["time"] = 0.0
        self.state["entry"] = 0
        self.state["Tsum"] = 0.0

    def outputFnc(self):
        return { self.updates: (self.name, self.state["transactions"], self.state["preempted"]) }

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.leave in inputs:
            trns = inputs[self.leave]
            assert len(self.state["transactions"]) > 0 and self.state["transactions"][-1][0] == trns
            _, T = self.state["transactions"].pop()
            self.state["preempted"] = False
            self.state["Tsum"] += self.state["time"] - T
        if self.enter in inputs:
            self.state["transactions"].append((inputs[self.enter], self.state["time"]))
            self.state["entry"] += 1
        if self.preempt in inputs:
            self.state["transactions"].append((inputs[self.enter], self.state["time"]))
            self.state["preempted"] = True
            self.state["entry"] += 1
        self.state["changed"] = True
        return self.state

    def summary(self, time=None):
        if time is None:
            time = self.state["time"]
        Trem = sum([(time - x[1]) for x in self.state["transactions"]])
        Tsum = self.state["Tsum"] + Trem

        return {
            "name": self.name[len(PREFIX_RES):],
            "entry": self.state["entry"],
            "util": self.state["Tsum"] / time,
            "transit": Tsum / self.state["entry"] if self.state["entry"] > 0 else 0.0,
            "owner": self.state["transactions"][-1][0] if len(self.state["transactions"]) > 0 else None,
            "inter": self.state["preempted"],
            "busy": len(self.state["transactions"]) > 0
        }


class Storage(_AU):
    def __init__(self, name, capacity):
        assert capacity > 0 and "Impossible storage capacity."
        super(Storage, self).__init__(name)
        self.capacity = capacity
        self.state["N"] = 0
        self.state["claims"] = []

        # STATISTICS
        self.clear()

    def clear(self):
        self.state["max"] = 0
        self.state["entry"] = 0
        self.state["Tsum"] = 0.0
        # self.state["Tlz"] = 0.0
        self.state["time"] = 0.0
        for c in range(len(self.state["claims"])):
            self.state["claims"][c][1] = 0.0

    def outputFnc(self):
        return { self.updates: self.capacity - self.state["N"] }

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.enter in inputs:
            # if self.state["N"] == 0:
            #     self.state["Tlz"] = self.state["time"]
            n = inputs[self.enter]
            self.state["N"] += n
            self.state["entry"] += 1
            self.state["max"] = max(self.state["max"], self.state["N"])
            self.state["claims"].append([n, self.state["time"]])
        if self.leave in inputs:
            n = inputs[self.leave]
            self.state["N"] -= n
            while n > 0:
                claim = self.state["claims"][0]
                time = self.state["time"] - claim[1]
                amount = min(n, claim[0])
                n -= amount
                self.state["Tsum"] += amount * time
                if claim[0] == amount:
                    self.state["claims"].pop(0)
                else:
                    self.state["claims"][0][0] -= amount
            # if self.state["N"] == 0:
            #     self.state["Tsum"] += self.state["time"] - self.state["Tlz"]
            #     self.state["Tlz"] = 0.0
        assert 0 <= self.state["N"] <= self.capacity
        self.state["changed"] = True
        return self.state

    def summary(self, time=None):
        if time is None:
            time = self.state["time"]
        Tsum = self.state["Tsum"]
        for claim in self.state["claims"]:
            Tsum += claim[0] * (time - claim[1])
        return {
            "name": self.name[len(PREFIX_RES):],
            "capacity": self.capacity,
            "remaining": self.capacity - self.state["N"],
            "entry": self.state["entry"],
            "max": self.state["max"],
            "util": Tsum / (time * self.capacity) if time > 0 else 0.0,
            "avgc": Tsum / time if time > 0 else 0.0
        }


# TODO: reduce notify amount
class Chain(_AU):
    def __init__(self, name):
        super(Chain, self).__init__(name)
        self.state["chain"] = []

        # STATISTICS
        self.clear()

    def clear(self):
        self.state["entries"] = 0
        self.state["max"] = 0
        self.state["time"] = 0.0
        self.state["Tsum"] = 0.0

    def outputFnc(self):
        if self.state["changed"]:
            return { self.updates: self.state["chain"] }
        return {}

    def timeAdvance(self):
        if self.state["changed"]:
            return 0.0
        return INFINITY

    def intTransition(self):
        if self.state["changed"]:
            self.state["changed"] = False
        return self.state

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.enter in inputs:
            trns, B = inputs[self.enter]
            if B == "FIFO":
                self.state["chain"].append((trns, self.state["time"]))
            elif B == "LIFO":
                self.state["chain"].insert(0, (trns, self.state["time"]))
            else:
                ins = False
                for i in range(len(self.state["chain"])):
                    ct = self.state["chain"][i][0]
                    if ct.parameters[B] <= trns.parameters[B]:
                        self.state["chain"].insert(i, (trns, self.state["time"]))
                        ins = True
                        break
                if not ins:
                    self.state["chain"].append((trns, self.state["time"]))
            self.state["entries"] += 1
            self.state["max"] = max(self.state["max"], len(self.state["chain"]))
            self.state["changed"] = True
        if self.leave in inputs:
            for trns, time in self.state["chain"]:
                if trns == inputs[self.leave]:
                    self.state["Tsum"] += self.state["time"] - time
                    self.state["chain"].remove((trns, time))
                    break
            self.state["changed"] = True
        return self.state

    def summary(self, time=None):
        if time is None:
            time = self.state["time"]
        Trem = sum([(time - x[1]) for x in self.state["chain"]])
        Tsum = self.state["Tsum"] + Trem

        return {
            # Chain Name
            "name": self.name[len(PREFIX_RES):],
            # Maximal Queue Size
            "max": self.state["max"],
            # Average Queue Content
            "avgc": Tsum / time if time > 0 else 0.0,
            # Amount of Entries
            "entry": self.state["entries"],
            # Average Time in the Queue
            "transit": Tsum / self.state["entries"] if self.state["entries"] > 0 else 0.0,
            # Current Queue Size
            "current": len(self.state["chain"])
        }


class Queue(AtomicDEVS):
    def __init__(self, name):
        super(Queue, self).__init__(name)
        self.state = {
            "queue": [],
            "size": 0,

            # STATISTICS
            "entries": 0,
            "zeros": 0,
            "max": 0,
            "time": 0.0,
            "Tsum": 0.0
        }

        self.enter = self.addInPort("enter")
        self.leave = self.addInPort("leave")

    def clear(self):
        self.state["entries"] = 0
        self.state["zeros"] = 0
        self.state["max"] = 0
        self.state["time"] = 0.0
        self.state["Tsum"] = 0.0

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.enter in inputs:
            trns, n = inputs[self.enter]
            self.state["queue"].append((trns, self.state["time"]))
            self.state["size"] += n
            self.state["max"] = max(self.state["max"], self.state["size"])
            self.state["entries"] += 1
        if self.leave in inputs:
            trns, n = inputs[self.leave]
            tlist = [x[0] for x in self.state["queue"]]
            if trns in tlist:
                _, a = self.state["queue"].pop(tlist.index(trns))
                self.state["size"] -= n
                assert self.state["size"] >= 0
                T = self.state["time"] - a
                self.state["Tsum"] += T
                if T == 0:
                    self.state["zeros"] += 1
            else:
                logger.warn("Transaction is not in the queue.")
        return self.state

    def summary(self, time=None):
        if time is None:
            time = self.state["time"]
        Trem = sum([(time - x[1]) for x in self.state["queue"]])
        Tsum = self.state["Tsum"] + Trem
        # zl = [x for x in self.state["queue"] if (time - x[1]) == 0]
        zeros = self.state["zeros"]

        return {
            # Queue Name
            "name": self.name[len(PREFIX_RES):],
            # Maximal Queue Size
            "max": self.state["max"],
            # Average Queue Content
            "avgc": Tsum / time if time > 0 else 0.0,
            # Amount of Entries
            "entry": self.state["entries"],
            # Amount of Zero Entries
            "zeros": zeros,
            # Percentage of Zero Entries compared to the Amount of Entries
            "zeropct": (zeros / self.state["entries"]) * 100 if self.state["entries"] > 0 else 0.0,
            # Average Time in the Queue
            "transit": Tsum / self.state["entries"] if self.state["entries"] > 0 else 0.0,
            # Average Time in Queue for Nonzero Transactions
            "ztransit": Tsum / (self.state["entries"] - zeros) if (self.state["entries"] - zeros) > 0 else 0.0,
            # Current Queue Size
            "current": self.state["size"]
        }


# TODO: for certain values, all information must be kept,
#       i.e. deviation from mean, multiple of mean, average overflow value
class Table(AtomicDEVS):
    def __init__(self, name, statistic, L, W, N, weighted=False, rate=None):
        super(Table, self).__init__(name)
        self.statistic = statistic
        self.weighted = weighted
        self.state = {
            "intervals": { k: 0 for k in range(L, (N-1)*W, W) },
            "N": 0,
            "sum": 0,
            "squares": 0,
            "time": 0.0
        }
        self.state["intervals"][-INFINITY] = 0
        self.state["intervals"][INFINITY] = 0
        self.intervals = sorted(self.state["intervals"].keys())
        if rate is not None:
            self.state["rate"] = 0
        self.rate = rate
        self.input = self.addInPort("input")

    def count(self):
        return self.state["N"]

    def sum(self):
        return self.state["sum"]

    def sumOfSquares(self):
        return self.state["squares"]

    def mean(self):
        n = self.count()
        if n  == 0:
            return 0.0
        return self.sum() / n

    def variance(self): # As is defined by Gordon
        n = self.count()
        if n <= 1:
            return 0.0
        mu = self.mean()
        s = self.sum()
        sq = self.sumOfSquares()
        return ((n * (mu ** 2)) + sq - (2 * mu * s)) / (n - 1)

    def std(self):
        return self.variance() ** 0.5

    def clear(self):
        for k in self.state["intervals"]:
            self.state["intervals"][k] = 0
        self.state["N"] = 0
        self.state["sum"] = 0
        self.state["squares"] = 0
        if "rate" in self.state:
            self.state["rate"] = 0

    def timeAdvance(self):
        if self.rate is not None:
            return self.rate
        return INFINITY

    def intTransition(self):
        self.state["time"] += self.timeAdvance()
        self.update(1, self.state["rate"])
        self.state["rate"] = 0
        return self.state

    def extTransition(self, inputs):
        self.state["time"] += self.elapsed
        if self.input in inputs:
            if self.rate is not None:
                self.state["rate"] += 1
            else:
                trns, size = inputs[self.input]
                value = SNA(self.statistic, self.parent, trns, self.state["time"])
                if not self.weighted:
                    size = 1
                self.update(size, value)
        return self.state

    def update(self, size, value):
        for i in range(len(self.intervals) - 1):
            if self.intervals[i] < value <= self.intervals[i + 1]:
                self.state["intervals"][self.intervals[i+1]] += size
        self.state["N"] += size
        self.state["sum"] += size * value
        self.state["squares"] += size * (value ** 2)

    def summary(self):
        return {
            "name": self.name[len(PREFIX_RES):],
            "count": self.count(),
            "mean": self.mean(),
            "variance": self.variance(),
            "deviation": self.std(),
            "intervals": self.state["intervals"]
        }


class LogicSwitches(AtomicDEVS):
    def __init__(self, name):
        super().__init__(name)
        self.state = {
            "switches": {},
            "changed": True
        }
        self.change = self.addInPort("change")
        self.updates = self.addOutPort("updates")

    def timeAdvance(self):
        return 0.0 if self.state["changed"] else INFINITY

    def outputFnc(self):
        return { self.updates: self.state["switches"] }

    def intTransition(self):
        self.state["changed"] = False
        return self.state

    def extTransition(self, inputs):
        if self.change in inputs:
            name, status = inputs[self.change]
            self.state["switches"][name] = status
            self.state["changed"] = True
        return self.state

    def summary(self):
        return self.state["switches"]

## ----------------------------------------------------------------------------------------- ##

# TODO: serialized name?
# TODO: Assumes all Transactions to be unique!
class Hold(AtomicDEVS):
    def __init__(self, name, send=False):
        super(Hold, self).__init__(name)
        self.send = send
        self.state = {
            "transactions": {},
            "release": None,
            "changed": None
        }

        self.input = self.addInPort("input")
        self.select = self.addInPort("select")   # When True, the input will be associated with the fallback output
        self.output = self.addOutPort("output")
        self.fallback = self.addOutPort("fallback")

        # Connections to the Controller
        self.release = self.addInPort("release")
        self.contents = self.addOutPort("contents")

    def timeAdvance(self):
        trns = self.state["release"]
        if (trns is not None and trns in self.state["transactions"]) \
                or (self.send and self.state["changed"] is not None):
            return 0.0
        return INFINITY

    def outputFnc(self):
        res = {}
        trns = self.state["release"]
        if trns is not None and trns in self.state["transactions"]:
            for t in self.state["transactions"]:
                if t == trns:
                    trns = t
                    break
            if self.state["transactions"][trns]:
                res[self.fallback] = trns
            else:
                res[self.output] = trns
        trns = self.state["changed"]
        if self.send and trns is not None:
            # res[self.contents] = (trns, self.state["transactions"][trns])
            res[self.contents] = list(self.state["transactions"].items())
        return res

    def intTransition(self):
        trns = self.state["release"]
        if trns is not None and trns in self.state["transactions"]:
            del self.state["transactions"][trns]
        trns = self.state["changed"]
        if not self.send or (self.send and trns is not None):
            self.state["changed"] = None
        return self.state

    def extTransition(self, inputs):
        if self.release in inputs:
            self.state["release"] = inputs[self.release]
        if self.input in inputs:
            trns = inputs[self.input]
            fallback = inputs.get(self.select, False)
            self.state["transactions"][trns] = fallback
            self.state["changed"] = trns
            if fallback:
                self.state["release"] = None
        return self.state


# TODO: P-DEVS
class Controller(AtomicDEVS):
    # TODO: Optimize -- make "unblock" facility/storage-specific
    # TODO: Add a stepwise buildup of this in thesis/paper
    # REPEAT
    #     IF inner loop not broken:         <-- split up and show why by example!
    #         wait until new is not empty   <-- Why?
    #     END
    #     add new to active
    #     sort active by priority (and time in system?)  <-- Why?
    #     REPEAT for each active transaction T           <-- Why?
    #         move T until blocked, ADVANCE or TERMINATE
    #     END
    # END
    #
    #
    # on TERMINATE:
    #     remove T from transactions
    #
    # on ADVANCE:
    #     move T from active to delayed
    #
    # on blocked:
    #     move T from active to blocked
    #
    # on GENERATE:
    #     add T to new
    #
    # on moved:
    #     IF T is delayed:
    #         move T from delayed to new
    #     END
    #
    # on RELEASE/LEAVE:                     <-- Why?
    #     add blocked to new
    #     break from inner loop
    def __init__(self, name):
        super(Controller, self).__init__(name)  # make sure this is handled BEFORE any other block!
        self.state = {
            "active": [],       # All active transactions, this somewhat corrsponds to the current events chain
            "blocked": [],      # All transactions that were blocked at some point
            "delayed": [],      # All transactions that were delayed for some reason (e.g. due to ADVANCE)
            "created": [],      # All newly created transactions (or all that must join the active set)

            "start": True,      # Indicator that we're restarting the scan
            "broken": False,    # Indicator that we need to break out of the scanning loop
            "waiting": False,   # Indicator that the notification has happened

            "terminated": 0,    # The termination counter
            "bcd": None,        # The last notified item

            "facilities": {},   # Facilities in the system
            "pause": []         # All transactions that need to be paused
        }

        self.terminate = self.addInPort("terminate")
        self.create = self.addInPort("create")
        self.moved = self.addInPort("moved")
        self.delay = self.addInPort("delay")
        self.unblock = self.addInPort("unblock")
        self.facilities = self.addInPort("facilities")

        self.notify = self.addOutPort("notify")
        self.pause = self.addOutPort("pause")

    def finished(self, count):
        return self.state["terminated"] >= count

    def reset(self):
        self.state["terminated"] = 0

    def timeAdvance(self):
        if self.state["start"] or self.state["broken"] or (not self.state["waiting"] and len(self.state["active"]) > 0):
            return 0.0
        return INFINITY

    def outputFnc(self):
        res = {}
        if 0 < len(self.state["active"]):
            tb = self.state["active"][0]
            if self.state["bcd"] is None or tb != self.state["bcd"]:
                res[self.notify] = self.state["active"][0]
        if len(self.state["pause"]) > 0:
            res[self.pause] = self.state["pause"][:]
        return res

    def intTransition(self):
        self.state["pause"].clear()
        if 0 < len(self.state["active"]):
            self.state["waiting"] = True
            self.state["bcd"] = self.state["active"][0]
        if self.state["start"]:
            self.state["active"] += self.state["created"]
            self.state["created"].clear()
            self.state["active"].sort()
            self.state["start"] = False
        if self.state["broken"]:
            self.state["start"] = True
            self.state["broken"] = False
            self.state["waiting"] = False
        return self.state

    def extTransition(self, inputs):
        if self.create in inputs:
            self.state["created"].append(inputs[self.create])
        if self.terminate in inputs:
            trns, cnt = inputs[self.terminate]
            self.state["active"].remove(trns)
            self.state["waiting"] = False
            self.state["terminated"] += cnt
        if self.moved in inputs:
            moves = inputs[self.moved]
            if not isinstance(moves, (tuple, list)):
                moves = [moves]
            for move in moves:
                if isinstance(move, (tuple, list)):
                    trns, blocked = move
                else:
                    trns = move
                    blocked = False
                if blocked and trns in self.state["active"]:
                    self.state["active"].remove(trns)
                    self.state["blocked"].append(trns)
                    self.state["waiting"] = False
                elif trns in self.state["delayed"]:
                    self.state["delayed"].remove(trns)
                    self.state["created"].append(trns)
        if self.delay in inputs:
            trns = inputs[self.delay]
            self.state["active"].remove(trns)
            self.state["delayed"].append(trns)
            self.state["waiting"] = False
        if self.unblock in inputs:
            self.state["created"] += self.state["blocked"]
            self.state["blocked"].clear()
            self.state["broken"] = True
        if self.facilities in inputs:
            name, trns, pre = inputs[self.facilities]
            self.state["facilities"][name] = (trns, pre)
            self.state["pause"] = [(x, True) for x in trns[:-1]]
            if len(trns) > 0:
                self.state["pause"] += [(trns[-1], False)]

            # A transaction can be interrupted up to 255 times.
            ap = []
            for n in self.state["facilities"]:
                x = self.state["facilities"][n]
                if x[1]:
                    ap.append(x)
            for T in set(ap):
                assert ap.count(T) < 255

        if len(self.state["active"]) == 0:
            self.state["start"] = True

        return self.state


from pypdevsbbl.generic.generators import RandomDelayGenerator as GENERATE
class GPSS2DEVS(CoupledDEVS):
    def __init__(self, name, gens=1):
        super().__init__(name)
        self.gens = gens
        self.cgens = 0
        ctrl = PREFIX_CRL + "Controller"
        setattr(self, ctrl, self.addSubModel(Controller(ctrl)))
        ls = PREFIX_RES + "LogicSwitches"
        setattr(self, ls, self.addSubModel(LogicSwitches(ls)))
        self.connectPorts(self.switches().updates, self.controller().unblock)

    def controller(self):
        return getattr(self, PREFIX_CRL + "Controller")

    def switches(self):
        return getattr(self, PREFIX_RES + "LogicSwitches")

    def facility(self, name):
        return getattr(self, PREFIX_RES + name)

    def storage(self, name):
        return getattr(self, PREFIX_RES + name)

    def table(self, name):
        return getattr(self, PREFIX_RES + name)

    def chain(self, name):
        return getattr(self, PREFIX_RES + name)

    def queue(self, name):
        return getattr(self, PREFIX_RES + name)

    def createFacility(self, label):
        name = PREFIX_RES + label
        setattr(self, name, self.addSubModel(Facility(name)))
        self.connectPorts(getattr(self, name).updates, self.controller().facilities)

    def createStorage(self, label, capacity):
        name = PREFIX_RES + label
        setattr(self, name, self.addSubModel(Storage(name, capacity)))

    def createQueue(self, label):
        name = PREFIX_RES + label
        setattr(self, name, self.addSubModel(Queue(name)))

    def createTable(self, label, statistic, L, W, N, weighted=False, rate=None):
        name = PREFIX_RES + label
        setattr(self, name, self.addSubModel(Table(name, statistic, L, W, N, weighted, rate)))

    def createChain(self, label):
        name = PREFIX_RES + label
        setattr(self, name, self.addSubModel(Chain(name)))

    def connect(self, A, B, delay=False):
        prefix = PREFIX_DLY if delay else PREFIX_BLK
        self.connectPorts(getattr(self, PREFIX_HLD + A).output, getattr(self, prefix + B).input)

    def GENERATE(self, label, dist, args=(), max=INFINITY, priority=0):
        assert self.cgens < self.gens and "Creating more GENERATE blocks than initially said."
        name = PREFIX_GEN + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(
            GENERATE(name, dist=dist, args=args, dt=None, max=max,
                     func=lambda x, t, n=self.gens, g=self.cgens, p=priority: Transaction(n * x + g, t, p))))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, name).output, self.controller().create)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.cgens += 1

    def ADVANCE(self, label, prev, dist, args=()):
        name = PREFIX_DLY + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(ADVANCE(name, dist=dist, args=args)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, name).output, self.controller().moved)
        # self.connectPorts(getattr(self, hname).contents, self.controller().moved)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(self.controller().pause, getattr(self, name).pause)
        self.connectPorts(getattr(self, PREFIX_HLD + prev).output, self.controller().delay)

    def TERMINATE(self, label, count='0'):
        name = PREFIX_BLK + label
        setattr(self, name, self.addSubModel(TERMINATE(name, count)))
        self.connectPorts(getattr(self, name).output, self.controller().terminate)

    def MARK(self, label, sna="M1"):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(MARK(name, sna)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)

    def ASSIGN(self, label, parameters, assign):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(ASSIGN(name, parameters, assign)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)

    def TABULATE(self, label, table, size=1):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(TABULATE(name, size)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).table, getattr(self, PREFIX_RES + table).input)

    def QUEUE(self, label, queue, size=1):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(QUEUE(name, size)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).queue, getattr(self, PREFIX_RES + queue).enter)

    def DEPART(self, label, queue, size=1):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(DEPART(name, size)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).queue, getattr(self, PREFIX_RES + queue).leave)

    def SEIZE(self, label, facility):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(SEIZE(name)))
        setattr(self, hname, self.addSubModel(Hold(hname, True)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, name).halting, getattr(self, hname).select)
        self.connectPorts(getattr(self, hname).fallback, getattr(self, name).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, hname).contents, self.controller().moved)
        self.connectPorts(getattr(self, name).request, getattr(self, PREFIX_RES + facility).enter)
        self.connectPorts(getattr(self, PREFIX_RES + facility).updates, getattr(self, name).facility)

    def RELEASE(self, label, facility):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(RELEASE(name)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).facility, getattr(self, PREFIX_RES + facility).leave)
        self.connectPorts(getattr(self, name).facility, self.controller().unblock)

    def ENTER(self, label, storage, size=1):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(ENTER(name, size)))
        setattr(self, hname, self.addSubModel(Hold(hname, True)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, name).halting, getattr(self, hname).select)
        self.connectPorts(getattr(self, hname).fallback, getattr(self, name).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, hname).contents, self.controller().moved)
        self.connectPorts(getattr(self, name).request, getattr(self, PREFIX_RES + storage).enter)
        self.connectPorts(getattr(self, PREFIX_RES + storage).updates, getattr(self, name).storage)

    def LEAVE(self, label, storage, size=1):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(LEAVE(name, size)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).storage, getattr(self, PREFIX_RES + storage).leave)
        self.connectPorts(getattr(self, name).storage, self.controller().unblock)

    def PREEMPT(self, label, facility, interrupt=False):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(PREEMPT(name, interrupt)))
        setattr(self, hname, self.addSubModel(Hold(hname, True)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, name).halting, getattr(self, hname).select)
        self.connectPorts(getattr(self, hname).fallback, getattr(self, name).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, hname).contents, self.controller().moved)
        self.connectPorts(getattr(self, name).request, getattr(self, PREFIX_RES + facility).preempt)
        self.connectPorts(getattr(self, PREFIX_RES + facility).updates, getattr(self, name).facility)

    def RETURN(self, label, facility):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(RETURN(name)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).release, getattr(self, PREFIX_RES + facility).leave)
        self.connectPorts(getattr(self, name).release, self.controller().unblock)
        self.connectPorts(getattr(self, PREFIX_RES + facility).updates, getattr(self, name).facility)

    def LINK(self, label, chain, prev, B, C=None):
        name = PREFIX_BLK + label
        setattr(self, name, self.addSubModel(LINK(name, B, C)))
        self.connectPorts(getattr(self, PREFIX_HLD + prev).output, self.controller().delay)
        self.connectPorts(getattr(self, PREFIX_RES + chain).updates, getattr(self, name).chain)
        self.connectPorts(getattr(self, name).output, getattr(self, PREFIX_RES + chain).enter)
        if C is not None:
            assert hasattr(self, C)   # C must be a defined Hold block, otherwise: add connection afterwards
            self.connectPorts(getattr(self, name).C, getattr(self, C).input)

    def UNLINK(self, label, chain, count, D=None, E=None, F=None):
        name = PREFIX_DLY + label
        hname1 = PREFIX_HLD + label
        hname2 = PREFIX_HLD + label + "_B"
        setattr(self, name, self.addSubModel(UNLINK(name, count, D, E, F)))
        setattr(self, hname1, self.addSubModel(Hold(hname1)))
        setattr(self, hname2, self.addSubModel(Hold(hname2)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname1).input)
        self.connectPorts(getattr(self, name).B, getattr(self, hname2).input)
        self.connectPorts(getattr(self, name).B, self.controller().moved)
        self.connectPorts(self.controller().notify, getattr(self, hname1).release)
        self.connectPorts(self.controller().notify, getattr(self, hname2).release)
        # self.connectPorts(getattr(self, hname2).contents, self.controller().moved)
        self.connectPorts(getattr(self, name).unlink, getattr(self, PREFIX_RES + chain).leave)
        self.connectPorts(getattr(self, PREFIX_RES + chain).updates, getattr(self, name).chain)

        if F is not None:
            hname3 = PREFIX_HLD + label + "_F"
            setattr(self, hname3, self.addSubModel(Hold(hname3)))
            self.connectPorts(getattr(self, name).F, getattr(self, hname3).input)
            self.connectPorts(self.controller().notify, getattr(self, hname3).release)

    def GATE(self, label, switch, B=None, status="S"):
        name = PREFIX_BLK + label
        hname1 = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(GATE(name, switch, B, status)))
        setattr(self, hname1, self.addSubModel(Hold(hname1, True)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname1).input)
        self.connectPorts(getattr(self, name).halting, getattr(self, hname1).select)
        self.connectPorts(getattr(self, hname1).fallback, getattr(self, name).input)
        self.connectPorts(self.controller().notify, getattr(self, hname1).release)
        self.connectPorts(getattr(self, hname1).contents, self.controller().moved)
        self.connectPorts(self.switches().updates, getattr(self, name).switches)
        if B is not None:
            hname2 = PREFIX_HLD + label + "_B"
            setattr(self, hname2, self.addSubModel(Hold(hname2)))
            self.connectPorts(getattr(self, name).B, getattr(self, hname2).input)
            self.connectPorts(self.controller().notify, getattr(self, hname2).release)

    def TRANSFER_BOTH(self, label, nxt, resource, endp):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(TRANSFER_ALL(name)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(getattr(self, PREFIX_RES + resource).updates, getattr(self, name).claim)
        self.connectPorts(getattr(self, name).outputs[0], getattr(self, PREFIX_BLK + nxt).input)
        self.connectPorts(endp, getattr(self, name).inputs[0])
        self.connectPorts(self.controller().notify, getattr(self, hname).release)

    def LOGIC(self, label, operator, switch):
        name = PREFIX_BLK + label
        hname = PREFIX_HLD + label
        setattr(self, name, self.addSubModel(LOGIC(name, operator, switch)))
        setattr(self, hname, self.addSubModel(Hold(hname)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname).input)
        self.connectPorts(self.controller().notify, getattr(self, hname).release)
        self.connectPorts(getattr(self, name).switches, self.switches().change)

    def TEST(self, label, A, operator, B):
        name = PREFIX_BLK + label
        hname1 = PREFIX_HLD + label + "_Y"
        hname2 = PREFIX_HLD + label + "_N"
        setattr(self, name, self.addSubModel(TEST(name, A, operator, B)))
        setattr(self, hname1, self.addSubModel(Hold(hname1)))
        setattr(self, hname2, self.addSubModel(Hold(hname2)))
        self.connectPorts(getattr(self, name).output, getattr(self, hname1).input)
        self.connectPorts(getattr(self, name).C, getattr(self, hname2).input)
        self.connectPorts(self.controller().notify, getattr(self, hname1).release)
        self.connectPorts(self.controller().notify, getattr(self, hname2).release)
