#ifndef FSFW_DATAPOOLLOCAL_LOCALDATAPOOLMANAGER_H_
#define FSFW_DATAPOOLLOCAL_LOCALDATAPOOLMANAGER_H_

#include "HasLocalDataPoolIF.h"

#include "../housekeeping/HousekeepingPacketDownlink.h"
#include "../housekeeping/HousekeepingMessage.h"
#include "../housekeeping/PeriodicHousekeepingHelper.h"
#include "../datapool/DataSetIF.h"
#include "../datapool/PoolEntry.h"
#include "../objectmanager/SystemObjectIF.h"
#include "../ipc/MutexIF.h"
#include "../ipc/CommandMessage.h"
#include "../ipc/MessageQueueIF.h"
#include "../ipc/MutexHelper.h"

#include <map>

namespace Factory {
void setStaticFrameworkObjectIds();
}

class LocalPoolDataSetBase;
class HousekeepingPacketUpdate;

/**
 * @brief 		This class is the managing instance for the local data pool.
 * @details
 * The actual data pool structure is a member of this class. Any class which
 * has a local data pool shall have this manager class as a member and implement
 * the HasLocalDataPoolIF.
 *
 * The manager offers some adaption points and functions which can be used
 * by the owning class to simplify data handling significantly.
 *
 * Please ensure that both initialize and initializeAfterTaskCreation are
 * called at some point by the owning class in the respective functions of the
 * same name!
 *
 * Users of the data pool use the helper classes LocalDataSet,
 * LocalPoolVariable and LocalPoolVector to access pool entries in
 * a thread-safe and efficient way.
 *
 * The local data pools employ a blackboard logic: Only the most recent
 * value is stored. The helper classes offer a read() and commit() interface
 * through the PoolVariableIF which is used to read and update values.
 * Each pool entry has a valid state too.
 * @author 		R. Mueller
 */
class LocalDataPoolManager {
	template<typename T> friend class LocalPoolVariable;
	template<typename T, uint16_t vecSize> friend class LocalPoolVector;
	friend class LocalPoolDataSetBase;
	friend void (Factory::setStaticFrameworkObjectIds)();
public:
	static constexpr uint8_t INTERFACE_ID = CLASS_ID::HOUSEKEEPING_MANAGER;

    static constexpr ReturnValue_t QUEUE_OR_DESTINATION_NOT_SET = MAKE_RETURN_CODE(0x0);

    static constexpr ReturnValue_t WRONG_HK_PACKET_TYPE = MAKE_RETURN_CODE(0x01);
    static constexpr ReturnValue_t REPORTING_STATUS_UNCHANGED = MAKE_RETURN_CODE(0x02);
    static constexpr ReturnValue_t PERIODIC_HELPER_INVALID = MAKE_RETURN_CODE(0x03);

    /**
     * This constructor is used by a class which wants to implement
     * a personal local data pool. The queueToUse can be supplied if it
     * is already known.
     *
     * initialize() has to be called in any case before using the object!
     * @param owner
     * @param queueToUse
     * @param appendValidityBuffer Specify whether a buffer containing the
     * validity state is generated  when serializing or deserializing packets.
     */
	LocalDataPoolManager(HasLocalDataPoolIF* owner, MessageQueueIF* queueToUse,
	        bool appendValidityBuffer = true);
	virtual~ LocalDataPoolManager();

	/**
	 * Assigns the queue to use. Make sure to call this in the #initialize
	 * function of the owner.
	 * @param queueToUse
	 * @param nonDiagInvlFactor See #setNonDiagnosticIntervalFactor doc
	 * @return
	 */
	ReturnValue_t initialize(MessageQueueIF* queueToUse);

	/**
	 * Initializes the map by calling the map initialization function and
	 * setting the periodic factor for non-diagnostic packets.
	 * Don't forget to call this in the #initializeAfterTaskCreation call of
	 * the owner, otherwise the map will be invalid!
	 * @param nonDiagInvlFactor
	 * @return
	 */
	ReturnValue_t initializeAfterTaskCreation(
	        uint8_t nonDiagInvlFactor = 5);

    /**
     * @brief   This should be called in the periodic handler of the owner.
     * @details
     * This in generally called in the #performOperation function of the owner.
     * It performs all the periodic functionalities of the data pool manager,
     * for example generating periodic HK packets.
     * Marked virtual as an adaption point for custom data pool managers.
     * @return
     */
    virtual ReturnValue_t performHkOperation();

	/**
	 * @brief   Subscribe for the generation of periodic packets.
	 * @details
     * This subscription mechanism will generally be used by the data creator
     * to generate housekeeping packets which are downlinked directly.
	 * @return
	 */
	ReturnValue_t subscribeForPeriodicPacket(sid_t sid, bool enableReporting,
			float collectionInterval, bool isDiagnostics,
			object_id_t packetDestination = defaultHkDestination);

