//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2005-2025, Thierry Lelegard
// BSD-2-Clause license, see LICENSE.txt file or https://tsduck.io/license
//
//----------------------------------------------------------------------------
//!
//!  @file
//!  Input switch (tsswitch) core engine.
//!
//----------------------------------------------------------------------------

#pragma once
#include "tsInputSwitcherArgs.h"
#include "tstsswitchInputExecutor.h"
#include "tstsswitchOutputExecutor.h"
#include "tstsswitchEventDispatcher.h"
#include "tsWatchDog.h"

namespace ts {
    //!
    //! Input switch (tsswitch) namespace.
    //!
    namespace tsswitch {
        //!
        //! Input switch (tsswitch) core engine.
        //! @ingroup libtsduck plugin
        //!
        class Core: private WatchDogHandlerInterface
        {
            TS_NOBUILD_NOCOPY(Core);
        public:
            //!
            //! Constructor.
            //! @param [in] opt Command line options.
            //! @param [in] handlers Registry of plugin event handlers.
            //! @param [in,out] log Log report.
            //!
            Core(const InputSwitcherArgs& opt, const PluginEventHandlerRegistry& handlers, Report& log);

            //!
            //! Destructor.
            //!
            virtual ~Core() override;

            //!
            //! Start the @c tsswitch processing.
            //! @return True on success, false on error.
            //!
            bool start();

            //!
            //! Stop the @c tsswitch processing.
            //! @param [in] success False if the stop is triggered by an error.
            //!
            void stop(bool success);

            //!
            //! Wait for completion of all plugin threads.
            //!
            void waitForTermination();

            //!
            //! Switch to another input plugin.
            //! @param [in] pluginIndex Index of the new input plugin.
            //!
            void setInput(size_t pluginIndex);

            //!
            //! Switch to the next input plugin.
            //!
            void nextInput();

            //!
            //! Switch to the previous input plugin.
            //!
            void previousInput();

            //!
            //! Get the index of the current input plugin.
            //! @return The index of the current input plugin.
            //!
            size_t currentInput();

            //!
            //! Called by an input plugin when it started an input session.
            //! @param [in] pluginIndex Index of the input plugin.
            //! @param [in] success True if the start operation succeeded.
            //! @return False when @c tsswitch is terminating.
            //!
            bool inputStarted(size_t pluginIndex, bool success);

            //!
            //! Called by an input plugin when it received input packets.
            //! @param [in] pluginIndex Index of the input plugin.
            //! @return False when @c tsswitch is terminating.
            //!
            bool inputReceived(size_t pluginIndex);

            //!
            //! Called by an input plugin when it stopped an input session.
            //! @param [in] pluginIndex Index of the input plugin.
            //! @param [in] success True if the stop operation succeeded.
            //! @return False when @c tsswitch is terminating.
            //!
            bool inputStopped(size_t pluginIndex, bool success);

            //!
            //! Called by the output plugin when it needs some packets to output.
            //! Wait until there is some packets to output.
            //! @param [out] pluginIndex Returned index of the input plugin.
            //! @param [out] first Returned address of first packet to output.
            //! @param [out] data Returned address of metadata for the first packet to output.
            //! @param [out] count Returned number of packets to output.
            //! Never zero, except when @c tsswitch is terminating.
            //! @return False when @c tsswitch is terminating.
            //!
            bool getOutputArea(size_t& pluginIndex, TSPacket*& first, TSPacketMetadata*& data, size_t& count);

            //!
            //! Called by the output plugin after sending packets.
            //! @param [in] pluginIndex Index of the input plugin from which the packets were sent.
            //! @param [in] count Number of output packets to release.
            //! @return False when @c tsswitch is terminating.
            //!
            bool outputSent(size_t pluginIndex, size_t count);

        private:
            // Upon reception of an event (end of input, remote command, etc), there
            // is a list of actions to execute which depends on the switch policy.
            // Types of actions (can also be used as bit mask):
            enum ActionType {
                NONE            = 0x0001,  // Nothing to do.
                START           = 0x0002,  // Start a plugin.
                WAIT_STARTED    = 0x0004,  // Wait for start completion of a plugin.
                WAIT_INPUT      = 0x0008,  // Wait for input packets on a plugin.
                STOP            = 0x0010,  // Stop a plugin.
                WAIT_STOPPED    = 0x0020,  // Wait for stop completion of a plugin.
                NOTIF_CURRENT   = 0x0040,  // Notify a plugin it is the current one (or not).
                SET_CURRENT     = 0x0080,  // Set current plugin index.
                RESTART_TIMEOUT = 0x0100,  // Restart the input timeout on current input.
                SUSPEND_TIMEOUT = 0x0200,  // Suspend the input timeout on current input.
                ABORT_INPUT     = 0x0400,  // Abort current input if flags is true.
            };

            // Description of an action with its parameters.
            class Action: public StringifyInterface
            {
            public:
                ActionType type;   // Action to execute.
                size_t     index;  // Input plugin index.
                bool       flag;   // Boolean parameter (depends on the action).

                // Constructor.
                Action(ActionType t = NONE, size_t i = 0, bool f = false) : type(t), index(i), flag(f) {}

                // Copy constructor, changing the flag.
                Action(const Action& other, bool f) : type(other.type), index(other.index), flag(f) {}

                // Implement StringifyInterface.
                virtual UString toString() const override;

                // Operator "less" for containers.
                bool operator<(const Action& a) const;
            };

            using ActionSet = std::set<Action>;
            using ActionQueue = std::deque<Action>;

            Report&                     _log;               // Asynchronous log report.
            const InputSwitcherArgs&    _opt;               // Command line options.
            InputExecutorVector         _inputs;            // Input plugins threads.
            OutputExecutor              _output;            // Output plugin thread.
            EventDispatcher             _eventDispatcher;   // External event dispatcher.
            WatchDog                    _receiveWatchDog;   // Handle reception timeout.
            std::recursive_mutex        _mutex {};          // Global mutex, protect access to all subsequent fields.
            std::condition_variable_any _gotInput {};       // Signaled each time an input plugin reports new packets.
            size_t                      _curPlugin = 0;     // Index of current input plugin.
            size_t                      _curCycle = 0;      // Current input cycle number.
            volatile bool               _terminate = false; // Terminate complete processing.
            ActionQueue                 _actions {};        // Sequential queue list of actions to execute.
            ActionSet                   _events {};         // Pending events, waiting to be cleared.

            // Names of actions for debug messages.
            static const Names _actionNames;

            // Change input plugin with mutex already held.
            void setInputLocked(size_t index, bool abortCurrent);

            // Enqueue an action (with mutex already held).
            void enqueue(const Action& action, bool highPriority = false);

            // Remove all instructions with type in bitmask (with mutex already held).
            void cancelActions(int typeMask);

            // Execute all commands until one needs to wait (with mutex already held).
            // The event can be used to unlock a wait action.
            void execute(const Action& event = Action());

            // Implementation of WatchDogHandlerInterface
            virtual void handleWatchDogTimeout(WatchDog& watchdog) override;
        };
    }
}
