#pragma once
#include <Arduino.h>

/// Качество фикса из поля 6 сообщения $..GGA (NEO-6/7/8 обычно шлют только 0/1/2)
enum class FixQuality : uint8_t {
    NoFix = 0,   ///< координаты не определены
    GpsFix = 1,  ///< обычный GPS fix
    DgpsFix = 2, ///< DGPS fix (точнее)
};

/// Координаты и кинематика, собранные из полей $..RMC и $..GGA.
/// Все поля валидны только при valid == true (поле 2 из RMC равно 'A').
struct Position {
    double latitude = 0.0;   ///< десятичные градусы, N положительно, S отрицательно
    double longitude = 0.0;  ///< десятичные градусы, E положительно, W отрицательно
    float speedKmh = 0.0f;   ///< скорость над землёй, переведена из узлов (RMC поле 7)
    float courseDeg = 0.0f;  ///< курс над землёй, 0-360° (RMC поле 8)
    float altitudeM = 0.0f;  ///< высота над уровнем моря, метры (GGA поле 9)
    float hdop = 0.0f;       ///< горизонтальный geometric dilution of precision (GGA поле 8)
    bool valid = false;      ///< статус поля 2 из RMC: 'A' = valid, 'V' = void
};

/**
 * @brief Класс для работы с GPS модулями серии NEO (6M/7M/8M).
 * Инициализация UART, побайтовый разбор NMEA, проверка контрольной суммы,
 * мониторинг связи (watchdog) и извлечение статуса спутников ($GGA, $GSV).
 */
class GpsNeo {
public:
    GpsNeo(HardwareSerial &serial = Serial2);

    void begin(unsigned long baud = 9600, int8_t rxPin = 16, int8_t txPin = 17);

    /**
     * @brief Чтение байта из GPS и пересылка в целевой поток.
     * @return true, если только что принята полная строка ('\n')
     */
    bool process(Stream &target = Serial);

    uint32_t getLineCount() const { return _lineCount; }
    uint32_t getValidLineCount() const { return _validLineCount; }
    bool isLastLineValid() const { return _lastLineValid; }

    /// Получал ли модуль хотя бы один байт за последние timeoutMs (физически на линии)
    bool isAlive(unsigned long timeoutMs = 2000) const {
        return (millis() - _lastByteTime) < timeoutMs;
    }

    /// Сколько спутников ВИДНО модулю суммарно по ВСЕМ созвездиям
    /// (GPS+GLONASS+BeiDou+Galileo), даже без фикса координат
    uint8_t getSatellitesInView() const;

    /// Сколько спутников РЕАЛЬНО используется в расчёте координат (из $..GGA)
    uint8_t getSatellitesUsed() const { return _satellitesUsed; }

    /// Качество фикса из поля 6 сообщения $..GGA
    FixQuality getFixQuality() const { return _fixQuality; }
    bool hasFix() const { return _fixQuality != FixQuality::NoFix; }

    /// Координаты/скорость/курс из последнего успешно разобранного $..RMC,
    /// плюс высота/HDOP из последнего $..GGA. Смотри Position::valid перед использованием.
    const Position& getPosition() const { return _position; }

private:
    static bool validateChecksum(const char* line, uint8_t len);
    static bool getField(const char* line, uint8_t len, uint8_t fieldIndex, char* out, uint8_t outSize);
    static bool parseNmeaCoordinate(const char* field, char hemisphere, double& outDegrees);
    void parseSentence(const char* line, uint8_t len);
    void parseRmc(const char* line, uint8_t len);
    void parseGgaPosition(const char* line, uint8_t len);
    void updateSatellitesInView(const char* talker, uint8_t count);

    /// Максимальная длина одной строки NMEA (с запасом; спецификация — до 82 символов)
    static constexpr uint8_t kBufferSize = 96;
    static constexpr uint8_t kMaxTalkers = 4; // GPS, GLONASS, Galileo, BeiDou

    HardwareSerial* _serial;
    uint32_t _lineCount = 0;
    uint32_t _validLineCount = 0;
    bool _lastLineValid = false;
    unsigned long _lastByteTime = 0;

    // Каждое созвездие (GP/GL/GA/BD) шлёт свой $..GSV с собственным
    // "сколько видно" — храним по каждому отдельно, чтобы не перезатирать.
    struct TalkerSatCount {
        char talker[3] = {0, 0, 0};
        uint8_t count = 0;
        bool active = false;
    };
    TalkerSatCount _talkerSats[kMaxTalkers];

    uint8_t _satellitesUsed = 0;
    FixQuality _fixQuality = FixQuality::NoFix;
    Position _position;

    char _buffer[kBufferSize];
    uint8_t _bufIndex = 0;
};