#!/usr/bin/env python3

"""
Usage:  dabtest.py [options] [SERVICE:COMPONENT...]

Arguments:
  SERVICE  service index
  COMPONENT  component index within service

Options:
  -h, --help                           Show this help message and exit
  -a ADAP_IDX, --adapter=ADAP_IDX      Adapter index [default: 0]
  -t TUNER_IDX, --tuner=TUNER_IDX      Tuner index [default: 0]
  -f FREQUENCY, --frequency=FREQUENCY  Channel frequency in kHz. [default: 180064]
  -F FILENAME, --filename=FILENAME     Filename [default: packets.dab]
  -l, --no-list-services               Dont list services and components
  -p PROGRAMME, --programme=PROGRAMME  Audio programme index. [default: 0]
  -r RUNTIME, --runtime=RUNTIME        Runtime seconds collecting packets. [default: 10]
  -s, --stop-services                  Stop services instead of starting
  -R, --reset                          Reset band/freq/prog on start
  --all                                Read data from all components in turn
"""



from audioscience import hpi
from collections import namedtuple, defaultdict, OrderedDict
from docopt import docopt
import logging
import struct
import sys
import time

from dab_data import *


def set_band(hc, band):
    logging.info('set band %d = %s', band, hpi.Tuner_SetBand(hc, band))
    while True:
        e, current = hpi.Tuner_GetBand(hc)
        if band == current:
            break
        logging.info('wait band %d' % band)
        time.sleep(1)

def setup(opts):
    adapter = opts['--adapter']
    tuner = opts['--tuner']
    freq = opts['--frequency']
    prog = opts['--programme']
    reset = opts['--reset']

    hpi.setup(0)
    hm  = hpi.raise_error(hpi.Mixer_Open(adapter))
    hc = hpi.raise_error(hpi.Mixer_GetControl(hm, hpi.SOURCENODE_TUNER, tuner, 0, 0, hpi.CONTROL_TUNER))

    if reset:
        set_band(hc, hpi.TUNER_BAND_FM)

    set_band(hc, hpi.TUNER_BAND_DAB)

    if reset:
        logging.info('set F(174000) = %s', hpi.Tuner_SetFrequency(hc, 174000))
        time.sleep(1)

    logging.info('set F(%dkHz) =  %s', freq, hpi.Tuner_SetFrequency(hc, freq))

    while True:
        e, curr, count = hpi.Tuner_GetDabAudioServiceCount(hc)
        if count:
            break
        logging.info('wait service count')
        time.sleep(1)

    logging.info('tuner firmware version: %s', hpi.Tuner_GetFirmwareVersion(hc))
    logging.info('rf level: %s', hpi.Tuner_GetRFLevel(hc))
    logging.info('get mux name: %s', hpi.Tuner_GetDabMultiplexName(hc))
    logging.info('get audio service count: %s', hpi.Tuner_GetDabAudioServiceCount(hc))

    return hc


def set_service(hc, serv, comp=0, start=True):
    try:
        hpi.Control_WaitReady(hpi.Tuner_SetDabService, hc, serv, comp, start)
    except hpi.HpiError as e:

        logging.error('Error {} {} {:#X}:{:#X}'.format(e, ['stopping', 'starting'][start], serv, comp))
        return False

    return True

component_info = struct.Struct('BxBB16sHBB')
ComponentInfo = namedtuple('ComponentInfo', 'id, lang, charset, label, abbrev, num_ua, len_ua, ua')

def read_component(hc, si, ci):
    try:
        s  = hpi.Control_WaitReady(hpi.Tuner_GetDabComponentInfo, hc, si, ci)
    except hpi.HpiError as e:
        print('\tFailed to read component info')
        return

    if not len(s):
        return None

    comp = list(component_info.unpack_from(s, 0))

    if comp[3][0] == 0:
        comp[3] = '<no label>'
    comp[3] = comp[3].strip()

    try:
        comp[3] = comp[3].decode('utf-8')
    except:
        pass

    comp.append([])
    comp = ComponentInfo._make(comp)

    num_ua = comp.num_ua
    ua_ofs = component_info.size
    for u in range(num_ua):
        uatype, uadatalen = struct.unpack_from('HB', s, ua_ofs)
        if uadatalen:
            dl = struct.unpack_from('B' * uadatalen, s, ua_ofs + 3)
        else:
            dl = tuple()
        ua = (uatype, dl)
        comp.ua.append(ua)
        ua_ofs += uadatalen + 3
        if not (uadatalen % 2):
            ua_ofs += 1  # padding

    return comp, s


service_info = struct.Struct('IBBBB16s')
ServiceInfo = namedtuple('ServiceInfo', 'sid, info1, info2, info3, num_comp, label, comps')

component_id = struct.Struct('HBB')  # contents of components list at end of service_info
ComponentId = namedtuple('ComponentId', 'cid, info, valid')


