/*
 * GPS.c
 *
 * Copyright 2015 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include "GPS.h"
#include "hardware.h"
#include "time.h"
#include "usart.h"
#include "str.h"
#include "usb.h"
#include <string.h> // memcpy

#define GPS_send_string(str) do{usart_send(GPS_USART, str);}while(0)

gps_status GPS_status = GPS_NOTFOUND;
int need2startseq = 1;

static uint8_t hex(uint8_t n){
    return ((n < 10) ? (n+'0') : (n+'A'-10));
}

/**
 * Check checksum
 */
static int checksum_true(const char *buf){
    char *eol;
    uint8_t checksum = 0, cs[3];
    if(*buf != '$' || !(eol = getchr(buf, '*'))){
        return 0;
    }
    while(++buf != eol)
        checksum ^= (uint8_t)*buf;
    ++buf;
    cs[0] = hex(checksum >> 4);
    cs[1] = hex(checksum & 0x0f);
    if(buf[0] == cs[0] && buf[1] == cs[1])
        return 1;
    return 0;
}

static void send_chksum(uint8_t chs){
    usart_putchar(GPS_USART, hex(chs >> 4));
    usart_putchar(GPS_USART, hex(chs & 0x0f));
}

/**
 * Calculate checksum & write message to port
 * @param  buf - command to write (without leading $ and trailing *)
 * return 0 if fails
 */
static void write_with_checksum(const char *buf){
    /*
    for(int i = 0; i < 10000; ++i){
        char *txt = NULL;
        if(usartrx(GPS_USART)){
            usart_getline(GPS_USART, &txt);
            DBG("Old data");
            GPS_parse_answer(txt);
            break;
        }
    }*/
    uint8_t checksum = 0;
    usart_putchar(GPS_USART, '$');
    GPS_send_string(buf);
    do{
        checksum ^= *buf++;
    }while(*buf);
    usart_putchar(GPS_USART, '*');
    send_chksum(checksum);
    newline(GPS_USART);
}


/*
 * MTK fields format:
 *      $PMTKxxx,yyy,zzz*2E
 *          P - proprietary, MTK - always this, xxx - packet type, yyy,zzz - packet data
 * Packet types:
 * 220 - PMTK_SET_POS_FIX, data - position fix interval (msec, > 200)
 * 255 - PMTK_SET_SYNC_PPS_NMEA - turn on/off (def - off) PPS, data = 0/1 ->  "$PMTK255,1" turn ON
 * 285 - PMTK_SET_PPS_CONFIG - set PPS configuration, data fields:
 *      1st - 0-disable, 1-after 1st fix, 2-3D only, 3-2D/3D only, 4-always
 *      2nd - 2..998 - pulse width
 * 314 - PMTK_API_SET_NMEA_OUTPUT - set output messages, N== N fixes per output,
 *      order of messages: GLL,RMC,VTG,GGA,GSA,GSV,GRS,GST, only RMC per every pos fix:
 *      $PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
 * 386 - PMTK_API_SET_STATIC_NAV_THD speed threshold (m/s) for static navigation
 *      $PMTK386,1.5
 * ;
 */

/**
 * Send starting sequences (get only RMC messages)
 */
void GPS_send_start_seq(){
    DBG("Send start seq");
    // turn ON PPS:
    write_with_checksum("PMTK255,1");
    // set pulse width to 10ms with working after 1st fix
    write_with_checksum("PMTK285,1,10");
    // set only RMC:
    write_with_checksum("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0");
    // set static speed threshold
    write_with_checksum("PMTK386,1.5");
    need2startseq = 0;
}

// send "full cold start" command to clear all almanach & location data
void GPS_send_FullColdStart(){
    write_with_checksum("PMTK104");
}

/**
 * Parse answer from GPS module
 *
 * Recommended minimum specific GPS/Transit data
 * $GPRMC,hhmmss.sss,status,latitude,N,longitude,E,spd,cog,ddmmyy,mv,mvE,mode*cs
 * 1    = UTC of position fix
 * 2    = Data status (V=valid, A=invalid)
 * 3    = Latitude (ddmm.mmmm)
 * 4    = N or S
 * 5    = Longitude (dddmm.mmmm)
 * 6    = E or W
 * 7    = Speed over ground in knots
 * 8    = Cource over ground in degrees
 * 9    = UT date (ddmmyy)
 * 10   = Magnetic variation degrees (Easterly var. subtracts from true course)
 * 11   = E or W
 * 12   = Mode: N(bad), E(approx), A(auto), D(diff)
 * 213457.00,A,4340.59415,N,04127.47560,E,2.494,,290615,,,A*7B
 */
void GPS_parse_answer(const char *buf){
    char *ptr;
    if(buf[1] == 'P') return; // answers to proprietary messages
    if(cmpstr(buf+3, "RMC", 3)){ // not RMC message
        //need2startseq = 1;
        return;
    }
    if(!checksum_true(buf)){
        return; // wrong checksum
    }
    if(showGPSstr){
        showGPSstr = 0;
        sendstring(buf);
    }
    buf += 7; // skip header
    if(*buf == ','){ // time unknown
        GPS_status = GPS_WAIT;
        return;
    }
    ptr = getchr(buf, ',');
    if(!ptr ) return;
    *ptr++ = 0;
    if(*ptr == 'A'){
        GPS_status = GPS_VALID;
        set_time(buf);
    }else{
        uint8_t goth = (buf[0]-'0')*10 + buf[1]-'0';
        if(current_time.H != goth) set_time(buf); // set time once per hour even if it's not valid
        GPS_status = GPS_NOT_VALID;
    }
}
