#  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 Collector 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 -> Collector``

Note:
    The :class:`pypdevsbbl.generic.collectors.TableCollector` will not be tested
    in a standalone-way. Its correctness will be evaluated w.r.t. all other
    tests that are executed.

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

import unittest
from pypdevsbbl.generic.collectors import TableCollector, Collector, EstimateCollector, Counter
from pypdevsbbl.generic.generators import ConstantGenerator, TableGenerator, SingleFire

from test.generic import Model, simulate

import numpy as np

class TestCollectors(unittest.TestCase):
    def setUp(self):
        self.items = [3, 7, 4, 12, 6, 8, 2]
        self.table = [(i, self.items[i]) for i in range(len(self.items))]

    def test_collector(self):
        """Tests the :class:`pypdevsbbl.generic.collectors.Collector`."""
        gen = TableGenerator("Generator", self.table)
        coll = Collector("Collector")

        model = Model((gen, coll), ((gen.output, coll.input),))
        simulate(model)

        assert coll.min() == np.min(self.items)
        assert coll.max() == np.max(self.items)
        assert coll.sum() == np.sum(self.items)
        assert coll.sumOfSquares() == np.sum([x**2 for x in self.items])
        assert coll.count() == len(self.items)
        assert coll.mean() == np.mean(self.items)
        assert coll.variance() == np.var(self.items)
        assert coll.std() == np.std(self.items)

    def test_collector_clear(self):
        """Tests the clearing of the :class:`pypdevsbbl.generic.collectors.Collector`."""
        gen = TableGenerator("Generator", self.table)
        coll = Collector("Collector")

        sf = SingleFire("SF", True, 3)
        model = Model((gen, coll, sf), ((gen.output, coll.input), (sf.output, coll.clear)))
        simulate(model)

        items = self.items[4:]
        assert coll.min() == np.min(items)
        assert coll.max() == np.max(items)
        assert coll.sum() == np.sum(items)
        assert coll.sumOfSquares() == np.sum([x ** 2 for x in items])
        assert coll.count() == len(items)
        assert coll.mean() == np.mean(items)
        assert coll.variance() == np.var(items)
        assert coll.std() == np.std(items)

    def test_estimate(self):
        """Tests the :class:`pypdevsbbl.generic.collectors.EstimateCollector`."""
        gen = TableGenerator("Generator", self.table)
        coll = EstimateCollector("Collector")

        model = Model((gen, coll), ((gen.output, coll.input),))
        simulate(model)
        coll = model.nodes[1]

        q = coll.state.histoplot()[0]

        # Q1, median and Q3 are estimates => we cannot test their accuracy
        # for [3, 7, 4, 12, 6, 8, 2], the median estimate equals the actual estimate

        assert q[0] == np.min(self.items)
        assert q[2] == np.median(self.items)
        assert q[4] == np.max(self.items)

    def test_estimate_clear(self):
        """Tests the clearing of the :class:`pypdevsbbl.generic.collectors.EstimateCollector`."""
        gen = TableGenerator("Generator", self.table)
        coll = EstimateCollector("Collector")

        sf = SingleFire("SF", True, 1)
        model = Model((gen, coll, sf), ((gen.output, coll.input), (sf.output, coll.clear)))
        simulate(model)

        items = self.items[2:]

        q = coll.state.histoplot()[0]

        assert q[0] == np.min(items)
        assert q[1] == np.percentile(items, 25)
        assert q[2] == np.median(items)
        assert q[3] == np.percentile(items, 75)
        assert q[4] == np.max(items)

    def test_counter(self):
        """Tests the :class:`pypdevsbbl.generic.collectors.EstimateCollector`."""

        gen = ConstantGenerator("Generator")
        cnt = Counter("Counter")
        collc = TableCollector("CCollector")
        colli = TableCollector("ICollector")

        model = Model([gen, cnt, collc, colli], [(gen.output, cnt.input), (cnt.output, colli.input), (cnt.count, collc.input)])
        simulate(model, 10.0)

        assert [x + 1 for x in range(11)] == [x[1] for x in model.nodes[-2].table()]
        assert [0] * 11 == [x[1] for x in model.nodes[-1].table()]

    def test_counter_clear(self):
        """Tests the clearing of the :class:`pypdevsbbl.generic.collectors.EstimateCollector`."""

        gen = ConstantGenerator("Generator")
        cnt = Counter("Counter")
        collc = TableCollector("CCollector")
        colli = TableCollector("ICollector")
        sf = SingleFire("SF", True, 5)

        model = Model([gen, cnt, collc, colli, sf], [
            (gen.output, cnt.input), (cnt.output, colli.input), (cnt.count, collc.input), (sf.output, cnt.clear)])
        simulate(model, 10.0)

        assert [(x % 6) + 1 for x in range(11)] == [x[1] for x in model.nodes[-3].table()]
        assert [0] * 11 == [x[1] for x in model.nodes[-2].table()]


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