#ifndef __USER_SENSE_API_LIB_H__
#define __USER_SENSE_API_LIB_H__

#include <stdbool.h>
#include <optional>
#include <string>
#include <functional>
#include <memory>
#include <vector>
#include <preprocessor.h>
#include <user/deviceAttachmentSpec.h>
#include <user/smoHooks.h>
#include <spinscale/co/dynamicPostingInvoker.h>
#include <spinscale/co/invokers.h>
#include <spinscale/co/postTarget.h>
#include <spinscale/componentThread.h>

namespace smo {

namespace stim_buff {

struct StimBuffDeviceOpResult
{
	bool success = false;
	std::shared_ptr<device::DeviceAttachmentSpec> deviceSpec;
};

using sal_mlo_initializeCIndFn = sscl::co::ViralNonPostingInvoker<int>(void);
using sal_mlo_finalizeCIndFn = sscl::co::ViralNonPostingInvoker<int>(void);
using sal_mlo_attachDeviceCReqFn =
	sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>(
	sscl::co::ExplicitPostTarget postTarget,
	const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
	const std::shared_ptr<sscl::ComponentThread>& componentThread);
using sal_mlo_detachDeviceCReqFn =
	sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>(
	sscl::co::ExplicitPostTarget postTarget,
	const std::shared_ptr<device::DeviceAttachmentSpec>& desc);

struct Sal_Mgmt_LibOps
{
    /* When Salmanoff loads a stim buff API lib, it calls this function to initialize
     * the lib. When this returns, the lib should be ready to attach devices.
     */
    sal_mlo_initializeCIndFn *initializeCInd;
    /* 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_finalizeCIndFn *finalizeCInd;
    /* Salmanoff calls this to attach a device to the lib. When it returns, the
     * device should be attached and ready to present its stimbuff.
     */
    sal_mlo_attachDeviceCReqFn *attachDeviceCReq;
    // When this returns, the device should be detached.
    sal_mlo_detachDeviceCReqFn *detachDeviceCReq;

    static bool sanityCheck(const Sal_Mgmt_LibOps &ops)
    {
        if (!ops.initializeCInd || !ops.finalizeCInd
            || !ops.attachDeviceCReq || !ops.detachDeviceCReq)
        {
            return false;
        }

        return true;
    }
};

/* Exported by all stim buff API Libraries to tell Salmanoff what API the lib uses
 * to connect to providers; and also to state which quale-iface APIs it exports.
 */
class StimBuffApiDesc
{
public:
    class ExportedQualeIfaceApiDesc
    {
    public:
        static bool sanityCheck(const ExportedQualeIfaceApiDesc &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 QualeIface APIs:\n";
        for (size_t i = 0; i < exportedQualeIfaceApis.size(); ++i)
        {
            result += "  - " + exportedQualeIfaceApis[i].name;
            if (i + 1 < exportedQualeIfaceApis.size()) {
                result += "\n";
            }
        }
        return result;
    }

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

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

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

    std::string name;
    // These are the quale-iface APIs this lib exports.
    std::vector<ExportedQualeIfaceApiDesc> exportedQualeIfaceApis;
    Sal_Mgmt_LibOps sal_mgmt_libOps;
};


#define SMO_GET_STIM_BUFF_API_DESC_FN_NAME getStimBuffApiDesc
#define SMO_GET_STIM_BUFF_API_DESC_FN_NAME_STR \
    SMO_QUOTE(SMO_GET_STIM_BUFF_API_DESC_FN_NAME)
#define SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF \
    SMO_CONCAT(SMO_GET_STIM_BUFF_API_DESC_FN_NAME, Fn)

/* Every Stim Buff 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 StimBuffApiDesc struct that Smo will tell
 * Smo what quale-iface APIs can be used with it & what APIs it exports.
 * The StimBuffApiDesc 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_context for network operations and event handling.
 */
typedef const StimBuffApiDesc &(SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF)(
    const SmoCallbacks& callbacks,
    const SmoThreadingModelDesc& threadingModel);

} // namespace stim_buff
} // namespace smo

#endif // __USER_SENSE_API_LIB_H__
