#!/usr/bin/env python3
# #!/bin/env PYTHONUNBUFFERED="yes" python
"""
Subscribe an e-link and output to file.

Usage:
    felix-elink2file [options] <local_hostname> <fid> <file>

Options:
    -b, --bus-dir DIRECTORY         Directory for the bus [default: ./bus]
    -g, --bus-groupname GROUP_NAME  Group name to use [default: FELIX]
    -v, --verbose                   Verbose output
    --error-out=fifo                (NI) Write error information to a UNIX FIFO

Arguments:
    <local_hostname>    Internet hostname to subscribe from
    <fid>               FID to subscribe to
    <file>              File to write to
"""
import functools
import os
import time
import sys

print = functools.partial(print, flush=True)

# add the ../python path to find felix python libs
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'python'))

from docopt import docopt  # noqa: E402

from netio_py import ffi, lib  # noqa: E402

from libfelix_bus_py import FelixBus, FelixBusInfo  # noqa: E402

from felix.felix_fifo_util import FelixFifoUtil  # noqa: E402

from felixtag import FELIX_TAG


@ffi.def_extern()
def on_subscribe_msg_received(netio_socket, fid, data, size):
    """On Msg Received."""
    global pipe

    if verbose:
        print("on_msg_received", hex(fid), size)

    buffer = ffi.buffer(data, size)
    written = pipe.write(bytes(buffer))
    pipe.flush()
    os.fsync(pipe.fileno())
    assert written == size, "Written size %d not equal to datasize %d" % (written, size)


@ffi.def_extern()
def on_subscribe_connection_closed(socket):
    """On Connection Closed."""
    global pipe, timer

    if verbose:
        print("connection to publisher closed")
    lib.netio_timer_start_s(timer, 1)


@ffi.def_extern()
def on_subscribe_connection_established(socket):
    """On Connection Established."""
    global timer

    if verbose:
        print("connection from remote")
    lib.netio_timer_stop(timer)


@ffi.def_extern()
def on_subscribe_error_connection_refused(socket):
    """On Error Connectiorn Refused."""
    if verbose:
        print("connection refused for subscribing")


@ffi.def_extern()
def on_timer(ptr):
    """On Timer."""
    global socket
    if verbose:
        print("netio subscribe")
    lib.netio_subscribe(socket, fid)


@ffi.def_extern()
def on_init(data):
    """On Init."""
    if verbose:
        print("on_init")

if __name__ == "__main__":
    """elink2file."""
    global context, evloop

    args = docopt(__doc__, version=sys.argv[0] + " " + FELIX_TAG)

    bus_dir = args["--bus-dir"]
    bus_groupname = args["--bus-groupname"]
    verbose = args['--verbose']

    # FIXME: not yet used
    error = FelixFifoUtil(args['--error-out']) if args['--error-out'] else None

    local_hostname = args['<local_hostname>']
    fid = int(args['<fid>'], 0)
    out_file = args['<file>']

    bus = FelixBus()
    bus.set_path(bus_dir)
    bus.set_groupname(bus_groupname)
    bus.set_verbose(verbose)

    # FIXME: keep trying with timeout just like subscribe to netio
    print("sleeping 2s...")
    time.sleep(2)

    try:
        info = bus.get_info(fid)
        print("bus:", info.ip, info.port, info.unbuffered, info.pubsub, info.netio_pages, info.netio_pagesize)
    except RuntimeError:
        print("ERROR - fid could not be found on bus", hex(fid))
        exit(1)

    if info.unbuffered:
        print("ERROR - Handling of unbuffered elink2file not implemented", hex(fid))
        exit(1)

    pipe = None

    if verbose:
        print("netio_context")
    context = ffi.new("struct netio_context *")

    if verbose:
        print("netio_init")
    lib.netio_init(context)

    if verbose:
        print("netio_eventloop and cb")
    evloop = ffi.addressof(context.evloop)
    evloop.cb_init = lib.on_init

    if verbose:
        print("Open file")
    pipe = open(out_file, 'wb')

    if verbose:
        print("netio socket setup")
    attr = ffi.new("struct netio_buffered_socket_attr *")
    attr.num_pages = info.netio_pages
    attr.pagesize = info.netio_pagesize * 1024
    attr.watermark = int(info.netio_pagesize * 0.7 * 1024)
    socket = ffi.new("struct netio_subscribe_socket *")

    if verbose:
        print("netio socket init")
    lib.netio_subscribe_socket_init(socket, context, attr, local_hostname.encode("ascii"), info.ip.encode("ascii"), int(info.port))
    socket.cb_connection_closed = lib.on_subscribe_connection_closed
    socket.cb_connection_established = lib.on_subscribe_connection_established
    socket.cb_error_connection_refused = lib.on_subscribe_error_connection_refused
    socket.cb_msg_received = lib.on_subscribe_msg_received

    if verbose:
        print("netio timer setup")
    timer = ffi.new("struct netio_timer *")

    if verbose:
        print("netio timer init")
    lib.netio_timer_init(evloop, timer)
    timer.cb = lib.on_timer
    lib.netio_timer_start_s(timer, 1)

    # run loop
    lib.netio_run(evloop)

    pipe.close()

    print("Never Ends")
