#!/usr/bin/python3
# -*- coding: utf-8 -*-

import argparse
import re
from subprocess import run, PIPE
import sys
import asyncio,signal, traceback, gpsd, mgrs, zmq, os, time, logging
from datetime import datetime
from pytz import timezone, utc
from configobj import ConfigObj
from timezonefinder import TimezoneFinder
from logging.handlers import RotatingFileHandler

APP_VERSION = "4.1.6"

### Logging ###

rotate_handler = RotatingFileHandler("/bdr/gpsctl.log", mode='a', maxBytes=10*1024*1024, backupCount=3, encoding=None, delay=0)
log_formatter = logging.Formatter('%(name)s:%(asctime)s - %(levelname)s: %(message)s',"%Y-%m-%d %H:%M:%S")
rotate_handler.setFormatter(log_formatter)
log = logging.getLogger('GPSctl')
log.addHandler(rotate_handler)
log.propagate = False
log.setLevel(logging.DEBUG)
################

### Read global config
# Reference
# https://stackoverflow.com/a/42568457/2147823
# https://stackoverflow.com/a/54148952/2147823
conf = ConfigObj('/etc/bdr.conf') # GPS_DAEMON_SLEEP variable and other global
tracking = '/bdr/gpstrack.log'

# ovrl_cmd = '/usr/local/bin/gstd-client element_set cam_src_pipe location text '
# tz_obj=TimezoneFinder(in_memory=True)
#tz_obj=TimezoneFinder()

CMDDEBUG = False
prev_sats = None
PREV_GEO_UNITS = None

SELF_GPS = "6105"
DUMMY_GPS = "FE00"
NO_LOC_GPS = "FE01"


### ZMQ
zmq_context = zmq.Context()
zmq_socket = zmq_context.socket(zmq.PUB)
zmq_socket.connect("tcp://localhost:5555")
time.sleep(1)

websimulator = "/bdr/websimulator.py"

### Connect to the local gpsd ###
gpsd.connect() # gpsd.connect(host="127.0.0.1", port=2947)
location_current = None
m = mgrs.MGRS()

class CmdTool:
    def __init__(self, name, cmd):
        self.name = name
        self.cmd = cmd

    def to_arg_list(self, *argv):
        args = self.cmd.split()
        for arg in argv:
            args.append(arg)

        return args

    def run(self, *argv):
        cmd_args = self.to_arg_list(*argv)
        cmd_out = run(cmd_args, stdout=PIPE, stderr=PIPE, universal_newlines=True)
        rc = cmd_out.returncode
        try:
            output = cmd_out.stdout  # int(cmd_out.stdout, base=10)
        except Exception:
            output = None

        return rc, output

def parse_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument('--debug', action='store_true', help="DEBUG mode.")
    parser.add_argument('-V', '--version', action='store_true', help='Display the application version.')
    return parser.parse_args()

def deleteContent(fName):
    open(fName, "w").close()

def trim_file_to_size(filepath, max_size_mb=50):
    """Премахва най-старите редове, докато файлът стане <= max_size_mb."""
    if not os.path.exists(filepath):
        return
    max_size = max_size_mb * 1024 * 1024
    while os.path.getsize(filepath) > max_size:
        with open(filepath, 'r') as f:
            lines = f.readlines()
        # Премахва първите 100 реда (или по-малко, ако файлът е малък)
        lines = lines[100:] if len(lines) > 100 else lines[1:]
        with open(filepath, 'w') as f:
            f.writelines(lines)

def split_mgrs(mgrs_string):

    match = re.match(r'^(\d{1,2}[A-Z])([A-Z]{2})(\d+)$', mgrs_string)

    zone = match.group(1)
    square_id = match.group(2)
    coordinates = match.group(3)

    half_len = len(coordinates) // 2
    easting = coordinates[:half_len]
    northing = coordinates[half_len:]

    return "{}|{}|{}|{}".format(zone, square_id, easting, northing)


