#include "sam_m10q.h"
#include "minmea.h"

#include <string.h>

#define SAM_M10Q_I2C_ADDR (0x42 << 1)

static I2C_HandleTypeDef *sam_i2c = NULL;

void SAM_M10Q_Init(I2C_HandleTypeDef *i2c)
{
    sam_i2c = i2c;
}

HAL_StatusTypeDef SAM_M10Q_IsReady(void)
{
    if (sam_i2c == NULL)
    {
        return HAL_ERROR;
    }

    return HAL_I2C_IsDeviceReady(
        sam_i2c,
        SAM_M10Q_I2C_ADDR,
        3,
        100);
}


static uint16_t GNSS_BytesAvailable(void)
{
    uint8_t len_buf[2];
    // sam register 253 (0xFD) and 254 (0xFE) hold the byte count value
    // get both in one i2c since they are consecutive
    HAL_StatusTypeDef ret = HAL_I2C_Mem_Read(
        sam_i2c, SAM_M10Q_I2C_ADDR,
        0xFD, I2C_MEMADD_SIZE_8BIT,
        len_buf, 2, 100);
    if (ret != HAL_OK) return 0;
    // bit shift len_buf[0] OR with  len_buf[1]
    return ((uint16_t)len_buf[0] << 8) | len_buf[1];
}

static HAL_StatusTypeDef GNSS_ReadChunk(uint8_t *data, uint16_t len)
{

    // read a chunk of bytes of size len from sam data stream register 0xFF 
    // 0 <= len < 256
    if (sam_i2c == NULL || data == NULL || len == 0){return HAL_ERROR;}

    return HAL_I2C_Mem_Read(
        sam_i2c,
        SAM_M10Q_I2C_ADDR,
        0xFF,
        I2C_MEMADD_SIZE_8BIT,
        data,
        len,
        100);
}

static uint8_t NMEA_ParseRMC(const char *sentence, SAM_M10Q_Data *gnss)
{
    struct minmea_sentence_rmc frame;

    if (sentence == NULL || gnss == NULL)
    {
        return 0;
    }

    if (!minmea_parse_rmc(&frame, sentence))
    {
        return 0;
    }

    gnss->year = (uint16_t)(2000U + (uint16_t)frame.date.year);
    gnss->month = (uint8_t)frame.date.month;
    gnss->day = (uint8_t)frame.date.day;

    return 1;
}

static uint8_t NMEA_ParseGGA(const char *sentence, SAM_M10Q_Data *gnss)
{

    struct minmea_sentence_gga frame;

    if (sentence == NULL || gnss == NULL)
    {
        return 0;
    }

    if (!minmea_parse_gga(&frame, sentence))
    {
        return 0;
    }

    // memset(gnss, 0, sizeof(*gnss));

    gnss->fix_quality = (uint8_t)frame.fix_quality;
    gnss->valid_fix = (frame.fix_quality != 0);
    gnss->satellites = (uint8_t)frame.satellites_tracked;

    gnss->hour = (uint8_t)frame.time.hours;
    gnss->minute = (uint8_t)frame.time.minutes;
    gnss->second = (uint8_t)frame.time.seconds;
    gnss->millisecond = (uint16_t)(frame.time.microseconds / 1000);

    gnss->latitude = (double)minmea_tocoord(&frame.latitude);

    gnss->longitude = (double)minmea_tocoord(&frame.longitude);

    gnss->altitude_m = (double)minmea_tofloat(&frame.altitude);

    return 1;
}

static char line[128];
static uint16_t line_len = 0;

uint8_t SAM_M10Q_Read_GGA(SAM_M10Q_Data *gnss)
{
    // keep going to sam sentence is captured = 1; this is a flag
    uint8_t captured = 0;


    // stores one complete NMEA sentence
    // google says NMEA sentences are normally much shorter than 128 bytes so this should be safe
    // char line[128];

    // increment the char index
    // uint16_t line_len = 0;

    // grab up to size of chunk from i2c bus (temp buffer)
    // A 64-byte chunk is trying to find a balance of reducing i2c calls and adding small buffer    
    uint8_t chunk[64];
    

    // debbuging 
    // seems like there's 1,000 bytes of output between GGA sentences
    // 1000 / 64 ~ 64
    // uint32_t chunks_read = 0;

    if (sam_i2c == NULL || gnss == NULL)
    {
        return 0;
    }

    uint16_t available = GNSS_BytesAvailable();

    if (available == 0 || available == 0xFFFF)
    {
        return 0;
    }

    uint16_t to_read;

    // if available > sizeof(chunk), we'll get the rest on the next loop go-around
    if (available > sizeof(chunk))
    {
        to_read = sizeof(chunk);
    }
    else
    {
        to_read = available;
    }

    // available shrinks with GNSS_ReadChunk i2c call
    if (GNSS_ReadChunk(chunk, to_read) != HAL_OK)
    {
        HAL_Delay(2);
        return 0;
    }

    // look at every byte in chunk and build data
    for (uint16_t i = 0; i < to_read; i++)
    {
        uint8_t byte = chunk[i];

        // every NMEA sentence begins with '$'. When '$' is found
        if (byte == '$')
        {
            line_len = 0;
        }

        // store the byte if room remains for it and for the final null terminator
        // need one line comment explaining (char)byte; 
        if (line_len < sizeof(line) - 1)
        {
            line[line_len++] = (char)byte;
        }
        else
        {
            // The sentence exceeded the buffer size, start over
            // this should not happen ever
            line_len = 0;
            return 0;
        }

        // NMEA sentences ends with "\r\n". 
        // Detecting '\n' tells an entire sentence has been received.
        if (byte == '\n')
        {
            line[line_len] = '\0';


            if (line_len > 6)
            {
                if (strncmp(line, "$GNRMC", 6) == 0)
                {
                    NMEA_ParseRMC(line, gnss);
                }
                else if (strncmp(line, "$GNGGA", 6) == 0)
                {
                    if (NMEA_ParseGGA(line, gnss))
                    {
                        captured = 1;
                        break;
                    }
                }
            }

            line_len = 0;
        }
    }

    return captured;
}
