#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 <spinscale/callback.h>
#include <spinscale/componentThread.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>

class OptionParser;

namespace smo {

namespace compute {
class ClBuffer;
class ComputeDevice;
} // namespace compute

namespace stim_buff {

/**
 * @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<sscl::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,
    const std::shared_ptr<sscl::ComponentThread>& componentThread,
    sscl::Callback<sal_mlo_attachDeviceReqCbFn> cb);
typedef void (sal_mlo_detachDeviceReqFn)(
    const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
    sscl::Callback<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 sscl::ComponentThread::getSelf().
     */
    std::shared_ptr<sscl::ComponentThread> (*ComponentThread_getSelf)(void);

    /**
     * @brief Get the OptionParser singleton instance
     * @return Reference to the OptionParser singleton
     *
     * This function provides access to the OptionParser singleton instance,
     * equivalent to calling OptionParser::getOptions().
     */
    OptionParser& (*OptionParser_getOptions)(void);

    /**
     * @brief Create a USE_HOST_PTR buffer on all OpenCL contexts
     * @param hostPtr Host pointer to the memory
     * @param size Size of the buffer in bytes
     * @param flags Additional OpenCL memory flags
     * @return Shared pointer to ClBuffer managing buffers on all devices
     */
    std::shared_ptr<smo::compute::ClBuffer>
	(*ComputeManager_createUseHostPtrBuffer)(
        void* hostPtr, size_t size, cl_mem_flags flags);

    /**
     * @brief Release USE_HOST_PTR buffers from all contexts
     * @param buffer Shared pointer to ClBuffer to release
     */
    void (*ComputeManager_releaseUseHostPtrBuffer)(
        std::shared_ptr<smo::compute::ClBuffer> buffer);

    /**
     * @brief Get a compute device
     * @return Shared pointer to ComputeDevice, or nullptr if no devices available
     */
    std::shared_ptr<smo::compute::ComputeDevice>
	(*ComputeManager_getDevice)(void);

    /**
     * @brief Release a compute device
     * @param device Shared pointer to ComputeDevice to release
     */
    void (*ComputeManager_releaseDevice)(
		std::shared_ptr<smo::compute::ComputeDevice> device);
};

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_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 present its stimbuff.
     */
    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 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 (const auto& api : exportedQualeIfaceApis) {
            result += "  - " + api.name + "\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_service 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__