def cmd_to_mcu(opcode,data):

    send_buff = []
    send_buff.append("E6")
    send_buff.append("00")
    send_buff.append(opcode[:2])
    send_buff.append(opcode[2:])
    for ch in data:      #String
        # if CMDDEBUG:
        #     print(ch)
        send_buff.append(hex(ord(ch))[2:])
    send_buff.append('FF') #ChSum

    # send_buff[1]=str(hex(len(send_buff))).lower() #Replace lenght byte
    strlen = len(send_buff)
    send_buff[1]=str("{0:02x}".format(strlen))

    cmd=''.join(ch for ch in send_buff)
    if CMDDEBUG:
        print("Lenght {} cmd {}".format(strlen,cmd))
        log.debug("Lenght {} cmd {}".format(strlen,cmd))

    CmdTool("cmd_to_mcu",websimulator).run(cmd)
    send_buff.clear()

# def timezone_refresh (current_lat,current_lon,datenew):
#     log.debug('Update TZ')

#     # Get current TZ
#     tz_file=open("/etc/timezone","r")
#     current_tz=tz_file.read()
#     tz_file.close()
#     log.debug('System TZ is: %s', current_tz)

#     # Get GPS TZ
#     tz_obj=TimezoneFinder(in_memory=True)
#     gps_tz=tz_obj.certain_timezone_at(lng=current_lon, lat=current_lat)
#     log.debug('GPS TZ is: %s', gps_tz)

#     if gps_tz.strip() != current_tz.strip():
#         # os.system("timedatectl set-timezone " + str(gps_tz))
#         # os.system("timedatectl set-local-rtc 0")
#         newdate="date -s" + "\"" + str(datenew) + "\""
#         os.system(newdate)
#         os.system("hwclock -w")
#         log.info('Refresh TZ to %s Date is %s', gps_tz, datenew)
#     else:
#         log.debug('Current TZ is the same: %s Date is %s', current_tz, datenew)
#         newdate='date -s"' + str(datenew) + '"'
#         os.system(newdate)

def set_offset (current_lat,current_lon):

    log.debug('Get UTC offset')

    format = "%Y-%m-%d %H:%M:%S %Z%z"
    # today = datetime.now()
    now_utc = datetime.now(timezone('UTC'))

    # Get current TZ
    tz_file=open("/etc/timezone","r")
    current_tz=tz_file.read()
    tz_file.close()
    log.debug('System TZ is: %s', current_tz)

    # Get GPS TZ
    tf=TimezoneFinder(in_memory=True)
    gps_tz=tf.certain_timezone_at(lng=current_lon, lat=current_lat)

    if gps_tz.strip() != current_tz.strip():
        now_current_tz = now_utc.astimezone(timezone(gps_tz))
        newdate='date -s"' + str(now_current_tz) + '"'
        os.system(newdate)
        log.debug("{}: UTC:{} GPSTZ:{}".format(now_current_tz.strftime(format),now_utc,gps_tz))


