/*
    SPDX-FileCopyrightText: 2023 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>

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

#pragma once

#include "capturemodulestate.h"
#include "sequencejob.h"

#include "indiapi.h"

#include <QObject>

namespace Ekos
{

class CaptureDeviceAdaptor;
class DarkProcessor;

/**
 * @class CaptureProcess
 * @brief The CaptureProcess class holds the entire business logic to control capturing execution.
 *
 * Capture Execution
 * =================
 * Executing the sequence jobs is a complex process (explained here for light frames) and works roughly
 * as follows and starts by calling {@see Capture#start()} either from the scheduler, by DBUS or by
 * pressing the start button:
 * 1. Select the next sequence job to be executed ({@see startNextPendingJob()}. If the list of jobs is
 * empty, an {@see #addJob()} event is sent. The corresponding callback function
 * {@see #jobAdded(SequenceJob*)} is triggered. Now we know that at least one sequence job is
 * to be executed.
 * 2. Prepare the selected job
 *    - update the counters of captured frames ({@see #prepareJob(SequenceJob *)})
 *    - execute the pre job script, if existing ({@see #prepareActiveJobStage1()})
 *    - set temperature, rotator angle and wait (if required) for the initial guiding
 *      deviation being below the configured threshold ({@see #prepareJobExecution()})
 *      and wait until these parameters are OK.
 * 3. Prepare capturing a single frame
 *    We still need to do some preparation steps before capturing starts.
 *    - {@see #executeJob()} is the starting point, which simply sets the capture state
 *      to "busy" and sets the FITS attributes to the camera
 *    - Check all tasks that need to be completed before capturing may start (post meridian
 *      flip actions, guiding deviation, dithering, re-focusing, ..., see {@see #checkLightFramePendingTasks()}
 * 4. Capture a single frame
 *    - Initiate capturing (set diverse settings of {@see #activeCamera()} (see {@see #captureImage})
 *    - hand over the control of capturing to the sequence job ({@see SequenceJob#startCapturing()})
 *    - Select the correct filter (@see SequenceJobState#prepareTargetFilter()}
 *    - As soon as the correct filter is set, the sequence job state will send the event
 *      {@see SequenceJobState::initCaptureComplete()}, which will finally call trigger
 *      {@see SequenceJob::capture()}
 * 5. Listen upon capturing progress
 *    - listen to the event {@see ISD::Camera::newExposureValue}, update the remaining
 *      time and wait until the INDI state changes from busy to OK
 *    - start the download timer to measure download times
 *    - listen to the event {@see ISD::Camera::newImage} and start processing the FITS image
 *      as soon as it has been recieved
 * 6. Process received image
 *    - update the FITS image meta data {@see #updateImageMetadataAction()}
 *    - update time calculation and counters and execute post capture script ({@see imageCapturingCompleted()})
 * 7. Check how to continue the sequence execution ({@see resumeSequence()})
 *    - if the current sequence job isn't completed,
 *      - execute the post capture script
 *      - start next exposure (similar to 3.) ({@see startNextExposure()})
 *        TODO: check why we need this separate method and cannot use {@see updatePreCaptureCalibrationStatus()}
 *    - if the current sequence is complete,
 *      - execute the post sequence script ({@see processJobCompletion1()})
 *      - stop the current sequence job ({@see processJobCompletion2()})
 *      - recall {@see resumeSequence()}, which calls {@see startNextJob()}
 *      - if there is another job to be executed, jump to 2., otherwise Capture is completed
 *        by sending a stopCapture(CAPTURE_COMPLETE) event
 *
 *  Autofocus
 *  =========
 *  Capture has three ways that trigger autofocus during a capturing sequence: HFR based, temperature drift based,
 *  timer based and post meridian flip based. Each time the capture execution reaches the preparation of caturing
 *  a single frame (3. above) (see {@see CaptureModuleState#startFocusIfRequired()} and
 *  {@see RefocusState#checkFocusRequired()}).
 *
 *  Meridian Flip
 *  =============
 *  The meridian flip itself is executed by the Mount module and is controlled by
 *  (see {@see MeridianFlipState}). Nevertheless, the Capture module plays an
 *  important rule in the meridian flip:
 *  1. Accept a flip to be executed
 *     As soon as a meridian flip has been planned (informed through
 *     {@see #updateMFMountState(MeridianFlipState::MeridianFlipMountState)}, the meridian flip state is set
 *     to MF_REQUESTED.
 *     - If capturing is running the state remains in this state until the frame has been captured. As soon as
 *       the capturing state changes to id, suspended or aborted (see {@see CaptureModuleState::setCaptureState(CaptureState)}),
 *       the meridian flip state is set to MF_ACCEPTED (see {@see MeridianFlipState::updateMeridianFlipStage(const MFStage)}).
 *       This is triggered from {@see #checkLightFramePendingTasks()}, i.e. this function is looping once per second until
 *       the meridian flip has been completed.
 *     - If capturing is not running, the latter happens immediately.
 *     Now the meridian flip is started.
 *  2. Post MF actions
 *     As soon as the meridian flip has been completed (and the Capture module is waiting for it), the Capture module
 *     takes over the control and executes all necessary tasks: aligning, re-focusing, guiding, etc. This happens all through
 *     {@see #checkLightFramePendingTasks()}. As soon as all has recovered, capturing continues.
 */
