#pragma once

#include "AP_Proximity.h"
#include "AP_Proximity_Backend_Serial.h"

#define PROXIMITY_SF40C_TIMEOUT_MS            200   // requests timeout after 0.2 seconds
#define PROXIMITY_SF40C_PAYLOAD_LEN_MAX       256   // maximum payload size we can accept (in some configurations sensor may send as large as 1023)
#define PROXIMITY_SF40C_COMBINE_READINGS        7   // combine this many readings together to improve efficiency

class AP_Proximity_LightWareSF40C : public AP_Proximity_Backend_Serial
{

public:
    // constructor
    using AP_Proximity_Backend_Serial::AP_Proximity_Backend_Serial;

    uint16_t rxspace() const override {
        return 1280;
    };

    // update state
    void update(void) override;

    // get maximum and minimum distances (in meters) of sensor
    float distance_max() const override { return 100.0f; }
    float distance_min() const override { return 0.20f; }

private:

    // initialise sensor
    void initialise();

    // restart sensor and re-init our state
    void restart_sensor();

    // message ids
    enum class MessageID : uint8_t {
        PRODUCT_NAME = 0,
        HARDWARE_VERSION = 1,
        FIRMWARE_VERSION = 2,
        SERIAL_NUMBER = 3,
        TEXT_MESSAGE = 7,
        USER_DATA = 9,
        TOKEN = 10,
        SAVE_PARAMETERS = 12,
        RESET = 14,
        STAGE_FIRMWARE = 16,
        COMMIT_FIRMWARE = 17,
        INCOMING_VOLTAGE = 20,
        STREAM = 30,
        DISTANCE_OUTPUT = 48,
        LASER_FIRING = 50,
        TEMPERATURE = 55,
        BAUD_RATE = 90,
        DISTANCE = 105,
        MOTOR_STATE = 106,
        MOTOR_VOLTAGE = 107,
        OUTPUT_RATE = 108,
        FORWARD_OFFSET = 109,
        REVOLUTIONS = 110,
        ALARM_STATE = 111,
        ALARM1 = 112,
        ALARM2 = 113,
        ALARM3 = 114,
        ALARM4 = 115,
        ALARM5 = 116,
        ALARM6 = 117,
        ALARM7 = 118
    };

    // motor states
    enum class MotorState : uint8_t {
        UNKNOWN = 0,
        PREPARING_FOR_STARTUP = 1,
        WAITING_FOR_FIVE_REVS = 2,
        RUNNING_NORMALLY = 3,
        FAILED_TO_COMMUNICATE = 4
    };

    // send message to sensor
    void send_message(MessageID msgid, bool write, const uint8_t *payload, uint16_t payload_len);

    // request motor state
    void request_motor_state();

    // request start of streaming of distances
    void request_stream_start();

    // request token of sensor (required for reset)
    void request_token();
    bool got_token() const { return (_sensor_state.token[0] != 0 || _sensor_state.token[1] != 0); }
    void clear_token() { memset(_sensor_state.token, 0, ARRAY_SIZE(_sensor_state.token)); }

    // request reset of sensor
    void request_reset();

    // check and process replies from sensor
    void process_replies();

    // process one byte received on serial port
    // state is stored in msg structure.  when a full package is received process_message is called
    void parse_byte(uint8_t b);

    // process the latest message held in the msg structure
    void process_message();

    // internal variables
    uint32_t _last_request_ms;              // system time of last request
    uint32_t _last_reply_ms;                // system time of last valid reply
    uint32_t _last_restart_ms;              // system time we restarted the sensor
    uint32_t _last_distance_received_ms;    // system time of last distance measurement received from sensor
    uint8_t _last_sector = UINT8_MAX;       // sector of last distance_cm

    // state of sensor
    struct {
        MotorState motor_state; // motor state (1=starting-up,2=waiting for first 5 revs, 3=normal, 4=comm failure)
        uint8_t output_rate;    // output rate number (0 = 20010, 1 = 10005, 2 = 6670, 3 = 2001)
        bool streaming;         // true if distance messages are being streamed
        uint8_t token[2];       // token (supplied by sensor) required for reset
    } _sensor_state;

    enum class ParseState {
        HEADER = 0,
        FLAGS_L,
        FLAGS_H,
        MSG_ID,
        PAYLOAD,
        CRC_L,
        CRC_H
    };

    // structure holding latest message contents
    struct {
        ParseState state;       // state of incoming message processing
        uint8_t flags_low;      // flags low byte
        uint8_t flags_high;     // flags high byte
        uint16_t payload_len;   // latest message payload length (1+ bytes in payload)
        uint8_t payload[PROXIMITY_SF40C_PAYLOAD_LEN_MAX];   // payload
        MessageID msgid;        // latest message's message id
        uint16_t payload_recv;  // number of message's payload bytes received so far
        uint8_t crc_low;        // crc low byte
        uint8_t crc_high;       // crc high byte
        uint16_t crc_expected;  // latest message's expected crc
    } _msg;

    // convert buffer to uint32, uint16
    uint32_t buff_to_uint32(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) const;
    uint16_t buff_to_uint16(uint8_t b0, uint8_t b1) const;
};
