import std/[logging, os, strutils]
import signal
import jacket

var
    jclient: Client
    midiPort: Port
    midiEventChan: Channel[MidiEventT]
    midiEventPrinter: Thread[void]
    status: cint
    exitSignalled: bool = false

var log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)


proc cleanup() =
    debug "Cleaning up..."

    if jclient != nil:
        debug "Deactivating JACK client..."
        jclient.deactivate()

    if midiEventPrinter.running:
        debug "Stopping MIDI event printer thread..."
        # Receiving an invalid event causes receiving thread to wake up and
        # break its endless loop
        let event = MidiEventT(size: 0)
        midiEventChan.send(event)

    midiEventPrinter.joinThread()

    debug "Closing MIDI event channel..."
    midiEventChan.close()

    if jclient != nil:
        debug "Closing JACK client..."
        jclient.clientClose()
        jclient = nil

    debug "Bye."

proc errorCb(msg: cstring) {.cdecl.} =
    # Suppress verbose JACK error messages when server is not available by
    # default. Pass ``lvlAll`` when creating the logger to enable them.
    debug "JACK error: " & $msg

proc signalCb(sig: cint) {.noconv.} =
    debug "Received signal: " & $sig
    exitSignalled = true

proc shutdownCb(arg: pointer = nil) {.cdecl.} =
    warn "JACK server has shut down."
    exitSignalled = true

proc midiEventPrinterProc() =
    while true:
        let event = midiEventChan.recv()

        if event.size == 0:
            break
        elif event.size <= 3:
            for i in 0..<event.size:
                stdout.write(event.buffer[i].toHex)
                stdout.write("h ")

            stdout.write("\n")
            stdout.flushFile()

proc processCb*(nFrames: NFrames, arg: pointer): cint {.cdecl.} =
    var event: MidiEventT
    let inbuf = portGetBuffer(midiPort, nFrames)
    let count = midiGetEventCount(inbuf)

    for i in 0..<count:
        if midiEventGet(event.addr, inbuf, i.uint32) == 0:
            if not midiEventChan.trySend(event):
                warn "MIDI event channel overflow!"

proc main() =
    addHandler(log)

    # Create JACK client
    setErrorFunction(errorCb)
    jclient = clientOpen("jacket_midi_print", NoStartServer or UseExactName, status.addr)
    debug "JACK server status: " & $status

    if jclient == nil:
        error getJackStatusErrorString(status)
        quit QuitFailure

    # Set up signal handlers to clean up on exit
    when defined(windows):
        setSignalProc(signalCb, SIGABRT, SIGINT, SIGTERM)
    else:
        setSignalProc(signalCb, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM)

    # Set up a thread, which receives MIDI events from process callback via a
    # Channel and prints them without danger of blocking the process callback
    midiEventChan.open()
    createThread(midiEventPrinter, midiEventPrinterProc)

    # Register JACK callbacks
    if jclient.setProcessCallback(processCb) != 0:
        error "Could not set JACK process callback function."
        cleanup()
        quit QuitFailure

    jclient.onShutdown(shutdownCb)

    # Create output port
    midiPort = jclient.portRegister("midi_in", JackDefaultMidiType, PortIsInput, 0)

    # Activate JACK client ...
    if jclient.activate() == 0:
        # ... and keep running until a signal is received
        while not exitSignalled:
            sleep(200)

    cleanup()


when isMainModule:
    main()