	/**
	 * @brief   Subscribe for the  generation of packets if the dataset
	 *          is marked as changed.
	 * @details
	 * This subscription mechanism will generally be used by the data creator.
	 * @param sid
	 * @param isDiagnostics
	 * @param packetDestination
	 * @return
	 */
    ReturnValue_t subscribeForUpdatePackets(sid_t sid, bool reportingEnabled,
            bool isDiagnostics,
            object_id_t packetDestination = defaultHkDestination);

	/**
	 * @brief   Subscribe for a notification message which will be sent
	 *          if a dataset has changed.
	 * @details
	 * This subscription mechanism will generally be used internally by
	 * other software components.
	 * @param setId     Set ID of the set to receive update messages from.
	 * @param destinationObject
	 * @param targetQueueId
	 * @param generateSnapshot If this is set to true, a copy of the current
	 * data with a timestamp will be generated and sent via message.
	 * Otherwise, only an notification message is sent.
	 * @return
	 */
	ReturnValue_t subscribeForSetUpdateMessages(const uint32_t setId,
	        object_id_t destinationObject,
	        MessageQueueId_t targetQueueId,
	        bool generateSnapshot);

    /**
     * @brief   Subscribe for an notification message which will be sent if a
     *          pool variable has changed.
     * @details
     * This subscription mechanism will generally be used internally by
     * other software components.
     * @param localPoolId Pool ID of the pool variable
     * @param destinationObject
     * @param targetQueueId
     * @param generateSnapshot If this is set to true, a copy of the current
     * data with a timestamp will be generated and sent via message.
     * Otherwise, only an notification message is sent.
     * @return
     */
    ReturnValue_t subscribeForVariableUpdateMessages(const lp_id_t localPoolId,
            object_id_t destinationObject,
            MessageQueueId_t targetQueueId,
            bool generateSnapshot);

	/**
	 * Non-Diagnostics packets usually have a lower minimum sampling frequency
	 * than diagnostic packets.
	 * A factor can be specified to determine the minimum sampling frequency
	 * for non-diagnostic packets. The minimum sampling frequency of the
	 * diagnostics packets,which is usually jusst the period of the
	 * performOperation calls, is multiplied with that factor.
	 * @param factor
	 */
	void setNonDiagnosticIntervalFactor(uint8_t nonDiagInvlFactor);

    /**
     * @brief   The manager is also able to handle housekeeping messages.
     * @details
     * This most commonly is used to handle messages for the housekeeping
     * interface, but the manager is also able to handle update notifications
     * and calls a special function which can be overriden by a child class
     * to handle data set or pool variable updates. This is relevant
     * for classes like controllers which have their own local datapool
     * but pull their data from other local datapools.
     * @param message
     * @return
     */
    virtual ReturnValue_t handleHousekeepingMessage(CommandMessage* message);

	/**
	 * Generate a housekeeping packet with a given SID.
	 * @param sid
	 * @return
	 */
	ReturnValue_t generateHousekeepingPacket(sid_t sid,
			LocalPoolDataSetBase* dataSet, bool forDownlink,
			MessageQueueId_t destination = MessageQueueIF::NO_QUEUE);

	HasLocalDataPoolIF* getOwner();

	ReturnValue_t printPoolEntry(lp_id_t localPoolId);

    /**
     * Different types of housekeeping reporting are possible.
     *  1. PERIODIC:
     *     HK packets are generated in fixed intervals and sent to
     *     destination. Fromat will be raw.
     *  2. UPDATE_NOTIFICATION:
     *     Notification will be sent out if HK data has changed.
     *  3. UPDATE_SNAPSHOT:
     *     HK packets are only generated if explicitely requested.
     *     Propably not necessary, just use multiple local data sets or
     *     shared datasets.
     */
    enum class ReportingType: uint8_t {
        //! Periodic generation of HK packets.
        PERIODIC,
        //! Housekeeping packet will be generated if values have changed.
        UPDATE_HK,
		//! Update notification will be sent out as message.
		UPDATE_NOTIFICATION,
        //! Notification will be sent out as message and a snapshot of the
        //! current data will be generated.
        UPDATE_SNAPSHOT,
    };

    /**
     * Different data types are possible in the HK receiver map.
     * For example, updates can be requested for full datasets or
     * for single pool variables. Periodic reporting is only possible for
     * data sets.
     */
    enum class DataType: uint8_t {
    	LOCAL_POOL_VARIABLE,
		DATA_SET
    };

    /* Copying forbidden */
    LocalDataPoolManager(const LocalDataPoolManager &) = delete;
    LocalDataPoolManager operator=(const LocalDataPoolManager&) = delete;

private:
    LocalDataPool localPoolMap;
    //! Every housekeeping data manager has a mutex to protect access
    //! to it's data pool.
    MutexIF* mutex = nullptr;

    /** The class which actually owns the manager (and its datapool). */
    HasLocalDataPoolIF* owner = nullptr;

    uint8_t nonDiagnosticIntervalFactor = 0;

	/** Default receiver for periodic HK packets */
	static object_id_t defaultHkDestination;
	MessageQueueId_t hkDestinationId = MessageQueueIF::NO_QUEUE;

    union DataId {
        DataId(): sid() {};
        sid_t sid;
        lp_id_t localPoolId;
    };

