#pragma once

#include "xmsdk_export.h"
#include "pixelFormat.h"

#include <functional>
#include <memory>
#include <optional>
#include <string_view>


namespace xavia::sdk
{
  class ISensor;
  class IPointCloud;
  class IImage;
  class SerialNumber;
  class Exception;
  enum class Alert;

  inline constexpr std::string_view DEFAULT_XMANAGER_IP_ADDRESS{"10.10.100.11"};
  inline constexpr int DEFAULT_XMANAGER_PORT{4000};

  /**
   * \brief Factory object, to configure and construct sensor objects
   * 
   * Use the sensor factory to create objects of type ISensor.
   * Use the available methods of the factory to configure the sensor object before
   * creating it.
   * 
   * The factory can be reused to create multiple sensors with the same (or similar)
   * configuration, or it can be used to recreate the same sensor object after
   * the previous one was destroyed.
   * 
   * See 'iSensor.h' for more information on the sensor object itself.
   */
  class XMSDK_EXPORT SensorFactory
  {
  public:
    SensorFactory();
    virtual ~SensorFactory();

    using pcCallback = std::function<void(std::unique_ptr<IPointCloud>)>;
    using imgCallback = std::function<void(std::unique_ptr<IImage>)>;
    using errCallback = std::function<void(const Exception&)>;
    using alertCallback = std::function<void(Alert, std::optional<bool>)>;

    /**
     * \brief Register a function where the SDK will report incoming point cloud data.
     * 
     * \param[in] callback function or lambda of the signature void(std::unique_ptr<IPointCloud>)
     * 
     * This function will be called by the sdk for every line. Due to network variations
     * this will not be a steady rhythm, but groups of 2 or 3 lines at once.
     * The sdk has an internal buffer and will provide the next line either when the
     * previous callback returns or when new data arrives from the sensor.
     * 
     * In order to keep up with the sensor, it is advised that callbacks return
     * within 850 microseconds or less.
     * 
     * The data is provided as a unique_ptr, which means that ownership of the point cloud object
     * is transferred from the SDK to the callback function. It will not be used again
     * in the sdk, do with it as you please.
     * 
     * See the `iPointCloud.h` header for the definition of IPointCloud
     */
    void SetPointCloudCallback(pcCallback callback);

    /**
     * \brief Register a function where the SDK will report errors.
     * 
     * \param[in] callback function or lambda of the signature void(const Exception&)
     * 
     * Errors in this context are exceptions streamed by the sensor itself.
     * Usually they are not the result of an action in the sdk.
     * 
     * See `error.h` header for the definition of Exception
     */
    void SetErrorCallback(errCallback callback);

    /**
     * \brief Register a function where the SDK will report alerts
     * 
     * \param[in] callback function or lambda of the signature void(const Alert, const std::optional<bool>)
     * 
     * Alerts are events from the sensor indicating that something occurred to which
     * the sensor is responding. Examples are overheating, opening of the sensor, ...
     * Most Alerts have a boolean flag to indicate if the alert is raised or released.
     * For example: when the sensor overheats an alert of type temperature_critical
     * will be issued with the flag true. When the sensor cools again, a second alert
     * of type temperature_critical will be issued with flag false.
     * 
     * Not all alerts have flags. E.g. the shutdown alert (indicating the sensor is powering
     * off) does not have a boolean flag.
     * 
     * For most alerts, the sensor will stop functioning normally when the alert is raised.
     */
    void SetAlertCallback(alertCallback callback);

    /**
     * \brief Allows control over the amount of incoming point cloud messages that are buffered by the SDK
     * 
     * \param[in] bufferSize  The preallocated size of the buffer.
     * 
     * The default preallocated buffer is 2 full frames. Lower the buffer size
     * if ram memory is limited. Less than 20 lines will likely result in occasional
     * misses, depending on processor speed, processor load and network occupancy.
     * Lower than 3 lines will give continuous misses due to bunching of data on the 
     * ethernet line.
     * Do not use this to compensate for processing not keeping up to recording.
     * If processing cannot keep up, the buffer will fill up anyway and the size
     * will never be enough. A large buffer will result in stale data.
     */
    void SetPointCloudBufferSize(std::size_t bufferSize);

