# Source code for pypdevsbbl.tracers

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

    """Create and use tracers for your models.

    This file provides a basic tracer (:class:`BaseTracer`) that can
    be used as a superclass for all custom tracers. Furthermore, a
    :meth:`setTracer` function is provided to allow for more complex
    tracing possibilities.

    The subpackage also provides a few commonly used tracers in
    production-based models.
    """

    from pypdevs.util import runTraceAtController, toStr
    import sys

    # Efficiency can be increased if this dict is cleared, but data can be lost
    # in cases where multiple tracers use the same attributes.
    _hashes = {}
    """Helper variable that will allow the :meth:`setTracer` method to work.

    It is a dict of the :meth:`id`-value of a tracer and its arguments.
    """

    def _hashedTracer(uid, server, tracer, *hargs):
        """Helper function to set the tracer."""
        global _hashes
        TRC = _hashes[tracer]
        args = [_hashes[x] for x in hargs]
        return TRC(uid, server, *args)

    def setTracer(simulator, tracerclass, *args):
        """Assigns a custom tracer with variable arguments.

        In `PythonPDEVS`' custom tracer registration process, all
        additional arguments are casted to a string before passed
        to an :meth:`eval` function call. Not all objects can be
        cast to a string and still retain their information.

        This function allows you to set a tracer with custom
        arguments that cannot be cast to a string.

        See Also:
            :file:`src/dependencies/pythonpdevs/src/pypdevs/tracer.py:40-42`

        Args:
            simulator:      The simulator to set the tracer on.
            tracerclass:    The tracer class to set as an
                            instatiatable.
            *args:          All arguments to pass to the tracer's
                            :meth:`__init__`-call, **except** the
                            ``uid`` and the ``server``.
        """
        global _hashes

        a = []
        for r in [tracerclass] + list(args):
            _hashes[id(r)] = r
            a.append(id(r))
        simulator.setCustomTracer("pypdevsbbl.tracers", "_hashedTracer", a)

    def superTrace(server, uid, model, args):
        runTraceAtController(server, uid, model, [toStr(x) for x in args])

    class BaseTracer:
        """Base for all custom tracers.

        Implements all required methods for a custom tracer.

        Args:
            uid:        A unique identifier for distributed simulation.
                        Can be ignored, but must be the first argument!
            server:     The server that runs the distributed simulation.
                        Can be ignored, but must be the second argument!
            filename:   The name of the file to write the results to.
                        When ``None``, all results will be written to the
                        standard output.
        """
        def __init__(self, uid, server, filename):
            # Contents of the function here are taken from the already existing tracers
            if server.getName() == 0:
                self.filename = filename
            else:
                self.filename = None
            self.server = server
            self.uid = uid

        def startTracer(self, recover):
            """Starts the Tracer

            Args:
                recover (bool): Represents whether or not this is a recovered call.
                                E.g., should the file be overwritten (``False``) or
                                appended to (``True``)?
            """
            if self.filename is None:
                self.file = sys.stdout
            elif recover:
                self.file = open(self.filename, 'a+')
            else:
                self.file = open(self.filename, 'w')

        def stopTracer(self):
            """Stops the tracer (e.g., flush the file)"""
            if self.file is not None:
                self.file.flush()

        def trace(self, *text, sep=' ', end=''):
            """Actual tracing function.

            This function sort of works like the "print" function,
            but only takes strings or bytes as arguments.

            Args:
                text:       The text to trace.
                sep (str):  The text separator. Defaults to a space.
                end (str):  The ending token of the stream to write.
                            Defaults to the empty string.
            """
            def write(obj):
                if isinstance(obj, str):
                    self.file.write(obj)
                elif isinstance(obj, bytes):
                    self.file.write(obj.decode())
                else:
                    self.file.write(str(obj))

            for string in text[:-1]:
                write(string)
                self.file.write(sep)
            write(text[-1])
            write(end)

        def traceInternal(self, aDEVS):
            """Called for each atomic DEVS model that does an internal transition."""
            pass

        def traceExternal(self, aDEVS):
            """Called for each atomic DEVS model that does an external transition."""
            pass

        def traceConfluent(self, aDEVS):
            """Called for each atomic DEVS model that does a confluent transition."""
            pass

        def traceInit(self, aDEVS, t):
            """Called upon initialization of a model.

            Args:
                t (numeric):    The time at which the model commences (likely 0).
            """
            pass

        def traceUser(self, time, aDEVS, variable, value):
            """Called upon so-called *god events* during debugging.

            A *god event* is an event where a user manually alters the state of an
            atomic DEVS instance.
            """
            pass

    # Make these available from the `tracers` subpackge root.
    from pypdevsbbl.tracers.FootprintTracer import FootprintTracer
    from pypdevsbbl.tracers.PlotTracer import PlotTracer
    from pypdevsbbl.tracers.StatisticsTracer import StatisticsTracer
    from pypdevsbbl.tracers.ProfileTracer import ProfileTracer