class CaptureProcess : public QObject
{
    Q_OBJECT

public:
    typedef enum
    {
        ADU_LEAST_SQUARES,
        ADU_POLYNOMIAL
    } ADUAlgorithm;

    CaptureProcess(QSharedPointer<CaptureModuleState> newModuleState, QSharedPointer<CaptureDeviceAdaptor> newDeviceAdaptor);

    // ////////////////////////////////////////////////////////////////////
    // handle connectivity to modules and devices
    // ////////////////////////////////////////////////////////////////////
    /**
     * @brief setMount Connect to the given mount device (and deconnect the old one
     * if existing)
     * @param device pointer to Mount device.
     * @return True if added successfully, false if duplicate or failed to add.
    */
    bool setMount(ISD::Mount *device);

    /**
     * @brief setRotator Connect to the given rotator device (and deconnect
     *  the old one if existing)
     * @param device pointer to rotator INDI device
     * @return True if added successfully, false if duplicate or failed to add.
     */
    bool setRotator(ISD::Rotator * device);

    /**
     * @brief setDustCap Connect to the given dust cap device (and deconnect
     * the old one if existing)
     * @param device pointer to dust cap INDI device
     * @return True if added successfully, false if duplicate or failed to add.
     */
    bool setDustCap(ISD::DustCap *device);

    /**
     * @brief setLightBox Connect to the given dust cap device (and deconnect
     * the old one if existing)
     * @param device pointer to light box INDI device.
     * @return True if added successfully, false if duplicate or failed to add.
    */
    bool setLightBox(ISD::LightBox *device);

    /**
     * @brief setDome Connect to the given dome device
     * @param device point to dome INDI device
     * @return True if added successfully, false if duplicate or failed to add.
     */
    bool setDome(ISD::Dome *device);

    /**
     * @brief setCamera Connect to the given camera device (and deconnect
     * the old one if existing)
     * @param device pointer to camera INDI device.
     * @return True if added successfully, false if duplicate or failed to add.
    */
    bool setCamera(ISD::Camera *device);

    /**
     * @brief setScope Set active train telescope name
     * @param name Name of scope
     */
    void setScope(const QString &name)
    {
        m_Scope = name;
    }

    /**
      * @brief Connect or disconnect the camera device
      * @param connection flag if connect (=true) or disconnect (=false)
      */
    void setCamera(bool connection);

    /**
     * @brief setFilterWheel Connect to the given filter wheel device (and deconnect
     * the old one if existing)
     * @param device pointer to filter wheel INDI device.
     * @return True if added successfully, false if duplicate or failed to add.
    */
    bool setFilterWheel(ISD::FilterWheel *device);

    /**
     * Toggle video streaming if supported by the device.
     * @param enabled Set to true to start video streaming, false to stop it if active.
     */
    void toggleVideo(bool enabled);

