import unittest
import string
from random import randint, randrange, sample
from datetime import datetime

from aprslib import base91
from aprslib.parsing.common import *
from aprslib.exceptions import ParseError


class ValidateCallsign(unittest.TestCase):

    def testvalid_input(self):
        chars = string.ascii_letters.upper() + string.digits

        def randomvalid_callsigns():
            for x in range(0, 500):
                call = "".join(sample(chars, randrange(1, 6)))

                if bool(randint(0, 1)):
                    call += "-%d" % randint(0, 15)

                yield call

        for call in randomvalid_callsigns():
            try:
                validate_callsign(call)
            except ParseError:
                self.fail(
                    "%s('%s') raised ParseError" %
                    (validate_callsign.__name__, call)
                    )

    def test_invalid_input(self):
        testData = [
            "",
            "-",
            "-1",
            "---1",
            "1234567",
            "CALL-",
            "CALL-16",
            "CALLCALL",
            ]

        for call in testData:
            self.assertRaises(ParseError, validate_callsign, call)


class ParseHeader(unittest.TestCase):
    def test_no_tocall(self):
        with self.assertRaises(ParseError):
            parse_header("AAA>")
            parse_header("AAA>,")

    def testvalid_input_and_format(self):
        # empty path header
        expected = {
            "from": "A",
            "to": "B",
            "via": "",
            "path": []
            }
        result = parse_header("A>B")

        self.assertEqual(expected, result)

        # with path
        expected2 = {
            "from": "A",
            "to": "B",
            "via": "",
            "path": list('CDE')
            }
        result2 = parse_header("A>B,C,D,E")

        self.assertEqual(expected2, result2)

        # test all currently valid q-cosntructs
        expected3 = {
            "from": "A",
            "to": "B",
            "via": "E",
            "path": ['C', 'D', 'qAR', 'E']
            }
        result3 = parse_header("A>B,C,D,qAR,E")

        self.assertEqual(expected3, result3)

        for qCon in map(lambda x: "qA"+x, list("CXUoOSrRRZI")):
            expected4 = {
                "from": "A",
                "to": "B",
                "via": "C",
                "path": [qCon, 'C']
                }
            result4 = parse_header("A>B,%s,C" % qCon)

            self.assertEqual(expected4, result4)

    def testvalid_fromcallsigns(self):
        testData = [
            "A>CALL",
            "4>CALL",
            "aaabbbccc>CALL",
            "AAABBBCCC>CALL",
            "AA1B2BCC9>CALL",
            "aa1b2bcc9>CALL",
            "-1>CALL",
            "-111>CALL",
            "-98765432>CALL",
            "-a>CALL",
            "-abcZZZxx>CALL",
            "a-a>CALL",
            "a-aaaaaa>CALL",
            "callsign1>CALL",
            "AAABBB-9>CALL",
            "AAABBBC-9>CALL",
            "AAABBB-S>CALL",
            "AAABBBC-S>CALL",
            ]

        for head in testData:
            try:
                parse_header(head)
            except ParseError as msg:
                self.fail("{0}('{1}') PraseError, {2}"
                          .format(parse_header.__name__, head, msg))

    def test_invalid_format(self):
        testData = [
            "",     # empty header
            ">CALL;>test",    # empty fromcall
            "A>:>test",   # empty tocall
            "A>-99:>test",  # invalid tocall
            "aaaAAAaaaA>CALL",  # fromcall too long
            "->CALL",  # invalid fromcall
            "A->CALL",  # invalid fromcall
            "AB->CALL",  # invalid fromcall
            "ABC->CALL",  # invalid fromcall
            "9999->CALL",  # invalid fromcall
            "999aaa11->CALL",  # invalid fromcall
            "-AAA999AAA>CALL",  # invalid fromcall, too long
            "A>aaaaaaaaaaa",  # invalid tocall
            "A>B,1234567890,C",  # invalid call in path
            "A>B,C,1234567890,D",  # invalid call in path
            "A>B,C,1234567890",  # invalid call in path
            ]

        for head in testData:
            try:
                parse_header(head)
                self.fail("{0} didn't raise exception for: {1}"
                          .format(parse_header.__name__, head))
            except ParseError:
                continue