    /** The data pool manager will keep an internal map of HK receivers. */
    struct HkReceiver {
		/** Object ID of receiver */
		object_id_t objectId = objects::NO_OBJECT;

		DataType dataType = DataType::DATA_SET;
        DataId dataId;

        ReportingType reportingType = ReportingType::PERIODIC;
        MessageQueueId_t destinationQueue = MessageQueueIF::NO_QUEUE;
    };

    /** This vector will contain the list of HK receivers. */
    using HkReceivers = std::vector<struct HkReceiver>;

    HkReceivers hkReceiversMap;

    struct HkUpdateResetHelper {
        DataType dataType = DataType::DATA_SET;
        DataId dataId;
        uint8_t updateCounter;
        uint8_t currentUpdateCounter;
    };

    using HkUpdateResetList = std::vector<struct HkUpdateResetHelper>;
    // Will only be created when needed.
    HkUpdateResetList* hkUpdateResetList = nullptr;

    /** This is the map holding the actual data. Should only be initialized
     * once ! */
    bool mapInitialized = false;
    /** This specifies whether a validity buffer is appended at the end
     * of generated housekeeping packets. */
    bool appendValidityBuffer = true;

	/**
	 * @brief Queue used for communication, for example commands.
	 * Is also used to send messages. Can be set either in the constructor
     * or in the initialize() function.
	 */
	MessageQueueIF* hkQueue = nullptr;

	/** Global IPC store is used to store all packets. */
	StorageManagerIF* ipcStore = nullptr;
	/**
	 * Get the pointer to the mutex. Can be used to lock the data pool
	 * externally. Use with care and don't forget to unlock locked mutexes!
	 * For now, only friend classes can accss this function.
	 * @return
	 */
	MutexIF* getMutexHandle();

	/**
	 * Read a variable by supplying its local pool ID and assign the pool
	 * entry to the supplied PoolEntry pointer. The type of the pool entry
	 * is deduced automatically. This call is not thread-safe!
	 * For now, only friend classes like LocalPoolVar may access this
	 * function.
	 * @tparam T Type of the pool entry
	 * @param localPoolId Pool ID of the variable to read
	 * @param poolVar [out] Corresponding pool entry will be assigned to the
	 * 						supplied pointer.
	 * @return
	 */
	template <class T> ReturnValue_t fetchPoolEntry(lp_id_t localPoolId,
			PoolEntry<T> **poolEntry);

    /**
     * This function is used to fill the local data pool map with pool
     * entries. It should only be called once by the pool owner.
     * @param localDataPoolMap
     * @return
     */
    ReturnValue_t initializeHousekeepingPoolEntriesOnce();

	ReturnValue_t serializeHkPacketIntoStore(
	        HousekeepingPacketDownlink& hkPacket,
	        store_address_t& storeId, bool forDownlink, size_t* serializedSize);

	void performPeriodicHkGeneration(HkReceiver& hkReceiver);
	ReturnValue_t togglePeriodicGeneration(sid_t sid, bool enable,
			bool isDiagnostics);
	ReturnValue_t changeCollectionInterval(sid_t sid,
			float newCollectionInterval, bool isDiagnostics);
	ReturnValue_t generateSetStructurePacket(sid_t sid, bool isDiagnostics);

	void handleHkUpdateResetListInsertion(DataType dataType, DataId dataId);
	void handleChangeResetLogic(DataType type,
	        DataId dataId, MarkChangedIF* toReset);
	void resetHkUpdateResetHelper();

	ReturnValue_t handleHkUpdate(HkReceiver& hkReceiver,
            ReturnValue_t& status);
	ReturnValue_t handleNotificationUpdate(HkReceiver& hkReceiver,
	        ReturnValue_t& status);
	ReturnValue_t handleNotificationSnapshot(HkReceiver& hkReceiver,
            ReturnValue_t& status);
	ReturnValue_t addUpdateToStore(HousekeepingPacketUpdate& updatePacket,
	        store_address_t& storeId);
};


template<class T> inline
ReturnValue_t LocalDataPoolManager::fetchPoolEntry(lp_id_t localPoolId,
		PoolEntry<T> **poolEntry) {
	auto poolIter = localPoolMap.find(localPoolId);
	if (poolIter == localPoolMap.end()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
		sif::warning << "HousekeepingManager::fechPoolEntry: Pool entry "
		        "not found." << std::endl;
#endif
		return HasLocalDataPoolIF::POOL_ENTRY_NOT_FOUND;
	}

	*poolEntry = dynamic_cast< PoolEntry<T>* >(poolIter->second);
	if(*poolEntry == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
		sif::debug << "HousekeepingManager::fetchPoolEntry:"
				" Pool entry not found." << std::endl;
#endif
		return HasLocalDataPoolIF::POOL_ENTRY_TYPE_CONFLICT;
	}
	return HasReturnvaluesIF::RETURN_OK;
}


#endif /* FSFW_DATAPOOLLOCAL_LOCALDATAPOOLMANAGER_H_ */