    // ////////////////////////////////////////////////////////////////////
    // capturing process steps
    // ////////////////////////////////////////////////////////////////////

    /**
     * @brief toggleSequence Toggle sequence state depending on its current state.
     * 1. If paused, then resume sequence.
     * 2. If idle or completed, then start sequence.
     * 3. Otherwise, abort current sequence.
     */
    void toggleSequence();

    /**
     * @brief startNextPendingJob Start the next pending job.
     *
     * Find the next job to be executed:
     * 1. If there are already some jobs defined, {@see #findNextPendingJob()} is
     *    used to find the next job to be executed.
     * 2. If the list is empty, the current settings are used to create a job instantly,
     *    which subsequently will be executed.
     */
    void startNextPendingJob();

    /**
     * @brief Counterpart to the event {@see#createJob(SequenceJob::SequenceJobType)}
     * where the event receiver reports whether one has been added successfully
     * and of which type it was.
     */
    void jobCreated(SequenceJob *newJob);

    /**
     * @brief capturePreview Capture a preview (single or looping ones)
     */
    void capturePreview(bool loop = false);

    /**
     * @brief stopCapturing Stopping the entire capturing state
     * (envelope for aborting, suspending, pausing, ...)
     * @param targetState state capturing should be having afterwards
     */
    void stopCapturing(CaptureState targetState);

    /**
     * @brief pauseCapturing Pauses capturing as soon as the current
     * capture is complete.
     */
    void pauseCapturing();

    /**
     * @brief startJob Start the execution of a selected sequence job:
     * - Initialize the state for capture preparation ({@see CaptureModuleState#initCapturePreparation()}
     * - Prepare the selected job ({@see #prepareJob(SequenceJob *)})
     * @param job selected sequence job
     */
    void startJob(SequenceJob *job);

    /**
     * @brief prepareJob Update the counters of existing frames and continue with prepareActiveJob(), if there exist less
     *        images than targeted. If enough images exist, continue with processJobCompletion().
     */
    void prepareJob(SequenceJob *job);

    /**
     * @brief prepareActiveJobStage1 Check for pre job script to execute. If none, move to stage 2
     */
    void prepareActiveJobStage1();
    /**
     * @brief prepareActiveJobStage2 Reset #calibrationStage and continue with preparePreCaptureActions().
     */
    void prepareActiveJobStage2();

    /**
     * @brief preparePreCaptureActions Trigger setting the filter, temperature, (if existing) the rotator angle and
     *        let the #activeJob execute the preparation actions before a capture may
     *        take place (@see SequenceJob::prepareCapture()).
     *
     * After triggering the settings, this method returns. This mechanism is slightly tricky, since it
     * asynchronous and event based and works as collaboration between Capture and SequenceJob. Capture has
     * the connection to devices and SequenceJob knows the target values.
     *
     * Each time Capture receives an updated value - e.g. the current CCD temperature
     * (@see updateCCDTemperature()) - it informs the #activeJob about the current CCD temperature.
     * SequenceJob checks, if it has reached the target value and if yes, sets this action as as completed.
     *
     * As soon as all actions are completed, SequenceJob emits a prepareComplete() event, which triggers
     * executeJob() from the CaptureProcess.
     */
    void prepareJobExecution();

    /**
     * @brief executeJob Start the execution of #activeJob by initiating updatePreCaptureCalibrationStatus().
     */
    void executeJob();

    /**
     * @brief refreshOpticalTrain Refresh the devices from the optical train configuration
     * @param name name of the optical train configuration
     */
    void refreshOpticalTrain(QString name);