class TimestampTC(unittest.TestCase):
    def test_timestamp_invalid(self):
        body = "000000ntext"
        remaining, parsed = parse_timestamp(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'timestamp': 0,
            'raw_timestamp': '000000n',
            })

    def test_status_timestamp_invalid(self):
        body = "000000htext"
        remaining, parsed = parse_timestamp(body, '>')

        self.assertEqual(remaining, body)
        self.assertEqual(parsed, {
            'timestamp': 0,
            'raw_timestamp': '000000h',
            })

    def test_timestamp_valid(self):
        date = datetime.utcnow()
        td = date - datetime(1970, 1, 1)
        timestamp = int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)

        # hhmmss format
        body = date.strftime("%H%M%Shtext")
        remaining, parsed = parse_timestamp(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'timestamp': timestamp,
            'raw_timestamp': body[:7],
            })

        # ddhhmm format
        body = date.strftime("%d%H%Mztext")
        remaining, parsed = parse_timestamp(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'timestamp': timestamp - date.second,
            'raw_timestamp': body[:7],
            })

        # ddhhmm format, local time, we parse as zulu
        body = date.strftime("%d%H%M/text")
        remaining, parsed = parse_timestamp(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'timestamp': timestamp - date.second,
            'raw_timestamp': body[:7],
            })

    def test_invalid_date(self):
        body = "999999ztext"

        remaining, parsed = parse_timestamp(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'timestamp': 0,
            'raw_timestamp': '999999z',
            })


class CommentTC(unittest.TestCase):
    def test_comment(self):
        body = "test body"
        parsed = {}
        parse_comment(body, parsed)

        self.assertEqual(parsed, {'comment': body})

        body = "/test body"
        parsed = {}
        parse_comment(body, parsed)

        self.assertEqual(parsed, {'comment': body[1:]})

        body = "  test body     "
        parsed = {}
        parse_comment(body, parsed)

        self.assertEqual(parsed, {'comment': body.strip(' ')})


class DataExtentionsTC(unittest.TestCase):
    def test_course_speed(self):
        body = "123/100/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '/text')
        self.assertEqual(parsed, {
            'course': 123,
            'speed': 100*1.852,
            })

    def test_course_speed_spaces(self):
        body = "   /   /text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '/text')
        self.assertEqual(parsed, {})

    def test_course_speed_dots(self):
        body = ".../.../text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '/text')
        self.assertEqual(parsed, {})

    def test_course_speed_zeros(self):
        body = "000/000/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '/text')
        self.assertEqual(parsed, {})

    def test_course_speed_valid_chars_but_invalid_values(self):
        body = "22./33 /text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '/text')
        self.assertEqual(parsed, {})

    def test_course_speed_invalid_chars_spd(self):
        body = "222/33a/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '222/33a/text')
        self.assertEqual(parsed, {})

    def test_course_speed_invalid_chars_cse(self):
        body = "22a/333/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, '22a/333/text')
        self.assertEqual(parsed, {})

    def test_empty_bearing_nrq(self):
        body = "111/100/   /...text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'course': 111,
            'speed': 100*1.852,
            })

        body = "111/100/2  /33.text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'course': 111,
            'speed': 100*1.852,
            })

    def test_course_speed_bearing_nrq_empty_cse_speed(self):
        body = "000/000/234/345text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'course': 0,
            'bearing': 234,
            'nrq': 345,
            })

    def test_course_speed_bearing_nrq(self):
        body = "123/100/234/345text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'course': 123,
            'speed': 100*1.852,
            'bearing': 234,
            'nrq': 345,
            })

    def test_PHGR_1(self):
        body = "PHG51325/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'phg': '51325',
            'phg_rate': 5,
            'phg_power': 25,
            'phg_height': 6.096,
            'phg_gain': 1.9952623149688795,
            'phg_dir': 90,
            'phg_range': 12.791023731208883,
            })

    def test_PHGR_2(self):
        body = "PHG5132F/text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'phg': '5132F',
            'phg_rate': 15,
            'phg_power': 25,
            'phg_height': 6.096,
            'phg_gain': 1.9952623149688795,
            'phg_dir': 90,
            'phg_range': 12.791023731208883,
            })

    def test_PHG_1(self):
        body = "PHG5132Atext"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'Atext')
        self.assertEqual(parsed, {
            'phg': '5132',
            'phg_power': 25,
            'phg_height': 6.096,
            'phg_gain': 1.9952623149688795,
            'phg_dir': 90,
            'phg_range': 12.791023731208883,
            })

    def test_PHG_2(self):
        body = "PHG5132text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'phg': '5132',
            'phg_power': 25,
            'phg_height': 6.096,
            'phg_gain': 1.9952623149688795,
            'phg_dir': 90,
            'phg_range': 12.791023731208883,
            })

    def test_PHG_dir_omni(self):
        body = "PHG0000text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed['phg_dir'], 'omni')

    def test_PHG_dir_invalid(self):
        body = "PHG0009text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed['phg_dir'], 'invalid')

    def test_range(self):
        body = "RNG1000text"
        remaining, parsed = parse_data_extentions(body)

        self.assertEqual(remaining, 'text')
        self.assertEqual(parsed, {
            'rng': 1000*1.609344,
            })