    /**
     * \brief Register a function where the SDK will report incoming camera image data.
     * 
     * \param[in] callback function or lambda of the signature void(std::unique_ptr<IImage>)
     * 
     * This function will be called by the SDK for every camera frame received.
     * The callback execution should be fast to avoid blocking image acquisition.
     * 
     * The data is provided as a unique_ptr, which means that ownership of the image object
     * is transferred from the SDK to the callback function. It will not be used again
     * in the SDK, do with it as you please.
     * 
     * The SDK will only attempt to initialize the camera if an image callback is registered.
     * 
     * See the `iImage.h` header for the definition of IImage
     */
    void SetImageCallback(imgCallback callback);

    /**
     * \brief Configure the camera IP address for this sensor
     * 
     * \param[in] ip4String IPv4 address of the camera (e.g., "10.10.100.12")
     * 
     * Use this to provide a non default IP address for the camera. If you don't use this function,
     * the default IP address 10.10.100.12 will be used.
     * 
     * The camera will be accessible via ISensor::GetCamera() after Build() completes.
     * 
     * Note: the SDK will only attempt to initialize the camera if an image callback is registered via SetImageCallback().
     * If no image callback is registered, the camera IP address will be ignored.
     */
    void SetCameraIPAddress(const std::string_view& ip4String);

    /**
     * \brief Allows control over the amount of incoming image messages that are buffered by the SDK
     * 
     * \param[in] bufferSize  The preallocated size of the buffer.
     * 
     * The default preallocated buffer is 5 frames. Adjust based on available memory
     * and processing capabilities.
     * 
     * A large buffer will result in stale data if processing cannot keep up.
     */
    void SetImageBufferSize(std::size_t bufferSize);

    /**
     * \brief Configure the pixel format of output images
     * 
     * \param[in] format The desired pixel format for the output images.
     *
     * The default format is RGB12.
     * 
     * There are three options:
     *  - BayerRG12: raw Bayer data from the camera, 12 bits per pixel (packed).
     *  - RGB8: 3 bytes per pixel (RGBRGBRGB).
     *  - RGB12: 12-bit RGB data, unpacked, 2 bytes per color channel (RGBRGBRGB).
     * 
     * Consequences:
     *  - BayerRG12: most compact format and no compression calculations required,
     *    but requires demosaicing to get RGB values. Does not support to save as jpg.
     *  - RGB8: straightforward to use, but larger in size compared to BayerRG12.
     *    Good for visualization.
     *  - RGB12: highest quality, but requires more memory and processing power.
     *    Keeps the full 12-bit color information from the camera, which can be 
     *    beneficial for post-processing and analysis.
     * 
     * This choice is important. The SDK will not be able to change the format 
     * after the image has been processed initially. Once it is BayerRG12, this
     * SDK cannot convert it to RGB. Similarly, once it is RGB8, the image cannot
     * be converted to RGB12.
     */
    void SetImageOutputFormat(const PixelFormat format);

    /** 
     * \brief Build the sensor object
     * 
     * \param[in] ip4String address of the sensor (optional, defaults to 10.10.100.11)
     * \param[in] port of the sensor (optional, defaults to 4000)
     * 
     * Use this function to connect to the sensor and build the sensor object.
     * This function will either return a valid sensor object to continue operation
     * or will throw an exception.
     * 
     * If a camera IP address was configured via SetCameraIPAddress(), the camera
     * will also be initialized and accessible via ISensor::GetCamera().
     * 
     * In case of errors use the logging to determine the underlying cause.
     */
    std::shared_ptr<ISensor> Build(const std::string_view& ip4String = DEFAULT_XMANAGER_IP_ADDRESS,
                                   const int port = DEFAULT_XMANAGER_PORT);

  private:
    class Impl;
    std::unique_ptr<Impl> pImpl;
  };
}