#!/usr/bin/env python
#
# Copyright (c) 2019, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#
# https://www.quectel.com/product/l76l.htm
#
# IMPORTANT !!!
# for best data use an active antenna part# 604565430465 Mouser
# Jump connctor P3 of Pytrack board
#
from machine import Timer
import time
import gc
import binascii

from trap import * # 2.2.20


class L76GNSS:

    GPS_I2CADDR = const(0x10)

    def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None, buffer=64, sdAccess=False): # 2.2.20
        # 8b
        # if pytrack is not None:
        #    self.i2c = pytrack.i2c
        # else:
        from machine import I2C
        self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))

        self.chrono = Timer.Chrono()

        self.timeout = timeout
        self.timeout_status = True
        self.buffer = buffer
        self.sdAccess = sdAccess # 2.2.20

        # 8b
        # self.reg = bytearray(1)
        # self.i2c.writeto(GPS_I2CADDR, self.reg)


    def begin(self): # 8b
        try:
            # disable write command
            # self.reg = bytearray(1)
            # self.i2c.writeto(GPS_I2CADDR, self.reg)
            nmea = b''
            nmea = self._read()
            print("GPS Hi:" + str(nmea))
            return True

        except:
            # 2.2.20  print('GPS Adr:' + str(GPS_I2CADDR) + ' not found')
            scratch = 'GPS Adr:' + str(GPS_I2CADDR) + ' not found' # 2.2.20
            # 2.2.18 print('Begin I2C1 scan ...')
            # 2.2.18 print(self.i2c.scan())
            file_errLog(0, scratch, self.sdAccess) # 2.2.20
            return False


    def _read(self):
        self.reg = self.i2c.readfrom(GPS_I2CADDR, self.buffer)
        return self.reg

    def _convert_coords(self, gngll_s):
        lat = gngll_s[1]
        lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60)
        lon = gngll_s[3]
        lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60)
        if gngll_s[2] == 'S':
            lat_d *= -1
        if gngll_s[4] == 'W':
            lon_d *= -1
        return(lat_d, lon_d)

    def coordinates(self, debug=False):
        lat_d, lon_d, debug_timeout = None, None, False
        if self.timeout is not None:
            self.chrono.reset()
            self.chrono.start()
        nmea = b''
        while True:
            if self.timeout is not None and self.chrono.read() >= self.timeout:
                self.chrono.stop()
                chrono_timeout = self.chrono.read()
                self.chrono.reset()
                self.timeout_status = False
                debug_timeout = True
            if not self.timeout_status:
                gc.collect()
                break
            nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n')
            gngll_idx = nmea.find(b'GNGLL')
            gpgll_idx = nmea.find(b'GPGLL')
            # 8b
            # if debug == True:
            #    print("gngll_idx:" + str(gngll_idx) + " - gpgll_idx:" + str(gpgll_idx))
            #    print("nmea_1:" + str(nmea))

            if gngll_idx < 0 and gpgll_idx >= 0: # get data stream either from GNGLL or GPGLL
                gngll_idx = gpgll_idx
            if gngll_idx >= 0:
                gngll = nmea[gngll_idx:]
                e_idx = gngll.find(b'\r\n')
                if e_idx >= 0:
                    try:
                        gngll = gngll[:e_idx].decode('ascii')
                        gngll_s = gngll.split(',')  # create a list of the data from the frame
                        # 8b
                        # if debug == True:
                        #    print("gngll_s(" + str(len(gngll_s)) + "):" + str(gngll_s))
                        #    # 9b print("nmea_2:" + str(nmea))

                        # 9b lat_d, lon_d = self._convert_coords(gngll_s)
                        nmea = nmea[(gngll_idx + e_idx):]
                        if len(gngll_s) >= 8: # Quectel_L76_Series_GNSS_Protocol_Specification_V3.3.pdf
                            if gngll_s[6] == 'A': # check the validity of the data [6] = A
                                lat_d, lon_d = self._convert_coords(gngll_s)
                                gc.collect()
                                break
                        if debug == True:
                            print('invalid gngll_s packet !!!')
                            print("gngll_s(" + str(len(gngll_s)) + "):" + str(gngll_s))

                    except Exception:
                        pass
                    # 9b
                    # finally:
                    #    nmea = nmea[(gngll_idx + e_idx):]
                    #    # 8b
                    #    # if debug == True:
                    #    #    print("nmea_3:" + str(nmea))
                    #
                    #    gc.collect()
                    #    break
            else:
                gc.collect()
                if len(nmea) > 410: # it could safely be changed to 82 (longest NMEA frame)
                    nmea = nmea[-5:] # keep the last 5 chr (anounciateur 'G*GLL')

            time.sleep(0.1)

        self.timeout_status = True
        if debug and debug_timeout:
            print('GPS timed out after %f seconds' % (chrono_timeout))
            return(None, None)
        else:
            return(lat_d, lon_d)

    def dump_nmea(self):
        nmea = b''
        while True:
            nmea = self._read().lstrip(b'\n\n').rstrip(b'\n\n')
            start_idx = nmea.find(b'$')
            #print('raw[{}]: {}'.format(start_idx, nmea))
            if nmea is not None and len(nmea) > 0:
                if start_idx != 0:
                    if len(nmea[:start_idx]) > 1:
                        print('{}'.format(nmea[:start_idx].decode('ASCII')), end='')
                if len(nmea[start_idx:]) > 1:
                    print('{}'.format(nmea[start_idx:].decode('ASCII')), end='')

    def _checksum(self, nmeadata):
        calc_cksum = 0
        for s in nmeadata:
            calc_cksum ^= ord(s)
        return('{:X}'.format(calc_cksum))

    def write(self, data):
        self.i2c.writeto(GPS_I2CADDR, '${}*{}\r\n'.format(data, self._checksum(data)) )
