#ifndef __USER_SENSE_API_LIB_H__
#define __USER_SENSE_API_LIB_H__

#include <stdbool.h>
#include <optional>
#include <string>
#include <memory>
#include <preprocessor.h>
#include <componentThread.h>
#include <user/deviceAttachmentSpec.h>

namespace smo {
namespace sense_api {

/**
 * @brief Threading model descriptor for senseApi libraries.
 *
 * This structure provides senseApi libraries with access to the information and
 * resources they need to operate with SMO's threading model.
 */
struct SmoThreadingModelDesc
{
	/**
	 * @brief sh_ptr to ComponentThread for device-independent state mgt.
	 *
	 * This ComponentThread should be used by senseApis for state management
	 * that's independent of any particular device or attachment spec.
	 * SMO will usually pass in the Marionette thread here.
	 *
	 * State management that's tied to a particular attachment spec should be
	 * done on the ComponentThread for the thread that SMO provided in the
	 * attachDeviceReq call.
	 */
	std::shared_ptr<ComponentThread> componentThread;
};

typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
	sal_mlo_attachDeviceReqCbFn;
typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
	sal_mlo_detachDeviceReqCbFn;

typedef int (sal_mlo_initializeIndFn)(void);
typedef int (sal_mlo_finalizeIndFn)(void);
typedef void (sal_mlo_attachDeviceReqFn)(
    const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
    sal_mlo_attachDeviceReqCbFn cb);
typedef void (sal_mlo_detachDeviceReqFn)(
    const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
    sal_mlo_detachDeviceReqCbFn cb);

/**
 * @brief Hooks provided by Salmanoff to senseApi libraries.

 * This structure contains function pointers that senseApi libraries can use
 * to interact with Salmanoff's functionality, such as searching for commonLibs.
 */
struct SmoCallbacks
{
    /**
     * @brief Search for a library in Salmanoff's search paths
     * @param libraryPath The relative filename of the library to search for
     * @return Optional containing the full path if found, nullopt if not found
     *
     * This function searches for the given library in the same search paths
     * that Salmanoff uses when loading senseApi libraries (user-specified
     * paths via -p option, current directory, and executable directory).
     */
    std::optional<std::string> (*searchForLibInSmoSearchPaths)(
        const std::string& libraryPath);

    /**
     * @brief Get the current ComponentThread instance
     * @return Shared pointer to the current ComponentThread
     *
     * This function provides access to the current ComponentThread instance,
     * equivalent to calling ComponentThread::getSelf().
     */
    std::shared_ptr<ComponentThread> (*ComponentThread_getSelf)(void);
};

struct Sal_Mgmt_LibOps
{
    /* When Salmanoff loads a sense API lib, it calls this function to initialize
     * the lib. When this returns, the lib should be ready to attach devices.
     */
    sal_mlo_initializeIndFn *initializeInd;
    /* Salmanoff calls this to finalize the lib and free its internal
     * resources. When this returns, the lib should be ready to be unloaded.
     */
    sal_mlo_finalizeIndFn *finalizeInd;
    /* Salmanoff calls this to attach a device to the lib. When it returns, the
     * device should be attached and ready to be implexed.
     */
    sal_mlo_attachDeviceReqFn *attachDeviceReq;
    // When this returns, the device should be detached.
    sal_mlo_detachDeviceReqFn *detachDeviceReq;

    static bool sanityCheck(const Sal_Mgmt_LibOps &ops)
    {
        if (!ops.initializeInd || !ops.finalizeInd
            || !ops.attachDeviceReq || !ops.detachDeviceReq)
        {
            return false;
        }

        return true;
    }
};

/* Exported by all sense API Libraries to tell Salmanoff what API the lib uses
 * to connect to providers; and also to state which implexor APIs it exports.
 */
class SenseApiDesc
{
public:
    class ExportedImplexorApiDesc
    {
    public:
        static bool sanityCheck(const ExportedImplexorApiDesc &desc)
        {
            if (desc.name.empty()) { return false; }
            return true;
        }

    public:
        std::string name;
    };

public:
    std::string stringify() const
    {
        std::string result = "Name: " + name + "\n";
        result += "Exported Implexor APIs:\n";
        for (const auto& api : exportedImplexorApis) {
            result += "  - " + api.name + "\n";
        }
        return result;
    }

    static bool sanityCheck(const SenseApiDesc &desc)
    {
        if (desc.name.empty() || desc.exportedImplexorApis.empty()) {
            return false;
        }

        for (const auto& api : desc.exportedImplexorApis) {
            if (!ExportedImplexorApiDesc::sanityCheck(api)) { return false; }
        }

        return Sal_Mgmt_LibOps::sanityCheck(desc.sal_mgmt_libOps);
    }

    std::string name;
    // These are the implexors whose APIs this lib exports.
    std::vector<ExportedImplexorApiDesc> exportedImplexorApis;
    Sal_Mgmt_LibOps sal_mgmt_libOps;
};


#define SMO_GET_SENSE_API_DESC_FN_NAME getSenseApiDesc
#define SMO_GET_SENSE_API_DESC_FN_NAME_STR \
    SMO_QUOTE(SMO_GET_SENSE_API_DESC_FN_NAME)
#define SMO_GET_SENSE_API_DESC_FN_TYPEDEF \
    SMO_CONCAT(SMO_GET_SENSE_API_DESC_FN_NAME, Fn)

/* Every Sense API library must define a global instance of this
 * function. Salmanoff will search for it and invoke it via dlsym().
 *
 * The function must return a SenseApiDesc struct that Smo will tell
 * Smo what implexors can be used with it & what APIs it exports.
 * The SenseApiDesc struct also gives Smo pointers to API functions
 * to invoke for communication between Smo and the library.
 *
 * The SmoCallbacks parameter provides the library with access to
 * Salmanoff's hooks.
 * The SmoThreadingModelDesc parameter provides the library with access to
 * the io_service for network operations and event handling.
 */
typedef const SenseApiDesc &(SMO_GET_SENSE_API_DESC_FN_TYPEDEF)(
    const SmoCallbacks& callbacks,
    const SmoThreadingModelDesc& threadingModel);

} // namespace sense_api
} // namespace smo

#endif // __USER_SENSE_API_LIB_H__
