#!/usr/bin/python3


"""
Created on Mar 3, 2017

@author: 4X5DM
@other: F4IYQ
"""

from math import floor
import argparse

# Constants
ASCII_0 = 48
ASCII_A = 65
ASCII_a = 97

# argparse
parser = argparse.ArgumentParser(description='Convert QTH locator to latitude and longitude.')

subparsers = parser.add_subparsers()
p_square_to_location = subparsers.add_parser('square_to_location', help='Convert QTH locator to latitude and longitude')
p_square_to_location.set_defaults(func='square_to_location')
p_square_to_location.add_argument('qth_locator', type=str, help='QTH locator in format AA00 or AA00aa00')

p_location_to_square = subparsers.add_parser('location_to_square', help='Convert latitude and longitude to QTH locator')
p_location_to_square.set_defaults(func='location_to_square')
p_location_to_square.add_argument('--lat', type=float, help='Latitude in decimal format')
p_location_to_square.add_argument('--lon', type=float, help='Longitude in decimal format')

p_test = subparsers.add_parser('test', help='Test QTH locator conversion')
p_test.set_defaults(func='test')

args = parser.parse_args()

def square_to_location(qth_locator):
    """
    Converts QTH locator to latitude and longitude in decimal format.
    Gets QTH locator as string.
    Returns Tuple containing latitude and longitude as floats.
    """

    # Validate input
    assert isinstance(qth_locator, str)
    assert 4 <= len(qth_locator) <= 8
    assert len(qth_locator) % 2 == 0

    qth_locator = qth_locator.upper()

    # Separate fields, squares and subsquares
    # Fields
    lon_field = ord(qth_locator[0]) - ASCII_A
    lat_field = ord(qth_locator[1]) - ASCII_A

    # Squares
    lon_sq = ord(qth_locator[2]) - ASCII_0
    lat_sq = ord(qth_locator[3]) - ASCII_0

    # Subsquares
    if len(qth_locator) >= 6:
        lon_sub_sq = ord(qth_locator[4]) - ASCII_A
        lat_sub_sq = ord(qth_locator[5]) - ASCII_A
    else:
        lon_sub_sq = 0
        lat_sub_sq = 0

    # Extended squares
    if len(qth_locator) == 8:
        lon_ext_sq = ord(qth_locator[6]) - ASCII_0
        lat_ext_sq = ord(qth_locator[7]) - ASCII_0
    else:
        lon_ext_sq = 0
        lat_ext_sq = 0

    # Calculate latitude and longitude
    lon = -180.0
    lat = -90.0

    lon += 20.0 * lon_field
    lat += 10.0 * lat_field

    lon += 2.0 * lon_sq
    lat += 1.0 * lat_sq

    lon += 5.0 / 60 * lon_sub_sq
    lat += 2.5 / 60 * lat_sub_sq

    lon += 0.5 / 60 * lon_ext_sq
    lat += 0.25 / 60 * lat_ext_sq

    return lat, lon


def location_to_square(lat, lon):
    """
    Converts latitude and longitude in decimal format to QTH locator.
    Gets latitude and longitude as floats.
    Returns QTH locator as string.
    """

    # Validate input
    assert isinstance(lat, (int, float))
    assert isinstance(lon, (int, float))
    assert -90.0 <= lat <= 90.0
    assert -180.0 <= lon <= 180.0

    # Separate fields, squares and subsquares
    lon += 180
    lat += 90

    # Fields
    lon_field = int(floor(lon / 20))
    lat_field = int(floor(lat / 10))

    lon -= lon_field * 20
    lat -= lat_field * 10

    # Squares
    lon_sq = int(floor(lon / 2))
    lat_sq = int(floor(lat / 1))

    lon -= lon_sq * 2
    lat -= lat_sq * 1

    # Subsquares
    lon_sub_sq = int(floor(lon / (5.0 / 60)))
    lat_sub_sq = int(floor(lat / (2.5 / 60)))

    lon -= lon_sub_sq * (5.0 / 60)
    lat -= lat_sub_sq * (2.5 / 60)

    # Extended squares
    lon_ext_sq = int(round(lon / (0.5 / 60)))
    lat_ext_sq = int(round(lat / (0.25 / 60)))

    qth_locator = f'{chr(lon_field + ASCII_A)}'
    qth_locator += chr(lat_field + ASCII_A)

    qth_locator += chr(lon_sq + ASCII_0)
    qth_locator += chr(lat_sq + ASCII_0)

    if lon_sub_sq > 0 or lat_sub_sq > 0 or lon_ext_sq > 0 or lat_ext_sq > 0:
        qth_locator += chr(lon_sub_sq + ASCII_a)
        qth_locator += chr(lat_sub_sq + ASCII_a)

    if lon_ext_sq > 0 or lat_ext_sq > 0:
        qth_locator += chr(lon_ext_sq + ASCII_0)
        qth_locator += chr(lat_ext_sq + ASCII_0)

    return qth_locator


if __name__ == '__main__':

    if args.func == 'test':
        print('Testing QTH Locator conversion...')
        squares = [
        ('JJ00', (0, 0)),
        ('KM32', (32, 26)),
        ('KM32jn07', (32.570831, 26.750003)),
        ('KM72kk55', (32.437487, 34.875015)),
        ('JN45fo', (45.603333, 8.456667)),
        ('KO92so', (52.601484, 39.565160)),
        ('KM72jb', (32.071209, 34.780089)),
        # ('', (0, 0)),
        # ('', (0, 0)),
        ]

        print('\nQTH locator to coordinates:')
        for sq, res in squares:
            loc = square_to_location(sq)
            print(f'{sq}: {res}, calculated: {loc}')

        print('\nCoordinates to QTH locator:')
        for sq, loc in squares:
            qth = location_to_square(loc[0], loc[1])
            print(f'{loc}: {sq}, calculated: {qth}')

    if args.func == 'square_to_location':
        qth = args.qth_locator
        lat, lon = square_to_location(qth)
        print(f'QTH locator: {qth}, calculated coordinates: {lat}, {lon}')
        exit(0)

    if args.func == 'location_to_square':
        lat = args.lat
        lon = args.lon
        if lat is None or lon is None:
            parser.print_help()
            exit(1)
        if lat == 0 and lon == 0:
            print('Error: latitude and longitude cannot be both zero.')
            exit(1)
        if lat < -90 or lat > 90:
            print('Error: latitude must be between -90 and 90.')
            exit(1)
        if lon < -180 or lon > 180:
            print('Error: longitude must be between -180 and 180.')
            exit(1)
        qth = location_to_square(lat, lon).upper()
        print(qth)
        exit(0)


    print('\nDone.')
