#!/usr/bin/env python

"""
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 __future__ import print_function, division

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 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 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 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()
