/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (C) 2025 Raspberry Pi Ltd
 *
 * Native implementation of the Android Fastboot USB protocol.
 *
 * Operates directly over IUsbTransport, sending ASCII commands via
 * bulk OUT and reading responses via bulk IN.
 */

#ifndef FASTBOOT_PROTOCOL_H
#define FASTBOOT_PROTOCOL_H

#include "../rpiboot/usb_transport.h"
#include "../rpiboot/rpiboot_types.h"

#include <atomic>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>

namespace fastboot {

struct Response {
    enum Type { Okay, Fail, Data, Info, Text };
    Type type = Fail;
    std::string message;
    uint32_t dataSize = 0;  // Only valid for Data type
};

class FastbootProtocol {
public:
    // Send an ASCII command and read the response.
    // For multi-response commands (INFO prefix), this reads until a
    // terminal response (OKAY or FAIL) is received.
    Response sendCommand(rpiboot::IUsbTransport& transport,
                         std::string_view command,
                         int timeoutMs);

    // Send an ASCII command and capture every INFO/TEXT line emitted
    // by the device before the terminal OKAY/FAIL response.  Useful
    // for OEM commands (e.g. "getvar public-key", "oem fwcrypto
    // sign-hash") whose payload is delivered across multiple INFO
    // packets rather than in the terminal response message.
    struct CaptureResult {
        Response terminal;
        std::vector<std::string> infoLines;
    };
    CaptureResult sendCommandCapture(rpiboot::IUsbTransport& transport,
                                      std::string_view command,
                                      int timeoutMs);

    // Download data to the device (for use with flash).
    // Sends "download:<hex-size>", waits for DATA response, then
    // streams the data via bulk OUT.
    bool download(rpiboot::IUsbTransport& transport,
                  std::span<const uint8_t> data,
                  rpiboot::ProgressCallback progress,
                  std::atomic<bool>& cancelled);

    // Stage data on the device for a subsequent oem command to consume.
    // Wire-identical to download() — it deliberately uses the "download:"
    // verb, NOT "stage:".  On rpi-fastbootd both dispatch to the same
    // DownloadHandler (the device keeps a "stage" alias only for backward
    // compat with older imagers), and "download" is the only staging verb
    // accepted on a restricted TCP data-plane connection.  Kept as a distinct
    // name from download() to document staging intent at call sites.
    bool stage(rpiboot::IUsbTransport& transport,
               std::span<const uint8_t> data,
               rpiboot::ProgressCallback progress,
               std::atomic<bool>& cancelled);

    // Upload data from the device to the host.
    // Sends "upload", reads DATA<size>, then reads payload from bulk IN.
    // Returns the data, or an empty vector on failure (lastError() set).
    std::vector<uint8_t> upload(rpiboot::IUsbTransport& transport,
                                 rpiboot::ProgressCallback progress,
                                 std::atomic<bool>& cancelled);

    // Flash the previously downloaded data to a partition.
    // Sends "flash:<partition>" and waits for OKAY/FAIL.
    bool flash(rpiboot::IUsbTransport& transport,
               std::string_view partition,
               int timeoutMs);

    // Query a device variable.
    // Sends "getvar:<name>" and returns the value, or nullopt on failure.
    std::optional<std::string> getVar(rpiboot::IUsbTransport& transport,
                                       std::string_view name);

    // Positively identify a genuine rpi-fastbootd gadget.
    //
    // The RPi fastboot gadget borrows Google's USB VID/PID (18d1:4e40), so
    // matching that VID/PID does NOT prove the device is a Raspberry Pi — a
    // non-Pi device (e.g. an Android phone in a colliding fastboot mode)
    // could enumerate identically.  Identifies via, in order:
    //   1. the transport's USB interface descriptor — the RPi gadget
    //      advertises "fastbootd-provisioner" (authoritative, no I/O beyond
    //      the descriptor already read at open time); then
    //   2. a fallback probe of the RPi-specific "block-devices" getvar, which
    //      stock Android fastboot/fastbootd does not implement (FAILs), for
    //      transports that cannot read the descriptor or customised gadgets.
    // Returns true only when one of these confirms a Pi; false otherwise.
    bool isRpiFastboot(rpiboot::IUsbTransport& transport);

    // Combined download + flash in one call.
    bool flashImage(rpiboot::IUsbTransport& transport,
                    std::string_view partition,
                    std::span<const uint8_t> data,
                    rpiboot::ProgressCallback progress,
                    std::atomic<bool>& cancelled);

    // Mount a block device partition on the device.
    // Sends "oem mount <device> <mountpoint> [fstype]".
    bool mountDevice(rpiboot::IUsbTransport& transport,
                     std::string_view device,
                     std::string_view mountpoint,
                     std::string_view fstype = {});

    // Unmount a mountpoint on the device.
    // Sends "oem umount <mountpoint>".
    bool umountDevice(rpiboot::IUsbTransport& transport,
                      std::string_view mountpoint);

    // Write a file to the device filesystem.
    // Stages the data, then sends "oem download-file <devicePath>".
    bool writeDeviceFile(rpiboot::IUsbTransport& transport,
                         std::string_view devicePath,
                         std::span<const uint8_t> data,
                         std::atomic<bool>& cancelled);

    // Read a file from the device filesystem.
    // Sends "oem upload-file <devicePath>", then uploads the data.
    // Returns file contents, or empty vector on failure (lastError() set).
    std::vector<uint8_t> readDeviceFile(rpiboot::IUsbTransport& transport,
                                         std::string_view devicePath,
                                         std::atomic<bool>& cancelled);

    // ── EEPROM (rpi-fastbootd be8a8ce) ────────────────────────────────
    //
    // The device-side handlers read whatever was just downloaded via
    // `download:` as the EEPROM image. `spidev` is optional and may be
    // empty to let the device auto-detect the SPI bus.

    // Download `image`, then issue "oem eeprom-update [<spidev>]".
    // Uses a long timeout to cover the SPI flash erase + write cycle.
    bool updateEeprom(rpiboot::IUsbTransport& transport,
                      std::span<const uint8_t> image,
                      std::string_view spidev,
                      rpiboot::ProgressCallback progress,
                      std::atomic<bool>& cancelled);

    // Download `image`, then issue "oem eeprom-verify [<spidev>]".
    bool verifyEeprom(rpiboot::IUsbTransport& transport,
                      std::span<const uint8_t> image,
                      std::string_view spidev,
                      rpiboot::ProgressCallback progress,
                      std::atomic<bool>& cancelled);

    // Issue "oem eeprom-read [<spidev>]" then upload the staged image.
    // Returns the EEPROM bytes, or empty on failure (lastError() set).
    std::vector<uint8_t> readEeprom(rpiboot::IUsbTransport& transport,
                                     std::string_view spidev,
                                     rpiboot::ProgressCallback progress,
                                     std::atomic<bool>& cancelled);

    const std::string& lastError() const { return _lastError; }

private:
    // Read a single response packet from the device
    Response readResponse(rpiboot::IUsbTransport& transport, int timeoutMs);

    // Send data to the device with a given command prefix ("download" or "stage").
    // Shared implementation for download() and stage().
    bool sendData(rpiboot::IUsbTransport& transport,
                  std::string_view commandPrefix,
                  std::span<const uint8_t> data,
                  rpiboot::ProgressCallback progress,
                  std::atomic<bool>& cancelled);

    std::string _lastError;

    // Fastboot uses these endpoint addresses
    static constexpr uint8_t EP_OUT = 0x01;
    static constexpr uint8_t EP_IN  = 0x81;
};

} // namespace fastboot

#endif // FASTBOOT_PROTOCOL_H
