/*
Copyright (C) 2011 Devin Anderson

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <cassert>
#include <memory>

#include "JackALSARawMidiOutputPort.h"
#include "JackError.h"

using Jack::JackALSARawMidiOutputPort;

JackALSARawMidiOutputPort::JackALSARawMidiOutputPort(const char* client_name,
                                                     snd_rawmidi_info_t *info,
                                                     size_t index,
                                                     size_t max_bytes_per_poll,
                                                     size_t max_bytes,
                                                     size_t max_messages):
    JackALSARawMidiPort(client_name, info, index, POLLOUT)
{
    alsa_event = 0;
    read_queue = new JackMidiBufferReadQueue();
    std::unique_ptr<JackMidiBufferReadQueue> read_ptr(read_queue);
    send_queue = new JackALSARawMidiSendQueue(rawmidi, max_bytes_per_poll);
    std::unique_ptr<JackALSARawMidiSendQueue> send_ptr(send_queue);
    thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
    std::unique_ptr<JackMidiAsyncQueue> thread_ptr(thread_queue);
    raw_queue = new JackMidiRawOutputWriteQueue(send_queue, max_bytes,
                                                max_messages, max_messages);
    thread_ptr.release();
    send_ptr.release();
    read_ptr.release();
}

JackALSARawMidiOutputPort::~JackALSARawMidiOutputPort()
{
    delete raw_queue;
    delete read_queue;
    delete send_queue;
    delete thread_queue;
}

bool
JackALSARawMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
                                       jack_nframes_t frames)
{
    read_queue->ResetMidiBuffer(port_buffer);
    bool enqueued = false;
    for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
         event = read_queue->DequeueEvent()) {
        switch (thread_queue->EnqueueEvent(event, frames)) {
        case JackMidiWriteQueue::BUFFER_FULL:
            jack_error("JackALSARawMidiOutputPort::ProcessJack - The thread "
                       "queue doesn't have enough room to enqueue a %d-byte "
                       "event.  Dropping event.", event->size);
            continue;
        case JackMidiWriteQueue::BUFFER_TOO_SMALL:
            jack_error("JackALSARawMidiOutputPort::ProcessJack - The thread "
                       "queue is too small to enqueue a %d-byte event.  "
                       "Dropping event.", event->size);
            continue;
        default:
            enqueued = true;
        }
    }
    return enqueued ? TriggerQueueEvent() : true;
}

bool
JackALSARawMidiOutputPort::ProcessPollEvents(bool handle_output, bool timeout,
                                             jack_nframes_t *frame)
{
    int io_event;
    int queue_event;
    send_queue->ResetPollByteCount();
    if (! handle_output) {
        assert(timeout);
        goto process_raw_queue;
    }
    io_event = GetIOPollEvent();
    if (io_event == -1) {
        return false;
    }
    queue_event = GetQueuePollEvent();
    if (queue_event == -1) {
        return false;
    }
    if (io_event || timeout) {
    process_raw_queue:
        // We call the 'Process' event early because there are events waiting
        // to be processed that either need to be sent now, or before now.
        raw_queue->Process();
    } else if (! queue_event) {
        return true;
    }
    if (! alsa_event) {
        alsa_event = thread_queue->DequeueEvent();
    }
    for (; alsa_event; alsa_event = thread_queue->DequeueEvent()) {
        switch (raw_queue->EnqueueEvent(alsa_event)) {
        case JackMidiWriteQueue::BUFFER_TOO_SMALL:
            jack_error("JackALSARawMidiOutputPort::ProcessQueues - The raw "
                       "output queue couldn't enqueue a %d-byte event.  "
                       "Dropping event.", alsa_event->size);
            // Fallthrough on purpose.
        case JackMidiWriteQueue::OK:
            continue;
        default:
            ;
        }

        // Try to free up some space by processing events early.
        *frame = raw_queue->Process();

        switch (raw_queue->EnqueueEvent(alsa_event)) {
        case JackMidiWriteQueue::BUFFER_FULL:
            goto set_io_events;
        case JackMidiWriteQueue::BUFFER_TOO_SMALL:
            // This shouldn't happen.
            assert(false);
        default:
            ;
        }
    }
    *frame = raw_queue->Process();
 set_io_events:
    bool blocked = send_queue->IsBlocked();
    SetIOEventsEnabled(blocked);
    if (blocked) {
        *frame = 0;
    }
    return true;
}