    /**
     * @brief Check all tasks that might be pending before capturing may start.
     *
     * The following checks are executed:
     *  1. Are there any pending jobs that failed? If yes, return with IPS_ALERT.
     *  2. Has pausing been initiated (@see checkPausing()).
     *  3. Is a meridian flip already running (@see m_MeridianFlipState->checkMeridianFlipRunning()) or ready
     *     for execution (@see CaptureModuleState::checkMeridianFlipReady()).
     *  4. check guide deviation for non meridian flip stages if the initial guide limit is set.
     *     Wait until the guide deviation is reported to be below the limit
     *     (@see Capture::setGuideDeviation(double, double)).
     *  5. Check if dithering is required or running.
     *  6. Check if re-focusing is required
     *     Needs to be checked after dithering checks to avoid dithering in parallel
     *     to focusing, since @startFocusIfRequired() might change its value over time
     *  7. Resume guiding if it was suspended (@see Capture::resumeGuiding())
     *
     * @return IPS_OK iff no task is pending, IPS_BUSY otherwise (or IPS_ALERT if a problem occured)
     */
    IPState checkLightFramePendingTasks();


    /**
     * @brief updatePreCaptureCalibrationStatus This is a wrapping loop for processPreCaptureCalibrationStage(),
     *        which contains all checks before captureImage() may be called.
     *
     * If processPreCaptureCalibrationStage() returns IPS_OK (i.e. everything is ready so that
     * capturing may be started), captureImage() is called. Otherwise, it waits for a second and
     * calls itself again.
     */
    void updatePreCaptureCalibrationStatus();

    /**
     * @brief processPreCaptureCalibrationStage Execute the tasks that need to be completed before capturing may start.
     *
     * For light frames, checkLightFramePendingTasks() is called.
     *
     * @return IPS_OK if all necessary tasks have been completed
     */
    IPState processPreCaptureCalibrationStage();

    /**
     * @brief captureStarted Manage the result when capturing has been started
     */
    void captureStarted(CaptureModuleState::CAPTUREResult rc);

    /**
     * @brief checkNextExposure Try to start capturing the next exposure (@see startNextExposure()).
     *        If startNextExposure() returns, that there are still some jobs pending,
     *        we wait for 1 second and retry to start it again.
     *        If one of the pending preparation jobs has problems, the looping stops.
     */
    void checkNextExposure();

    /**
     * @brief startNextExposure Ensure that all pending preparation tasks are be completed (focusing, dithering, etc.)
     *        and start the next exposure.
     *
     * Checks of pending preparations depends upon the frame type:
     *
     * - For light frames, pending preparations like focusing, dithering etc. needs
     *   to be checked before each single frame capture. efore starting to capture the next light frame,
     *   checkLightFramePendingTasks() is called to check if all pending preparation tasks have
     *   been completed successfully. As soon as this is the case, the sequence timer
     *   #seqTimer is started to wait the configured delay and starts capturing the next image.
     *
     * - For bias, dark and flat frames, preparation jobs are only executed when starting a sequence.
     *   Hence, for these frames we directly start the sequence timer #seqTimer.
     *
     * @return IPS_OK, iff all pending preparation jobs are completed (@see checkLightFramePendingTasks()).
     *         In that case, the #seqTimer is started to wait for the configured settling delay and then
     *         capture the next image (@see Capture::captureImage). In case that a pending task aborted,
     *         IPS_IDLE is returned.
     */

    IPState startNextExposure();

    /**
     * @brief resumeSequence Try to continue capturing.
     *
     * Take the active job, if there is one, or search for the next one that is either
     * idle or aborted. If a new job is selected, call startNextJob() to prepare it.
     * If the current job is still active, initiate checkNextExposure().
     *
     * @return IPS_OK if there is a job that may be continued, IPS_BUSY otherwise.
     */
    IPState resumeSequence();

    /**
     * @brief newFITS process new FITS data received from camera. Update status of active job and overall sequence.
     * @param data pointer to blob containing FITS data
     */
    void processFITSData(const QSharedPointer<FITSData> &data);

    /**
     * @brief setNewRemoteFile A new image has been stored as remote file
     * @param file local file path
     */
    void processNewRemoteFile(QString file);

    /**
     * @brief processCaptureCompleted Manage the capture process after a captured image has been successfully downloaded from the camera.
     *
     * When a image frame has been captured and downloaded successfully, send the image to the client (if configured)
     * and execute the book keeping for the captured frame. After this, either processJobCompletion() is executed
     * in case that the job is completed, and resumeSequence() otherwise.
     *
     * Special case for flat capturing: exposure time calibration is executed in this process step as well.
     *
     * Book keeping means:
     * - increase / decrease the counters for focusing and dithering
     * - increase the frame counter
     * - update the average download time
     *
     * @return IPS_OK if processing has been completed, IPS_BUSY if otherwise.
     */
    IPState processCaptureCompleted();

