/*
    KStars UI tests for alignment

    SPDX-FileCopyrightText: 2021 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>

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

#pragma once

#include "config-kstars.h"
#include "test_ekos.h"
#include "test_ekos_capture_helper.h"

#if defined(HAVE_INDI)

#include <QObject>

#define KTRY_CHECK_ALIGNMENTS(i) \
    qCInfo(KSTARS_EKOS_TEST) << "Alignment finished," << image_count << "captures," \
    << solver_count << "successful plate solves."; \
    QVERIFY(image_count == i); \
    QVERIFY(solver_count == i)


class TestEkosAlign : public QObject
{
    Q_OBJECT
public:
    explicit TestEkosAlign(QObject *parent = nullptr);

protected:
    // sequence of alignment states that are expected
    QQueue<Ekos::AlignState> expectedAlignStates;
    // sequence of telescope states that are expected
    QQueue<ISD::Mount::Status> expectedTelescopeStates;
    // sequence of capture states that are expected
    QQueue<Ekos::CaptureState> expectedCaptureStates;

    /**
     * @brief Track a single alignment
     * @param lastPoint true iff last alignment point
     * @param moveMount if true, an intermediate mount move is issued
     * @return true iff the alignment succeeded
     */
    bool trackSingleAlignment(bool lastPoint, bool moveMount);

protected slots:
    void initTestCase();
    void cleanupTestCase();

    void init();
    void cleanup();

    void prepareTestCase();

    bool prepareMountModel(int points);

    /**
     * @brief Execute a mount model creation
     * @param points number of alignment points
     * @param moveMount move the mount during image capturing
     * @return true iff the mount model creation succeeded
     */
    bool runMountModelTool(int points, bool moveMount);

    /**
     * @brief Execute a single alignment and check if the alignment target matches the given position
     * @param targetObject expected target
     * @return true iff the alignment was successful
     */
    bool executeAlignment(SkyObject *targetObject);

    /**
     * @brief Verify if the alignment target matches the expected coordinates
     * @param targetObject
     * @return true iff the alignment target coordinates match those of the target object
     */
    bool verifyAlignmentTarget(SkyObject *targetObject);

    /**
     * @brief Helper function to test alignment with the scheduler running
     * @param targetObject pointer to the target object
     * @param fitsTarget path to a FITS image. Leave null if none should be used
     * @return true iff the alignment target coordinates match those of the target object
     */
    bool alignWithScheduler(SkyObject *targetObject, QString fitsTarget = nullptr);

    /**
     * @brief find a target by the name
     * @param name target name
     * @return target with JNow and J2000 coordinates set
     */
    SkyObject *findTargetByName(QString name);

    /**
     * @brief update J2000 coordinates
     */
    void updateJ2000Coordinates(SkyPoint *target);

private:
    // helper class
    TestEkosCaptureHelper *m_CaptureHelper = nullptr;

    // test directory
    QTemporaryDir *testDir;

    // current alignment status
    Ekos::AlignState m_AlignStatus { Ekos::ALIGN_IDLE };

    // current scope status
    ISD::Mount::Status m_TelescopeStatus { ISD::Mount::MOUNT_IDLE };

    // current capture status
    Ekos::CaptureState m_CaptureStatus { Ekos::CAPTURE_IDLE };

    /**
     * @brief Slot to track the align status of the mount
     * @param status new align state
     */
    void alignStatusChanged(Ekos::AlignState status);

    /**
     * @brief Slot to track the mount status
     * @param status new mount state
     */
    void telescopeStatusChanged(ISD::Mount::Status status);

    /**
     * @brief Slot to track the capture status
     * @param status new capture status
     */
    void captureStatusChanged(Ekos::CaptureState status);

    /**
     * @brief slot to track captured images from the align process
     * @param view
     */
    void imageReceived(const QSharedPointer<FITSView> &view);

    // counter for images taken in a single test run
    int image_count;

    /**
     * @brief slot to track solver results from the align process
     * @param orientation image orientation
     * @param ra RA position
     * @param dec DEC position
     * @param pixscale image scale of a single image pixel
     */
    void solverResultReceived(double orientation, double ra, double dec, double pixscale);

    // counter of solver results in a single test run
    int solver_count;

private slots:
    /**
     * @brief Test a simple slew and a subsequent alignment
     */
    void testSlewAlign();

    /**
     * @brief Test alignment for a sky position in an empty sky segment
     */
    void testEmptySkyAlign();

    /**
     * @brief Test if only syncs have been used the alignment uses the latest
     *        sync as target.
     */
    void testSyncOnlyAlign();

    /**
     * @brief Test if guide drifting leaves the alignment target untouched
     */
    void testSlewDriftAlign();

    /**
     * @brief Test aligning to target where the target definition comes from a FITS image.
     */
    void testFitsAlign();

    /**
     * @brief Test alignment to the target of a scheduled job
     */
    void testAlignTargetScheduledJob();

    /**
     * @brief Test alignment to a FITS target of a scheduled job
     */
    void testFitsAlignTargetScheduledJob();

    /** @brief Test if aligning gets suspended when a slew occurs and recovers when
     *         the scope returns to tracking mode */
    void testAlignRecoverFromSlewing();

    /** @brief Test the mount model */
    void testMountModel();

    /** @brief Test if a slew event during the mount model creation is handled correctly
     *         by suspending when a slew occurs and recovers when the scope returns
     *         to tracking mode */
    void testMountModelToolRecoverFromSlewing();
};

#endif // HAVE_INDI
