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

Note:
    The :class:`pypdevsbbl.generic.io.Sound` block cannot be tested in a unit test,
    but it has been checked that the sounds will be played. To test this yourself,
    execute the :mod:`pypdevsbbl.generic.io` module.

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

import unittest
import tempfile

from pypdevsbbl.generic.collectors import TableCollector
from pypdevsbbl.generic.generators import TableGenerator, SingleFire
from pypdevsbbl.generic.io import *
from pypdevs import logger
from pypdevs.DEVS import CoupledDEVS
from pypdevs.simulator import Simulator

from test.generic import simulate as _simulate


class StubLogger:
    """Helper class to *cheat* the usage of an actual logger."""
    def __init__(self):
        self.lst_debug = []
        self.lst_info = []
        self.lst_notice = []
        self.lst_warn = []
        self.lst_error = []
        self.lst_critical = []
        self.lst_alert = []
        self.lst_emergency = []

    def debug(self, msg):
        self.lst_debug.append(msg)

    def info(self, msg):
        self.lst_info.append(msg)

    def notice(self, msg):
        self.lst_notice.append(msg)

    def warn(self, msg):
        self.lst_warn.append(msg)

    def error(self, msg):
        self.lst_error.append(msg)

    def critical(self, msg):
        self.lst_critical.append(msg)

    def alert(self, msg):
        self.lst_alert.append(msg)

    def emerg(self, msg):
        self.lst_emergency.append(msg)


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

    Args:
        table (list):   A list with all events that should happen.
        block:          The block to connect to the
                        :class:`pypdevsbbl.generic.generators.TableGenerator`.
        inp:            The input port to connect the generator on.
    """
    def __init__(self, table, block, inp):
        super(IOModel, self).__init__("Model")

        self.gen = self.addSubModel(TableGenerator("Generator", table))
        self.block = self.addSubModel(block)

        self.connectPorts(self.gen.output, inp)


def simulate(table, block, inp, tt=None):
    """Simulates the model.

    Args:
        table (list):   A list with all events that should happen.
        block:          The block to connect to the
                        :class:`pypdevsbbl.generic.generators.TableGenerator`.
        inp:            The input port to connect the generator on.
        tt (numeric):   The termination time or ``None`` if
                        no termination time needs to be set.
                        Defaults to ``None``.
    """
    model = IOModel(table, block, inp)
    _simulate(model, tt)
    return model


class TestIO(unittest.TestCase):
    def setUp(self):
        self.items = ["This is a test.", "Some text can change in any way.", "ABC"]
        self.table = [(x, self.items[x]) for x in range(len(self.items))]

    def test_logger(self):
        """Tests the :class:`pypdevsbbl.generic.io.Logger` and all its subclasses."""
        loggers = [Debug, Info, Notice, Warn, Error, Critical, Alert, Emergency]

        for l in loggers:
            logger.logger = StubLogger()
            logger.location = 154

            block = l("Logger")
            simulate(self.table, block, block.input)

            for k in loggers:
                exp = []
                if k == l:
                    exp = ["154 -- %s" % x for x in self.items]
                assert getattr(logger.logger, "lst_" + k.__name__.lower()) == exp

    def test_file_writer(self):
        """Tests the :class:`pypdevsbbl.generic.io.FileWriter`."""
        file = tempfile.NamedTemporaryFile()
        block = FileWriter("Writer", file.name)
        simulate(self.table, block, block.input)
        assert file.read().decode('utf-8') == "".join(self.items)
        file.close()

    def test_file_reader(self):
        """Tests the :class:`pypdevsbbl.generic.io.FileReader`."""
        class FRModel(CoupledDEVS):
            def __init__(self):
                super(FRModel, self).__init__("Model")

                self.gen = self.addSubModel(TableGenerator("Generator", [(0, str(__file__))]))
                self.fr = self.addSubModel(FileReader("FileReader"))
                self.coll = self.addSubModel(TableCollector("Collector"))

                self.connectPorts(self.gen.output, self.fr.filename)
                self.connectPorts(self.fr.contents, self.coll.input)

        model = FRModel()
        sim = Simulator(model)
        sim.setClassicDEVS()
        sim.simulate()

        with open(__file__) as file:
            assert file.read() == model.coll.table()[0][1]

    def test_listener(self):
        """Tests the :class:`pypdevsbbl.generic.io.Listener`."""
        class LModel(CoupledDEVS):
            def __init__(self):
                super(LModel, self).__init__("Model")

                self.lst = self.addSubModel(Listener("Listener", 1, lambda x, y: x * y, 2))
                self.sf1 = self.addSubModel(SingleFire("SingleFire1", 3, 4))
                self.sf2 = self.addSubModel(SingleFire("SingleFire2", 2, 9))
                self.coll = self.addSubModel(TableCollector("Collector"))

                self.connectPorts(self.lst.listen, self.coll.input)
                self.connectPorts(self.sf1.output, self.lst.dt)
                self.connectPorts(self.sf2.output, self.lst.dt)

        model = LModel()
        sim = Simulator(model)
        sim.setClassicDEVS()
        sim.setTerminationTime(11)
        sim.setVerbose(None)
        sim.simulate()

        assert [(0, 0), (1, 2), (2, 4), (3, 6), (4, 8), (7, 14), (9, 18), (11, 22)] == model.coll.table()

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