    /**
     * @brief Manage the capture process after a captured image has been successfully downloaded
     * from the camera.
     *
     * - stop timers for timeout and download progress
     * - update the download time calculation
     * - update captured frames counters ({@see updateCompletedCaptureCountersAction()})
     * - check flat calibration (for flats only)
     * - execute the post capture script (if existing)
     * - resume the sequence ({@see resumeSequence()})
     */
    void imageCapturingCompleted();

    /**
     * @brief processJobCompletionStage1 Process job completion. In stage 1 when simply check if the is a post-job script to be running
     * if yes, we run it and wait until it is done before we move to stage2
     */
    void processJobCompletion1();

    /**
     * @brief processJobCompletionStage2 Stop execution of the current sequence and check whether there exists a next sequence
     *        and start it, if there is a next one to be started (@see resumeSequence()).
     */
    void processJobCompletion2();

    /**
     * @brief startNextJob Select the next job that is either idle or aborted and
     * call prepareJob(*SequenceJob) to prepare its execution and
     * resume guiding if it was suspended (and no meridian flip is running).
     * @return IPS_OK if a job to be executed exists, IPS_IDLE otherwise.
     */
    IPState startNextJob();

    /**
     * @brief captureImage Initiates image capture in the active job.
     */
    void captureImage();

    /**
     * @brief resetFrame Reset frame settings of the camera
     */
    void resetFrame();

    // ////////////////////////////////////////////////////////////////////
    // capturing actions
    // ////////////////////////////////////////////////////////////////////

    /**
     * @brief setExposureProgress Manage exposure progress reported by
     * the camera device.
     */
    void setExposureProgress(ISD::CameraChip *tChip, double value, IPState state);

    /**
     * @brief setDownloadProgress update the Capture Module and Summary
     *        Screen's estimate of how much time is left in the download
     */
    void setDownloadProgress();

    /**
     * @brief continueFramingAction If framing is running, start the next capture sequence
     * @return IPS_OK in all cases
     */
    IPState continueFramingAction(const QSharedPointer<FITSData> &imageData);

    /**
     * @brief updateDownloadTimesAction Add the current download time to the list of already measured ones
     */
    IPState updateDownloadTimesAction();

    /**
     * @brief previewImageCompletedAction Activities required when a preview image has been captured.
     * @return IPS_OK if a preview has been completed, IPS_IDLE otherwise
     */
    IPState previewImageCompletedAction(QSharedPointer<FITSData> imageData);

    /**
     * @brief updateCompletedCaptureCounters Update counters if an image has been captured
     * @return
     */
    IPState updateCompletedCaptureCountersAction();

    /**
     * @brief updateImageMetadataAction Update meta data of a captured image
     */
    IPState updateImageMetadataAction(QSharedPointer<FITSData> imageData);

    /**
     * @brief runCaptureScript Run the pre-/post capture/job script
     * @param scriptType script type (pre-/post capture/job)
     * @param precond additional pre condition for starting the script
     * @return IPS_BUSY, of script exists, IPS_OK otherwise
     */
    IPState runCaptureScript(ScriptTypes scriptType, bool precond = true);

    /**
     * @brief scriptFinished Slot managing the return status of
     * pre/post capture/job scripts
     */
    void scriptFinished(int exitCode, QProcess::ExitStatus status);

    /**
     * @brief setCamera select camera device
     * @param name Name of the camera device
    */
    void selectCamera(QString name);

    /**
     * @brief configureCamera Refreshes the CCD information in the capture module.
     */
    void checkCamera();

    /**
     * @brief syncDSLRToTargetChip Syncs INDI driver CCD_INFO property to the DSLR values.
     * This include Max width, height, and pixel sizes.
     * @param model Name of camera driver in the DSLR database.
     */
    void syncDSLRToTargetChip(const QString &model);

