#  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 Generator building blocks to prove validity w.r.t. these tests.

Every test will run a PythonPDEVS simulation on a model. This model will
afterwards be analyzed against the expected measures. The model has a
general shape of

.. centered::
    ``Generator -> TableCollector``

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

import unittest
from pypdevsbbl.generic.generators import ConstantGenerator, FunctionGenerator, TableGenerator, SingleFire, BulkGenerator
from pypdevsbbl.generic.collectors import TableCollector

from pypdevs.DEVS import CoupledDEVS
from test.generic import Model, simulate as _simulate

class GenModel(CoupledDEVS):
    """Simple model constructor for the tests.

    Args:
        gen:    The generator to use.
    """
    def __init__(self, gen = None):
        super(GenModel, self).__init__("Model")

        self.gen = self.addSubModel(gen)
        self.coll = self.addSubModel(TableCollector("TableCollector"))

        self.connectPorts(self.gen.output, self.coll.input)

def simulate(gen, tt=None):
    """Simulates the model.

    Args:
        gen:            The generator to use.
        tt (numeric):   The termination time or ``None`` if
                        no termination time needs to be set.
                        Defaults to ``None``.
    """
    model = GenModel(gen)
    _simulate(model, tt)
    return model

class TestGenerators(unittest.TestCase):
    def test_constant(self):
        """Tests the :class:`pypdevsbbl.generic.generators.ConstantGenerator`."""
        nodes = [ConstantGenerator("Generator"), SingleFire("SF", 2, 80), TableCollector("TableCollector")]
        conn = [(nodes[0].output, nodes[2].input), (nodes[1].output, nodes[0].dt)]
        model = Model(nodes, conn)
        _simulate(model, 100.0)
        assert [(x, 0) for x in range(81)] + [(x, 0) for x in range(82, 101, 2)] == nodes[2].table()

        for v in range(50, 100, 3):
            gen = ConstantGenerator("Generator", v)
            model = simulate(gen, 10.0)
            assert [v] * 11 == [x[1] for x in model.coll.table()]

    def test_function(self):
        """Tests the :class:`pypdevsbbl.generic.generators.FunctionGenerator`."""
        nodes = [FunctionGenerator("Generator", lambda _, x: x*x), SingleFire("SF", 2, 80),
                 TableCollector("TableCollector")]
        conn = [(nodes[0].output, nodes[2].input), (nodes[1].output, nodes[0].dt)]
        model = Model(nodes, conn)
        _simulate(model, 100.0)
        assert [(x, x*x) for x in range(81)] + [(x, x*x) for x in range(82, 101, 2)] == nodes[2].table()

        gen = FunctionGenerator("Generator", lambda x, t: x * t)
        model = simulate(gen, 10.0)
        assert [0] * 11 == [x[1] for x in model.coll.table()]

        gen = FunctionGenerator("Generator", lambda x, t: x + t, ic=1)
        model = simulate(gen, 10.0)
        a = [1]
        for i in range(1, 11):
            a.append(a[-1] + i)
        assert a == [x[1] for x in model.coll.table()]

    def test_table(self):
        """Tests the :class:`pypdevsbbl.generic.generators.TableGenerator`."""
        table = [
            (0, 1),
            (1, 3),
            (1, 5),
            (2, 7),
            (3, 11),
            (4, 13),
            (4, 17),
            (4, 19),
            (5, 23)
        ]

        gen = TableGenerator("Generator", table)
        model = simulate(gen)
        assert table == model.coll.table()

    def test_table_repeat(self):
        """Tests the :class:`pypdevsbbl.generic.generators.TableGenerator` with a repeat."""
        table = [
            (0, 1),
            (1, 3),
            (1, 5),
            (2, 7),
            (3, 11),
            (4, 13),
            (4, 17),
            (4, 19),
            (5, 23)
        ]

        gen = TableGenerator("Generator", table, True)
        model = simulate(gen, 10)
        assert table + [(x[0] + 5, x[1]) for x in table] + [(10, 1)] == model.coll.table()

    def test_singlefire(self):
        """Tests the :class:`pypdevsbbl.generic.generators.SingleFire`."""
        table = [
            (0, 1),
            (1, 3),
            (1, 5),
            (2, 7),
            (3, 11),
            (4, 13),
            (4, 17),
            (4, 19),
            (5, 23)
        ]

        for t, i in table:
            gen = SingleFire("Generator", i, t)
            model = simulate(gen)
            assert (t, i) == model.coll.table()[0]

    def test_bulk(self):
        """Tests the :class:`pypdevsbbl.generic.generators.BulkGenerator`."""
        nodes = [SingleFire("Generator", 5, 7), BulkGenerator("Bulker"), TableCollector("TableCollector")]
        conn = [(nodes[0].output, nodes[1].input), (nodes[1].output, nodes[2].input)]
        model = Model(nodes, conn)
        _simulate(model, 15.0)
        assert [(7, i) for i in range(5)] == nodes[2].table()



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