/*
    SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
    SPDX-FileCopyrightText: 2024 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include "capturetypes.h"
#include "ekos/manager/meridianflipstate.h"
#include "ekos/ekos.h"

#include <QObject>
#include <QTimer>

namespace Ekos
{
class Camera;
class CameraState;

class CaptureModuleState : public QObject
{
        Q_OBJECT

        friend class Capture;

    Q_SIGNALS:
        void dither();
        // new log text for the module log window
        void newLog(const QString &text);

    public:
        explicit CaptureModuleState(QObject *parent = nullptr);

        // ////////////////////////////////////////////////////////////////////
        // shared attributes
        // ////////////////////////////////////////////////////////////////////
        bool forceInSeqAF(const QString opticaltrain) const
        {
            if (m_ForceInSeqAF.contains(opticaltrain))
                return m_ForceInSeqAF[opticaltrain];
            else
                // default value is off
                return false;
        }
        void setForceInSeqAF(bool value, const QString opticaltrain)
        {
            m_ForceInSeqAF[opticaltrain] = value;
        }

        // ////////////////////////////////////////////////////////////////////
        // Cameras
        // ////////////////////////////////////////////////////////////////////
        const QList<QSharedPointer<Camera>> &cameras() const
        {
            return m_Cameras;
        }
        QList<QSharedPointer<Camera>> &mutableCameras()
        {
            return m_Cameras;
        }

        void addCamera(QSharedPointer<Camera> newCamera);
        void removeCamera(int pos);

        // ////////////////////////////////////////////////////////////////////
        // State handling
        // ////////////////////////////////////////////////////////////////////

        // Capturing
        void captureStateChanged(CaptureState value, const QString &trainname, int cameraID);
        // Guiding
        void setGuideStatus(GuideState newstate);
        void setGuideDeviation(double delta_ra, double delta_dec);
        // meridian flip
        void updateMFMountState(MeridianFlipState::MeridianFlipMountState status);

        // ////////////////////////////////////////////////////////////////////
        // Action handling
        // ////////////////////////////////////////////////////////////////////
        /**
         * @brief activeAction Currently active action for a single camera, coordinated by the capture module
         * @param cameraID ID of the camera
         * @return workflow action, or CAPTURE_ACTION_NONE if none is active
         */
        CaptureWorkflowActionType activeAction(int cameraID);

        /**
         * @brief isActionActive Determine whether an action is actively coordinated by the capture module
         * @param cameraID ID of the camera
         */
        bool isActionActive(int cameraID)
        {
            return m_activeActions.contains(cameraID);
        }

    private:

        // ////////////////////////////////////////////////////////////////////
        // shared attributes
        // ////////////////////////////////////////////////////////////////////
        // User has requested an autofocus run as soon as possible
        QMap<QString, bool> m_ForceInSeqAF;

        // ////////////////////////////////////////////////////////////////////
        // Cameras
        // ////////////////////////////////////////////////////////////////////
        QList<QSharedPointer<Camera>> m_Cameras;

        // ////////////////////////////////////////////////////////////////////
        // Action handling
        // ////////////////////////////////////////////////////////////////////

        // currently active actions, maximal one per camera
        QMap<int, CaptureWorkflowActionType> m_activeActions;
        // planned action queues, one queue per camera
        QMap<int, QQueue<CaptureWorkflowActionType>> m_actionQueues;

        /**
         * @brief getActionQueue Retrieve the action queue for a given camera ID
         */
        QQueue<CaptureWorkflowActionType> &getActionQueue(int cameraId);

        /**
         * @brief setActiveAction Set the currently active action for a camera, that is
         *        coordinated by the capture module
         * @param cameraID ID of the camera
         * @param action workflow action, or CAPTURE_ACTION_NONE if none is active
         */
        void setActiveAction(int cameraID, CaptureWorkflowActionType action);

        /**
         * @brief requestAction Handle the execution request of an action.
         * @param cameraID ID of the camera
         * @param action workflow action
         */
        void handleActionRequest(int cameraID, CaptureWorkflowActionType action);

        /**
         * @brief checkActionExecution Check if next actions need to be executed.
         */
        void checkActionExecution();

        /**
         * @brief enqueueAction enqueue an action for future execution
         * @param cameraID ID of the camera for which the action should be executed
         */
        void enqueueAction(int cameraID, CaptureWorkflowActionType action);

        /**
         * @brief checkActiveActions Iterate over all cameras and check if the active
         *        actions may be executed and trigger next action from the action queue
         *        if no active action exists.
         */
        void checkActiveActions();

        /**
         * @brief checkAndExecuteNextAction check if there is an action pending for the
         *        given camera.
         * @param cameraID ID of the camera for which the action should be executed. If the
         *        ID == -1, execute this check for all cameras.
         */
        void checkNextActionExecution(int cameraID = -1);

        /**
         * @brief clearAllActions Clear all action queues and reset the active actions.
         */
        void clearAllActions(int cameraID);

        // ////////////////////////////////////////////////////////////////////
        // Action execution
        // ////////////////////////////////////////////////////////////////////

        /**
         * @brief prepareDitheringAction If a dither request has been received, capturing
         *        either requests all capturing cameras to pause or, if this would take too long,
         *        aborts their capturing. As a threshold, we set to pause all cameras where the
         *        remaining time is less than 50% of the exposure time of the camera that requested
         *        the dithering. All others will be aborted.
         * @param cameraId ID of the camera that requests dithering
         */
        void prepareDitheringAction(int cameraId);

        /**
         * @brief checkReadyForDithering Check if dithering may be requested, i.e. for no camera
         *        capturing is still running. If this is the case, request dithering.
         * @param cameraId ID of the camera that requests dithering
         */
        void checkReadyForDithering(int cameraId);

        /**
         * @brief startDithering Enforce dithering, abort all other capturing processes
         */
        void startDithering();

        /**
         * @brief setupRestartPostMF Setup the checks required before the suspended follower jobs
         *        might be restarted.
         */
        void setupRestartPostMF();

        /**
         * @brief pauseCapturingImmediately Pause capturing immediately by calling first to pause
         *        and subsequently to suspend.
         */
        void pauseCapturingImmediately(int cameraID = -1, bool followersOnly = true);

        // timer to avoid waiting infinitely long for settling of other cameras
        QTimer m_DitheringTimer;

        // meridian flip mount state
        MeridianFlipState::MeridianFlipMountState m_MFMountState {MeridianFlipState::MOUNT_FLIP_NONE};

        // ////////////////////////////////////////////////////////////////////
        // helper functions
        // ////////////////////////////////////////////////////////////////////
        /**
         * @brief Check if a meridian flip is ready to start, running or some post flip actions are not completed.
         */
        bool checkMeridianFlipActive();

        /**
         * @brief leadState Retrieve the state machine of the lead camera.
         */
        const QSharedPointer<CameraState> leadState();
};
} // namespace Ekos