class CommentAltitudeTC(unittest.TestCase):
    def test_valid_inputs(self):
        body = "asdasd/A=10000078zxc"
        remaining, parsed = parse_comment_altitude(body)

        self.assertEqual(remaining, "asdasd78zxc")
        self.assertEqual(parsed, {'altitude': 30480})

        body = "asdasd/A=-1000078zxc"
        remaining, parsed = parse_comment_altitude(body)

        self.assertEqual(remaining, "asdasd78zxc")
        self.assertEqual(parsed, {'altitude': -3048})

    def test_invalid(self):
        tests = [
            "",
            "aaaaaaaaaaaaaaaaaaaaa",
            "sdfsdfsdf/A=00000aaaa",
            "sdfsdfsdf/A=0000aaaaa",
            "sdfsdfsdf/A=000aaaaaa",
            "sdfsdfsdf/A=00aaaaaaa",
            "sdfsdfsdf/A=0aaaaaaaa",
            "sdfsdfsdf/A=aaaaaaaaa",
            "sdfsdfsdf/Aa000000aaa",
            "sdfsdfsdf/A=+00000aaa",
            "sdfsdfsdf/A=00a000aaa",
            ]

        for body in tests:
            remaining, parsed = parse_comment_altitude(body)
            self.assertEqual(remaining, body)
            self.assertEqual(parsed, {})


class DAO_TC(unittest.TestCase):
    def test_wgs84_human_readable(self):
        body = "!W36!"
        parsed = {'latitude': 0, 'longitude': 0}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, '')
        self.assertEqual(parsed, {
            'daodatumbyte': 'W',
            'latitude': 0.00005,
            'longitude': 0.0001,
            })

    def test_wgs84_human_readable_nagarive(self):
        body = "!W36!"
        parsed = {'latitude': -1, 'longitude': -1}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, '')
        self.assertEqual(parsed, {
            'daodatumbyte': 'W',
            'latitude': -1.00005,
            'longitude': -1.0001,
            })

    def test_wgs84_human_readable_blank(self):
        body = "!W  !"
        parsed = {'latitude': 1, 'longitude': 2}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, '')
        self.assertEqual(parsed, {
            'daodatumbyte': 'W',
            'latitude': 1,
            'longitude': 2,
            })

    def test_wgs84_base91(self):
        body = "!w??!"
        parsed = {'latitude': 0, 'longitude': 0}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, '')
        self.assertEqual(parsed, {
            'daodatumbyte': 'W',
            'latitude': 5.4945054945054945e-05,
            'longitude': 5.4945054945054945e-05,
            })

    def test_wgs84_base91_blank(self):
        body = "!w  !"
        parsed = {'latitude': 1, 'longitude': 2}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, '')
        self.assertEqual(parsed, {
            'daodatumbyte': 'W',
            'latitude': 1,
            'longitude': 2,
            })

    def test_dao_matching(self):
        body = "aaa!W12!bbb!W23!ccc!W45!ddd"
        parsed = {'latitude': 0, 'longitude': 0}
        remaining = parse_dao(body, parsed)

        self.assertEqual(remaining, 'aaa!W12!bbb!W23!cccddd')

    def test_other_datum_bytes(self):
        for datum in [chr(x) for x in range(0x21,0x7c)]:
            body = "!" + datum + "  !"
            parsed = {'latitude': 1, 'longitude': 2}
            remaining = parse_dao(body, parsed)

            self.assertEqual(remaining, '')
            self.assertEqual(parsed, {
                'daodatumbyte': datum.upper(),
                'latitude': 1,
                'longitude': 2,
                })
