#pragma once

#include "usb_device.hpp"

namespace dv::io::camera {

class USBDeviceNextGen : public USBDevice {
protected:
	USBDeviceNextGen(const DeviceDescriptor &deviceToOpen, loggerCallbackType deviceLogger,
		const LogLevel deviceLogLevel, const std::string_view shortName, const std::string_view longName,
		const compatibleCameraCallbackType &expectedDevice, const int32_t requiredFirmwareVersion = -1) :
		USBDevice(deviceToOpen, std::move(deviceLogger), deviceLogLevel, shortName, longName, expectedDevice,
			requiredFirmwareVersion) {
	}

	void spiConfigSendMultiple(std::span<SPIConfigurationParameters>) const                              = delete;
	void spiConfigSendMultipleAsync(std::span<SPIConfigurationParameters>, controlOutCallbackType) const = delete;

	void spiConfigSend(const uint16_t moduleAddr, const uint16_t paramAddr, const uint64_t param) const {
		std::array<uint8_t, sizeof(uint64_t)> spiConfig{};
		boost::endian::store_big_u64(spiConfig.data(), param);

		log(LogLevel::LVL_DEBUG,
			fmt::format("Sending SPI config to module {} with param {}: {}", moduleAddr, paramAddr, param));

		usbControlTransferOut(VENDOR_REQUEST_SPI_CONFIG, moduleAddr, paramAddr, spiConfig);
	}

	void spiConfigSendAsync(const uint16_t moduleAddr, const uint16_t paramAddr, const uint64_t param,
		controlOutCallbackType callback) const {
		std::array<uint8_t, sizeof(uint64_t)> spiConfig{};
		boost::endian::store_big_u64(spiConfig.data(), param);

		log(LogLevel::LVL_DEBUG,
			fmt::format("Sending Async SPI config to module {} with param {}: {}", moduleAddr, paramAddr, param));

		usbControlTransferOutAsync(VENDOR_REQUEST_SPI_CONFIG, moduleAddr, paramAddr, spiConfig, std::move(callback));
	}

	[[nodiscard]] uint64_t spiConfigReceive(const uint16_t moduleAddr, const uint16_t paramAddr) const {
		std::array<uint8_t, sizeof(uint64_t)> param{};

		usbControlTransferIn(VENDOR_REQUEST_SPI_CONFIG, moduleAddr, paramAddr, param);

		const auto paramInt = boost::endian::load_big_u64(param.data());

		log(LogLevel::LVL_DEBUG,
			fmt::format("Received SPI config from module {} with param {}: {}", moduleAddr, paramAddr, paramInt));

		return paramInt;
	}

	void spiConfigReceiveAsync(const uint16_t moduleAddr, const uint16_t paramAddr,
		std::function<void(libusb_transfer_status status, uint64_t param)> callback) const {
		usbControlTransferInAsync(VENDOR_REQUEST_SPI_CONFIG, moduleAddr, paramAddr, sizeof(uint64_t),
			[this, moduleAddr, paramAddr, callback = std::move(callback)](
				const enum libusb_transfer_status status, const std::span<const uint8_t> buffer) {
				if (callback) {
					uint64_t param{0};

					if ((status == LIBUSB_TRANSFER_COMPLETED) && (buffer.size() == sizeof(uint64_t))) {
						param = boost::endian::load_big_u64(buffer.data());
					}

					log(LogLevel::LVL_DEBUG,
						fmt::format("Received Async SPI config from module {} with param {}: {} (status {})",
							moduleAddr, paramAddr, param, dv::EnumAsInteger(status)));

					callback(status, param);
				}
			});
	}
};

} // namespace dv::io::camera
