/*!
 * \file ClientAPIForAlignmentDatabase.h
 *
 * \author Roger James
 * \date 13th November 2013
 *
 */

#pragma once

#include "basedevice.h"
#include "baseclient.h"

#include "Common.h"
#include <pthread.h>

namespace INDI
{
namespace AlignmentSubsystem
{
/*!
 * \class ClientAPIForAlignmentDatabase
 * \brief This class provides the client API to the driver side alignment database. It communicates
 * with the driver via the INDI properties interface.
 */
class ClientAPIForAlignmentDatabase
{
    public:
        /// \brief Default constructor
        ClientAPIForAlignmentDatabase();

        /// \brief Virtual destructor
        virtual ~ClientAPIForAlignmentDatabase();

        /**
         * \brief Append a sync point to the database.
         * \param[in] CurrentValues The entry to append.
         * \return True if successful
         */
        bool AppendSyncPoint(const AlignmentDatabaseEntry &CurrentValues);

        /**
         * \brief Delete all sync points from the database.
         * \return True if successful
         */
        bool ClearSyncPoints();

        /**
         * \brief Delete a sync point from the database.
         * \param[in] Offset Pointer to the entry to delete
         * \return True if successful
         */
        bool DeleteSyncPoint(unsigned int Offset);

        /**
         * \brief Edit a sync point in the database.
         * \param[in] Offset Pointer to where to make the edit.
         * \param[in] CurrentValues The entry to edit.
         * \return True if successful
         */
        bool EditSyncPoint(unsigned int Offset, const AlignmentDatabaseEntry &CurrentValues);

        /**
         * \brief Return the number of entries in the database.
         * \return The number of entries in the database
         */
        int GetDatabaseSize();

        /**
         * \brief Initialise the API
         * \param[in] BaseClient A pointer to the INDI::BaseClient class
         */
        void Initialise(INDI::BaseClient *BaseClient);

        /**
         * \brief Insert a sync point in the database.
         * \param[in] Offset Pointer to where to make then insertion.
         * \param[in] CurrentValues The entry to insert.
         * \return True if successful
         */
        bool InsertSyncPoint(unsigned int Offset, const AlignmentDatabaseEntry &CurrentValues);

        /**
         * \brief Load the database from persistent storage
         * \return True if successful
         */
        bool LoadDatabase();

        /**
         * \brief Process new BLOB message from driver. This routine should be called from within
         * the newBLOB handler in the client. This routine is not normally called directly but is called by
         * the ProcessNewBLOB function in INDI::Alignment::AlignmentSubsystemForClients which filters out
         * calls from unwanted devices. TODO maybe hide this function.
         * \param[in] BLOBPointer A pointer to the INDI::IBLOB.
         */
        void ProcessNewBLOB(IBLOB *BLOBPointer);

        /**
         * \brief Process new device message from driver. This routine should be called from within
         * the newDevice handler in the client. This routine is not normally called directly but is
         * called by the ProcessNewDevice function in INDI::Alignment::AlignmentSubsystemForClients which
         * filters out calls from unwanted devices. TODO maybe hide this function.
         * \param[in] DevicePointer A pointer to the INDI::BaseDevice object.
         */
        void ProcessNewDevice(INDI::BaseDevice *DevicePointer);

        /**
         * \brief Process new number message from driver. This routine should be called from within
         * the newNumber handler in the client. This routine is not normally called directly but
         * it is called by the ProcessNewNumber function in INDI::Alignment::AlignmentSubsystemForClients
         * which filters out calls from unwanted devices. TODO maybe hide this function.
         * \param[in] NumberVectorProperty A pointer to the INDI::INumberVectorProperty.
         */
        void ProcessNewNumber(INumberVectorProperty *NumberVectorProperty);

        /**
         * \brief Process new property message from driver. This routine should be called from within
         * the newProperty handler in the client. This routine is not normally called directly but it is
         * called by the ProcessNewProperty function in INDI::Alignment::AlignmentSubsystemForClients
         * which filters out calls from unwanted devices. TODO maybe hide this function.
         * \param[in] PropertyPointer A pointer to the INDI::Property object.
         */
        void ProcessNewProperty(INDI::Property *PropertyPointer);

        /**
         * \brief Process new switch message from driver. This routine should be called from within
         * the newSwitch handler in the client. This routine is not normally called directly but it is
         * called by the ProcessNewSwitch function in INDI::Alignment::AlignmentSubsystemForClients
         * which filters out calls from unwanted devices. TODO maybe hide this function.
         * \param[in] SwitchVectorProperty A pointer to the INDI::ISwitchVectorProperty.
         */
        void ProcessNewSwitch(ISwitchVectorProperty *SwitchVectorProperty);

        /**
         * \brief Increment the current offset then read a sync point from the database.
         * \param[out] CurrentValues The entry read.
         * \return True if successful
         */
        bool ReadIncrementSyncPoint(AlignmentDatabaseEntry &CurrentValues);

        /**
         * \brief Read a sync point from the database.
         * \param[in] Offset Pointer to where to read from.
         * \param[out] CurrentValues The entry read.
         * \return True if successful
         */
        bool ReadSyncPoint(unsigned int Offset, AlignmentDatabaseEntry &CurrentValues);

        /**
         * \brief Save the database to persistent storage
         * \return True if successful
         */
        bool SaveDatabase();

    private:
        // Private methods

        bool SendEntryData(const AlignmentDatabaseEntry &CurrentValues);
        bool SetDriverBusy();
        bool SignalDriverCompletion();
        bool WaitForDriverCompletion();

        // Private properties

        INDI::BaseClient *BaseClient { nullptr };
        pthread_cond_t DriverActionCompleteCondition;
        pthread_mutex_t DriverActionCompleteMutex;
        bool DriverActionComplete { false };
        INDI::BaseDevice *Device { nullptr };
        INDI::Property *MandatoryNumbers { nullptr };
        INDI::Property *OptionalBinaryBlob { nullptr };
        INDI::Property *PointsetSize { nullptr };
        INDI::Property *CurrentEntry { nullptr };
        INDI::Property *Action { nullptr };
        INDI::Property *Commit { nullptr };
};

} // namespace AlignmentSubsystem
} // namespace INDI
