"""
Sender pipeline:
Unix socket/sound source
  --- samples stream ---> [Sample Reader]
  ---  sample_queue  ---> [Packetizer]
  ---> Uni/Multicast UDP

Receiver pipeline:
Socket
  ---  UDP datagrams  ---> [Receiver]
  --- chunks/commands ---> [ChunkPlayer]
  ---> pyaudio sink stream
"""

import asyncio
import socket
import sys

from . import (
    AudioConfig,
    Packetizer,
    ChunkPlayer,
    ChunkQueue,
    SampleReader,
    time_machine,
    Receiver
)

from .cli_args import parse


def start_tx(args, loop):
    "Initialize sender"

    # Transmitted configuration
    audio_config = AudioConfig(rate=args.audio_rate,
                               sample=24 if args.audio_sample else 16,
                               channels=args.audio_channels,
                               latency_ms=args.latency_ms,
                               sink_latency_ms=args.sink_latency_ms)

    # Sound sample reader
    sample_reader = SampleReader(audio_config)
    sample_reader.payload_size = args.payload_size

    if args.local_play:
        chunk_queue = ChunkQueue()
        player = ChunkPlayer(chunk_queue,
                             receiver=None,
                             tolerance_ms=args.tolerance_ms,
                             buffer_size=args.buffer_size,
                             device_index=args.device_index,
                             use_callback=args.callback)
        play = player.chunk_player()
        asyncio.ensure_future(play)
    else:
        chunk_queue = None

    # Packet splitter / sender
    packetizer = Packetizer(sample_reader,
                            chunk_queue,
                            audio_config,
                            compress=args.compress)

    packetizer.create_socket(args.ip_list,
                             args.ttl,
                             args.multicast_loop,
                             args.broadcast)

    connection = loop.create_unix_connection(lambda: sample_reader, args.tx)

    # Start loop
    asyncio.ensure_future(packetizer.packetize())
    asyncio.ensure_future(connection)
    loop.run_forever()


def start_rx(args, loop):
    "Initialize receiver"

    # Network receiver with it's connection
    channel = args.ip_list[0]
    chunk_queue = ChunkQueue()
    receiver = Receiver(chunk_queue,
                        channel=channel,
                        sink_latency_ms=args.sink_latency_ms)

    connection = loop.create_datagram_endpoint(lambda: receiver,
                                               family=socket.AF_INET,
                                               local_addr=channel)

    # Coroutine pumping audio into PA
    player = ChunkPlayer(chunk_queue, receiver,
                         tolerance_ms=args.tolerance_ms,
                         buffer_size=args.buffer_size,
                         device_index=args.device_index,
                         use_callback=args.callback)

    play = player.chunk_player()

    tasks = asyncio.gather(connection, play)
    loop.run_until_complete(tasks)


def print_dev(p,d,verb):
    print('Index:',d['index'])
    if verb:
      for k,v in d.items():
        print('    ',k,'=',v)
    else:
      for i in ['name','hostApi','maxInputChannels','maxOutputChannels','defaultSampleRate']:
        if i=='maxInputChannels' and d[i]==0:continue
        if i=='maxOutputChannels' and d[i]==0:continue
        if i=='hostApi':
          desc=''
          if d['index']==p.get_host_api_info_by_index(d[i])['defaultInputDevice']: desc=desc+' [defaultInput]'
          if d['index']==p.get_host_api_info_by_index(d[i])['defaultOutputDevice']: desc=desc+' [defaultOutput]'
          print('    ',i,'=',d[i],'('+p.get_host_api_info_by_index(d[i])['name']+')'+desc)
        else:
          print('    ',i,'=',d[i])
      try:
        ll=d['defaultLowOutputLatency']
        lh=d['defaultHighOutputLatency']
        if not ll==-1 and not lh==-1:
          print('    ','output latency low/high: {:.1f} / {:.1f} ms'.format(ll*1000,lh*1000))
      except:
        pass


def list_devs(verb):
    import pyaudio
    p=pyaudio.PyAudio()
    print()
    print(p.get_device_count(),'devices found:')
    for i in range(p.get_device_count()):
        d=p.get_device_info_by_index(i)
        print()
        print_dev(p,d,verb)


def main():
    "Parse arguments and start the event loop"
    args = parse()

    if args.list:
        list_devs(False)
        sys.exit()
    elif args.listv:
        list_devs(True)
        sys.exit()

    loop = asyncio.get_event_loop()

    if args.debug:
        loop.set_debug(True)

    if not args.ntp == '':
        time_machine.ntp_init(args.ntp,args.ntpint,args.ntptol,args.ntpdq)

    try:
        if args.tx is not None:
            start_tx(args, loop)
        elif args.rx:
            start_rx(args, loop)
    finally:
        loop.close()
        if time_machine.ntptimer: time_machine.ntp_stoptimer()