    /**
     * @brief reconnectDriver Reconnect the camera driver
     */
    void reconnectCameraDriver(const QString &camera, const QString &filterWheel);

    /**
     * @brief Generic method for removing any connected device.
     */
    void removeDevice(const QSharedPointer<ISD::GenericDevice> &device);

    /**
     * @brief processCaptureTimeout If exposure timed out, let's handle it.
     */
    void processCaptureTimeout();

    /**
     * @brief processCaptureError Handle when image capture fails
     * @param type error type
     */
    void processCaptureError(ISD::Camera::ErrorType type);

    /**
     * @brief checkFlatCalibration check the flat calibration
     * @param imageData current image data to be analysed
     * @param exp_min minimal possible exposure time
     * @param exp_max maximal possible exposure time
     * @return false iff calibration has not been reached yet
     */
    bool checkFlatCalibration(QSharedPointer<FITSData> imageData, double exp_min, double exp_max);

    /**
     * @brief calculateFlatExpTime calculate the next flat exposure time from the measured ADU value
     * @param currentADU ADU of the last captured frame
     * @return next exposure time to be tried for the flat capturing
     */
    double calculateFlatExpTime(double currentADU);

    /**
     * @brief clearFlatCache Clear the measured values for flat calibrations
     */
    void clearFlatCache();

    /**
     * @brief updateTelescopeInfo Update the scope information in the camera's
     * INDI driver.
     */
    void updateTelescopeInfo();

    /**
     * @brief updateFilterInfo Update the filter information in the INDI
     * drivers of the current camera and dust cap
     */
    void updateFilterInfo();

    // ////////////////////////////////////////////////////////////////////
    // XML capture sequence file handling
    // ////////////////////////////////////////////////////////////////////
    /**
     * Loads the Ekos Sequence Queue file in the Sequence Queue. Jobs are appended to existing jobs.
     * @param fileURL full URL of the filename
     * @param targetName override the target defined in the sequence queue file (necessary for using the target of the scheduler)
     */
     bool loadSequenceQueue(const QString &fileURL, const QString &targetName = "", bool setOptions = true);

    /**
     * Saves the Sequence Queue to the Ekos Sequence Queue file.
     * @param fileURL full URL of the filename
     */
     bool saveSequenceQueue(const QString &path, bool loadOptions = true);

    // ////////////////////////////////////////////////////////////////////
    // helper functions
    // ////////////////////////////////////////////////////////////////////

    /**
     * @brief checkPausing check if a pause has been planned and pause subsequently
     * @param continueAction action to be executed when resume after pausing
     * @return true iff capturing has been paused
     */
    bool checkPausing(CaptureModuleState::ContinueAction continueAction);

    /**
     * @brief findExecutableJob find next job to be executed
     */
    SequenceJob *findNextPendingJob();

    //  Based on  John Burkardt LLSQ (LGPL)
    void llsq(QVector<double> x, QVector<double> y, double &a, double &b);

    /**
     * @brief generateScriptArguments Generate argument list to pass to capture script
     * @return generates argument list consisting of one argument -metadata followed by JSON-formatted key:value pair:
     * -ts UNIX timestamp
     * -image full path to captured image (if any)
     * -size size of file in bytes (if any)
     * -job {name, index}
     * -capture {name, index}
     * -filter
     * TODO depending on user feedback.
     */
    QStringList generateScriptArguments() const;

    /**
     * @brief Does the CCD has a cooler control (On/Off) ?
     */
    bool hasCoolerControl();

    /**
     * @brief Set the CCD cooler ON/OFF
     *
     */
    bool setCoolerControl(bool enable);

    /**
     * @brief restartCamera Restarts the INDI driver associated with a camera. Remote and Local drivers are supported.
     * @param name Name of camera to restart. If a driver defined multiple cameras, they would be removed and added again
     * after driver restart.
     * @note Restarting camera should only be used as a last resort when it comes completely unresponsive. Due the complex
     * nature of driver interactions with Ekos, restarting cameras can lead to unexpected behavior.
     */
    void restartCamera(const QString &name);