def read_service(hc, i, read_comp=True):
    e, raw_serv = hpi.Tuner_GetDabServiceInfo(hc, i)
    hdr = service_info.unpack_from(raw_serv)
    hdr = list(hdr)
    hdr[5] = hdr[5].strip()
    if hdr[5][0] == 0:
        hdr[5] = '<no label>'
    hdr[5] = hdr[5].strip()

    try:
        hdr[5] = hdr[5].decode('utf-8')
    except:
        pass

    hdr.append([])  # component list
    serv = ServiceInfo._make(hdr)

    raw_comps = []
    for c in range(serv.num_comp):
        comp = component_id.unpack_from(raw_serv, service_info.size + 4 * c)
        comp = ComponentId._make(comp)
        if read_comp:
            comp_details, cs = read_component(hc, serv.sid, comp.cid)
        else:
            comp_details = None
            cs = ''
        serv.comps.append((comp, comp_details))
        raw_comps.append((comp.cid, cs))
    return serv, (serv.sid, raw_serv, raw_comps)


def service_str(s):
    sl = ['Service id {0.sid:08X} {0.label}'.format(s)]
    for c in s.comps:
        if c[1] is None:
            sl.append('\tComponent {}'.format(c[0]))
            continue
        sl.append('\tComponent {0.cid:04X} {1.label}'.format(*c))
        for u in c[1].ua:
            sl.append('\t\tUA type {0:04X} {1}'.format(*u))
    return '\n'.join(sl)


def collect_packets(hc, interval=10, pf=None):
    start = time.time()
    poll_interval_ms = 40
    delta = 0
    packets = []
    while (delta < interval):
        try:
            packet, poll_interval_ms =  hpi.Control_WaitReady(hpi.Tuner_GetDabDataPacket, hc)
            delta = time.time() - start
        except hpi.HpiError as e:
            # logging.warning("Error {} reading packet".format(e))
            continue
        if pf:
            write_packet(pf, delta, packet)
        packets.append((delta, packet))
        print('.', end='')
        sys.stdout.flush()
        status = ord(packet[0])
        if status & 0x2:
            logging.warning("Packet loss detected")
        # Don't wait if there is more data waiting or a packet loss was detected.
        # This check should be removed once the underlying implementation of
        # Tuner_GetDabDataPacket() takes the status flags into account when
        # computing poll_interval_ms.
        if status & 0x3:
            continue
        else:
            time.sleep(poll_interval_ms/1000.0)
    print()
    return packets


def get_opts():
    opts = docopt(__doc__)
    # print(opts)
    # by default all docopt values are strings - fix this
    int_keys = '--adapter --frequency --programme --runtime --tuner'
    for k in int_keys.split():
        opts[k] = int(opts[k])
    sc = [s.split(':') for s in opts['SERVICE:COMPONENT']]
    opts['SERV_COMP'] = [(int(s, base=0), int(c, base=0)) for s,c in sc]
    # print(opts)
    return opts

def read_services(hc):
    e, curr, num_serv = hpi.Tuner_GetDabAudioServiceCount(hc)

    comp_list = []
    serv_list = []
    raw_list = []
    for si in range(num_serv):
        s, raw = read_service(hc, si, True)
        raw_list.append(raw)

        serv_list.append((s.sid, s))
        for ci, c in enumerate(s.comps):
            comp_list.append(((s.sid, c[0].cid), ((si, s), (ci, c))))

    comp_list.sort()
    components = OrderedDict(comp_list)

    serv_list.sort()
    services = OrderedDict(serv_list)

    return services, components, raw_list


def record_each_component(components, hc, opts):
    for k, v in list(components.items()):
        serv, comp = k
        filename = '{prefix}_f{f}_{k[0]:#08X}:{k[1]:#04X}.dab'.format(prefix=opts['--filename'], k=k, f=opts['--frequency'])

        print('Collecting {k[0]:#08X}:{k[1]:#04X} = {v[0][0]}:{v[1][0]} = {v[0][1].label} : {v[1][1][1].label} into {fn} '.format(k=k,v=v, fn=filename))

        if set_service(hc, serv, comp, start=True):
            with open(filename, 'wb') as pf:
                packets = collect_packets(hc, opts['--runtime'], pf)

                print('Stop', ('Fail', 'OK')[set_service(hc, serv, comp, start=False)])

        if opts['--reset']:
            hc = setup(opts)


def main():
    opts = get_opts()

    hc = setup(opts)
    services, components, raw_services = read_services(hc)

    if not opts['--no-list-services']:
        for s in list(services.values()):
            print(service_str(s))

        with open('service_list.raw', 'wb') as f:
            write_raw_service_list(f, raw_services)

    for k, v in list(components.items()):
        # print('k={}\nv={}'.format(k,v))
        print('{k[0]:#08X}:{k[1]:#04X} = {v[0][0]}:{v[1][0]} = {v[0][1].label} : {v[1][1][1].label} '
            .format(k=k,v=v))

    if opts['--all']:
        record_each_component(components, hc, opts)
        sys.exit()

    if len(opts['SERV_COMP']):
        start = not opts['--stop-services']
        for serv, comp in opts['SERV_COMP']:
            logging.info('{} {:#X}:{:#X}'.format({True: 'Start', False: 'Stop'}[start], serv, comp))
            set_service(hc, serv, comp, start)

    time.sleep(1)
    logging.info('current audio service {} {:#X}:{:#X}'.format(*hpi.Tuner_GetDabService(hc)))
    logging.info('audio info %s', hpi.Tuner_GetDabAudioInfo(hc))

    packets = []
    if opts['--runtime']:
        with open(opts['--filename'], 'wb') as pf:
            packets = collect_packets(hc, opts['--runtime'], pf)

    if packets:
        analyse_packets(packets)
    else:
        sys.exit('No packets collected')


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    main()
