#  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.

"""Tests the Routing building blocks to prove validity w.r.t. these tests.

See Also:
    :mod:`pypdevsbbl.generic.routing`
"""

import unittest

from pypdevsbbl.generic.collectors import Counter, TableCollector
from pypdevsbbl.generic.generators import ConstantGenerator, FunctionGenerator, TableGenerator, SingleFire
from pypdevsbbl.generic.queues import SimpleQueue
from pypdevsbbl.generic.routing import ChooseInput, ChooseOutput, Guard, Timer, Gate, Delayer, Finish, Sync, Halt, Pick
from pypdevsbbl.generic.transformers import Transformer
from pypdevs.simulator import Simulator

from test.generic import Model, simulate


class TestRouting(unittest.TestCase):
    def test_finish(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Finish` block."""
        # Just test that this does not crash
        nodes = [FunctionGenerator("Generator"), Finish("Finish"), TableCollector("Collector")]
        connections = [(nodes[0].output, nodes[1].input), (nodes[0].output, nodes[2].input)]
        model = Model(nodes, connections)
        simulate(model, 20)
        assert [(x, x) for x in range(21)] == nodes[2].table()

    def test_halt(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Halt` block."""
        nodes = [ConstantGenerator("Gen", 5), TableCollector("Collector"), Halt("Halt")]
        conn = [(nodes[0].output, nodes[1].input), (nodes[0].output, nodes[2].input)]
        model = Model(nodes, conn)
        sim = Simulator(model)
        sim.setClassicDEVS()
        sim.setTerminationCondition(Halt.anyHalted)
        sim.simulate()

        assert nodes[2].isHalted()
        assert nodes[2].state == 5
        assert [(0, 5)] == nodes[1].table()

    def test_pick(self):
        """Test the :class:`pypdevsbbl.generic.routing.Pick` block."""
        nodes = []
        conn = []
        nodes.append(ConstantGenerator("B", 10))
        nodes.append(Pick("A", 10, contents=[False if x == 8 else True for x in range(10)], lif=True))
        nodes.append(TableCollector("C"))
        nodes.append(SingleFire("D", 10, 5.0))
        conn.append((nodes[0].output, nodes[1].claim))
        conn.append((nodes[1].output, nodes[2].input))
        conn.append((nodes[3].output, nodes[1].inputs[0]))

        model = Model(nodes, conn)
        simulate(model, 20)

        # 8 is not claimed
        # at time 5.0, 0 is released, making it so 6 is *not* claimed, hence the repeat
        assert [0, 1, 2, 3, 4, 5, 6, 0, 6, 7, 9] == [x[1] for x in nodes[2].table()]

    def test_choose_input(self):
        """Tests the :class:`pypdevsbbl.generic.routing.ChooseInput` block."""
        for n in range(1, 20):
            nodes = []
            conn = []
            nodes.append(ChooseInput("ChooseInput", n))
            for i in range(n):
                nodes.append(ConstantGenerator("Gen-%i" % i, i))
                conn.append((nodes[-1].output, nodes[0].inputs[i]))
            nodes.append(Counter("CNT"))
            conn.append((nodes[0].output, nodes[-1].input))
            nodes.append(Transformer("MOD", lambda v, _, __, c=n: v % c))
            conn.append((nodes[-2].count, nodes[-1].input))
            conn.append((nodes[-1].transformed, nodes[0].select))
            nodes.append(TableCollector("Collector"))
            conn.append((nodes[-3].output, nodes[-1].input))

            model = Model(nodes, conn)
            simulate(model, 23.0)

            assert [(x, x % n) for x in range(24)] == nodes[-1].table()

    def test_choose_output(self):
        """Tests the :class:`pypdevsbbl.generic.routing.ChooseOutput` block."""
        for n in range(1, 20):
            nodes = [FunctionGenerator("Generator"),
                     Transformer("Transformer", lambda i, _, __, c=n: i % c),
                     ChooseOutput("ChooseOutput", n)]
            conn = [(nodes[0].output, nodes[1].input), (nodes[1].original, nodes[2].input),
                    (nodes[1].transformed, nodes[2].select)]
            for i in range(n):
                coll = TableCollector("Coll-%i" % i)
                conn.append((nodes[2].outputs[i], coll.input))
                nodes.append(coll)

            model = Model(nodes, conn)
            simulate(model, 23.0)


            for i in range(n):
                assert [x for x in range(24) if x % n == i] == [x[1] for x in nodes[3 + i].table()]

    def test_guard(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Guard` block."""
        nodes = [ConstantGenerator("Generator", 1), Guard("Guard", 2), SimpleQueue("Queue", dr=3.0),
                 TableCollector("Coll-1"), TableCollector("Coll-2"), Timer("Sync")]
        conn = [(nodes[5].finished, nodes[1].input), (nodes[1].blocked, nodes[3].input),
                (nodes[1].guarded, nodes[2].enqueue), (nodes[1].unguarded, nodes[4].input),
                (nodes[2].renege, nodes[1].leave), (nodes[0].output, nodes[5].start)]

        model = Model(nodes, conn)
        simulate(model, 10.0)

        assert [(2, 1), (5, 1), (8, 1)] == nodes[3].table()
        assert [(3, 1), (4, 1), (6, 1), (7, 1), (9, 1), (10, 1)] == nodes[4].table()

    def test_gate(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Gate` block."""
        nodes = [FunctionGenerator("Generator"), Gate("Gate", False),
                 TableGenerator("Blocker", [(7, True), (11, False), (11, True), (12, False)]),
                 TableCollector("Coll-1"), TableCollector("Coll-2")]
        conn = [(nodes[0].output, nodes[1].input), (nodes[1].blocked, nodes[3].input),
                (nodes[1].output, nodes[4].input), (nodes[2].output, nodes[1].block)]

        model = Model(nodes, conn)
        simulate(model, 23.0)

        assert (list(range(7)) + list(range(12, 24))) == [x[1] for x in nodes[4].table()]
        assert list(range(7, 12)) == [x[1] for x in nodes[3].table()]

    def test_timer(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Timer` block."""
        nodes = [ConstantGenerator("Generator"), Timer("Timer", 3),
                 TableCollector("Coll-1"), TableCollector("Coll-2"), SingleFire("SF", 4, 17)]
        conn = [(nodes[0].output, nodes[1].start),
                (nodes[1].finished, nodes[2].input),
                (nodes[1].blocked, nodes[3].input),
                (nodes[4].output, nodes[1].dt)]
        model = Model(nodes, conn)
        simulate(model, 23.0)

        assert [x for x in range(3, 17, 3)] + [19, 23] == [x[0] for x in nodes[2].table()]
        assert [x for x in range(18) if x % 3 != 0] + [18, 20, 21, 22] == [x[0] for x in nodes[3].table()]

    def test_delayer(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Delayer` block."""
        nodes = [FunctionGenerator("Generator"), Delayer("Delayer", lambda i: i % 4),
                 TableCollector("Coll-1"), TableCollector("Coll-2")]
        conn = [(nodes[0].output, nodes[1].start),
                (nodes[1].finished, nodes[2].input),
                (nodes[1].blocked, nodes[3].input)]
        model = Model(nodes, conn)
        simulate(model, 10.0)

        assert [(0, 0), (2, 1), (4, 2), (4, 4), (6, 5), (8, 6), (8, 8), (10, 9)] == nodes[2].table()
        assert [(3, 3), (7, 7)] == nodes[3].table()

    def test_sync(self):
        """Tests the :class:`pypdevsbbl.generic.routing.Sync` block."""
        nodes = [SingleFire("SF-1", 5, 3), SingleFire("SF-2", 5, 1), Sync("Sync", 2),
                 TableCollector("Collector-1"), TableCollector("Collector-2")]
        conn = [(nodes[0].output, nodes[2].inputs[0]), (nodes[1].output, nodes[2].inputs[1]),
                (nodes[2].outputs[0], nodes[3].input), (nodes[2].outputs[1], nodes[4].input)]
        model = Model(nodes, conn)
        simulate(model)

        assert nodes[3].table() == nodes[4].table()

if __name__ == '__main__':
    unittest.main()