    /**
     * @brief frameTypes Retrieve the frame types from the active camera's primary chip.
     */
    QStringList frameTypes();
    /**
     * @brief filterLabels list of currently available filter labels
     */
    QStringList filterLabels();

    /**
     * @brief getGain Update the gain value from the custom property value. Depending
     *        on the camera, it is either stored as GAIN property value of CCD_GAIN or as
     *        Gain property value from CCD_CONTROLS.
     */
    void updateGain(double value, QMap<QString, QMap<QString, QVariant> > &propertyMap);

    /**
     * @brief getOffset Update the offset value from the custom property value. Depending
     *        on the camera, it is either stored as OFFSET property value of CCD_OFFSET or as
     *        Offset property value from CCD_CONTROLS.
     */
    void updateOffset(double value, QMap<QString, QMap<QString, QVariant> > &propertyMap);


    // ////////////////////////////////////////////////////////////////////
    // attributes access
    // ////////////////////////////////////////////////////////////////////
    QProcess &captureScript()
    {
        return m_CaptureScript;
    }

signals:
    // controls for capture execution
    void addJob (SequenceJob *job);
    void createJob(SequenceJob::SequenceJobType jobtype = SequenceJob::JOBTYPE_BATCH);
    void jobStarting();
    void stopCapture(CaptureState targetState = CAPTURE_IDLE);
    void captureAborted(double exposureSeconds);
    void captureStopped();
    void syncGUIToJob(SequenceJob *job);
    void updateFrameProperties(int reset);
    void updateJobTable(SequenceJob *job, bool full = false);
    void jobExecutionPreparationStarted();
    void jobPrepared(SequenceJob *job);
    void captureImageStarted();
    void captureTarget(QString targetName);
    void captureRunning();
    void newExposureProgress(SequenceJob *job);
    void newDownloadProgress(double downloadTimeLeft);
    void downloadingFrame();
    void updateCaptureCountDown(int deltaMS);
    void darkFrameCompleted();
    void updateMeridianFlipStage(MeridianFlipState::MFStage stage);
    void cameraReady();
    void refreshCamera();
    void refreshCameraSettings();
    void refreshFilterSettings();
    void processingFITSfinished(bool success);
    void rotatorReverseToggled(bool enabled);
    // communication with other modules
    void newImage(SequenceJob *job, const QSharedPointer<FITSData> &data);
    void suspendGuiding();
    void resumeGuiding();
    void abortFocus();
    void captureComplete(const QVariantMap &metadata);
    void sequenceChanged(const QJsonArray &sequence);
    void driverTimedout(const QString &deviceName);
    // new log text for the module log window
    void newLog(const QString &text);


private:
    QSharedPointer<CaptureModuleState> m_State;
    QSharedPointer<CaptureDeviceAdaptor> m_DeviceAdaptor;
    QPointer<DarkProcessor> m_DarkProcessor;

    // Pre-/post capture script process
    QProcess m_CaptureScript;
    QString m_Scope;
    // Flat field automation
    QVector<double> ExpRaw, ADURaw;
    ADUAlgorithm targetADUAlgorithm { ADU_LEAST_SQUARES };


    /**
     * @brief activeJob Shortcut for the module state
     */
    QSharedPointer<CaptureModuleState> state() const
    {
        return m_State;
    }

    /**
     * @brief activeJob Shortcut to device adapter
     */
    QSharedPointer<CaptureDeviceAdaptor> devices()
    {
        return m_DeviceAdaptor;
    }

    /**
     * @brief activeJob Shortcut to the active job held in the state machine
     */
    SequenceJob *activeJob()
    {
        return  state()->getActiveJob();
    }

    /**
     * @brief activeCamera Shortcut to the active camera held in the device adaptor
     */
    ISD::Camera *activeCamera();

    /**
     * @brief resetAllJobs Iterate over all jobs and reset them.
     */
    void resetAllJobs();
    /**
     * @brief resetJobStatus Reset a single job to the given status
     */
    void resetJobStatus(JOBStatus newStatus);
    /**
     * @brief updatedCaptureCompleted Update the completed captures count to the given
     * number.
     */
    void updatedCaptureCompleted(int count);
};
} // Ekos namespace