def gps_parse(configuration):

    global location_current
    global prev_sats
    global PREV_GEO_UNITS

    try:
        gps_data = gpsd.get_current()

        if gps_data.mode < 2:

            time.sleep(3.0)
            log.debug('GPS mode {} sats {}'.format(gps_data.mode,gps_data.sats))
            os.system("echo 0 > /proc/bdr/gpslock")
            deleteContent('/bdr/gpslocation')
            location_current="000000"

            if prev_sats != gps_data.sats:
                cmd_to_mcu(NO_LOC_GPS,"No location @{}".format(gps_data.sats))
                prev_sats = gps_data.sats

        elif gps_data.mode >= 2:

            # time.sleep(int(str(configuration['GPS_DAEMON_SLEEP'])))
            if PREV_GEO_UNITS is None:
                PREV_GEO_UNITS = str(configuration['MILITARY']).strip().lower()

            if PREV_GEO_UNITS == str(configuration['MILITARY']).strip().lower():
                time.sleep(3.0)
                log.debug("****** Same geo units: %s", PREV_GEO_UNITS)
            else:
                location_current = None
                PREV_GEO_UNITS = str(configuration['MILITARY']).strip().lower()
                log.debug("****** Change geo units: %s", PREV_GEO_UNITS)

            latitude = gps_data.lat
            longtitude = gps_data.lon
            altitude = gps_data.alt
            location = str(float(latitude))+" "+str(float(longtitude))

            log.debug('GPS mode=%s location %s altitude %s, sleep %s', str(gps_data.mode), str(location), str(altitude), str(configuration['GPS_DAEMON_SLEEP']))

            if location_current != location:

                location_current = location

                ###     Check&Set TimeZone
                # getdateandtime=str(packet.get_time(local_time=True))
                # getdate=getdateandtime.split(' ',1)[1]
                # timezone_refresh(latitude,longtitude,getdate)
                set_offset(latitude,longtitude)

                ###     Loging data                     ###
                log.info('Change in GPS sats %s position %s',gps_data.sats,gps_data.position())

                ###     Keep track to file "tracking"   ###
                trim_file_to_size(tracking, 50)
                with open (tracking,'a') as trackingfile:
                    trackingfile.write("%s: %s  %s\n"% ( str(gps_data.get_time(local_time=True)),gps_data.position(),gps_data.map_url()))

                ### Send to DMC deaemon gps data with ZMQ
                ## TODO - Should be string.encode('utf-8') ?
                log.debug("Send to ZMQ: {} {} {} {}".format("GPS",latitude,longtitude,altitude))
                zmq_socket.send_string("%s %s %s" % ("GPS",latitude,longtitude))

                if str(configuration['MILITARY']).strip().lower() == 'true':
                    # 12Q FJ 12345 67890
                    # 12QFJ1234567890
                    mgrs_string = m.toMGRS(float(latitude),float(longtitude))
                    # data_string = split_mgrs(data_string)
                    data_string = mgrs_string #+ "|" + str(round(float(altitude)))
                    log.debug("Latitude {} Longtitude {} to MGRS+alt: {}".format(latitude,longtitude,data_string))
                else:
                    data_string = str(round(float(latitude), 6)) + "|" + str(round(float(longtitude), 6)) #+ "|" + str(round(float(altitude)))

                cmd_to_mcu(DUMMY_GPS,str(data_string))

                ###     Write GPS Mode 2/3 and data     ###
                with open ('/bdr/gpslocation','w') as gpslocation:
                    gpslocation.write(str(data_string))
                log.debug('GPS coords to MCU {}'.format(data_string))

        else:
            time.sleep(int(str(conf['GPS_DAEMON_SLEEP'])))
            os.system("echo 0 > /proc/bdr/gpslock")
            deleteContent('/bdr/gpslocation')
            location_current="000000"
            log.warning("No location @{}".format(gps_data.sats))
            cmd_to_mcu(NO_LOC_GPS,"No location @{}".format(gps_data.sats))

    except Exception as e:
        os.system("echo 0 > /proc/bdr/gpslock")
        deleteContent('/bdr/gpslocation')
        location_current="000000"
        log.error("{} {} ".format(traceback.extract_tb(e.__traceback__)[-1],e))

async def shutdown(sig, loop):
    try:

        cmd_to_mcu(DUMMY_GPS,"                  ")
        # signal the remaining tasks in the loop that its time to stop
        tasks = [task for task in asyncio.Task.all_tasks() if task is not
                 asyncio.tasks.Task.current_task()]
        list(map(lambda task: task.cancel(), tasks))
        await asyncio.gather(*tasks, return_exceptions=True)
        # stop the loop
        loop.stop()

    except Exception as e:
        logging.error("{} {} ".format(traceback.extract_tb(e.__traceback__)[-1],e))

def main_loop(loop):

    while True:

        try:

            # set_offset(42.51,24.14)
            conf = ConfigObj('/etc/bdr.conf') # GPS_DAEMON_SLEEP variable and other global vars

            try:
                gps_parse(conf)
            except Exception as str_error:
                log.error('Error gps_parse {}'.format(str_error))
                continue

        except Exception as e:
            log.error("{} {} ".format(traceback.extract_tb(e.__traceback__)[-1],e))

if __name__ == "__main__":

    args = parse_arguments()

    if args.debug:
        log.setLevel(logging.DEBUG)
    elif args.version:
        print("Version: {}".format(APP_VERSION))
        sys.exit()

    loop = asyncio.get_event_loop()
    asyncio.ensure_future(main_loop(loop), loop=loop)

    try:
        loop.add_signal_handler(getattr(signal, 'SIGINT'), lambda: asyncio.ensure_future(shutdown('SIGINT', loop)))
        loop.add_signal_handler(getattr(signal, 'SIGTERM'),lambda: asyncio.ensure_future(shutdown('SIGTERM', loop)))
    except Exception as e:
        logging.error("{} {} ".format(traceback.extract_tb(e.__traceback__)[-1],e))

    try:
        loop.run_forever()
    finally:
        loop.close()
### reference
# https://stackoverflow.com/a/20985318/2147823
# https://stackoverflow.com/a/48726480/2147823
###
