/*
Calf Box, an open source musical instrument.
Copyright (C) 2010-2013 Krzysztof Foltman

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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "dom.h"
#include "engine.h"
#include "io.h"
#include "midi.h"
#include "module.h"
#include "rt.h"
#include "stm.h"
#include "seq.h"
#include "song.h"
#include <assert.h>
#include <stdio.h>
#include <unistd.h>

CBOX_CLASS_DEFINITION_ROOT(cbox_rt)

static void cbox_rt_process(void *user_data, struct cbox_io *io, uint32_t nframes);

struct cbox_rt_cmd_instance
{
    struct cbox_rt_cmd_definition *definition;
    void *user_data;
    int *completed_ptr; // for synchronous commands only
};

static gboolean cbox_rt_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
    struct cbox_rt *rt = ct->user_data;
    if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
    {
        if (!cbox_check_fb_channel(fb, cmd->command, error))
            return FALSE;
        if (rt->io)
        {
            GError *cerror = NULL;
            if (cbox_io_get_disconnect_status(rt->io, &cerror))
            {
                return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, rt->io->io_env.input_count, rt->io->io_env.output_count) &&
                    cbox_execute_on(fb, NULL, "/state", "is", error, 1, "OK") &&
                    CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
            }
            else
            {
                return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, rt->io->io_env.input_count, rt->io->io_env.output_count) &&
                    cbox_execute_on(fb, NULL, "/state", "is", error, -1, cerror ? cerror->message : "Unknown error") &&
                    CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
            }
        }
        else
            return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, 0, 2) &&
                cbox_execute_on(fb, NULL, "/state", "is", error, 0, "Offline") &&
                CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
    }
    else if (!strcmp(cmd->command, "/cycle") && !strcmp(cmd->arg_types, ""))
    {
        if (rt->io && !cbox_io_get_disconnect_status(rt->io, NULL))
        {
            return cbox_io_cycle(rt->io, fb, error);
        }
        else
        {
            if (rt->io)
                g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Already connected");
            else
                g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot cycle connection in off-line mode");
            return FALSE;
        }
    }
    else if (!strcmp(cmd->command, "/flush") && !cmd->arg_types[0]) {
        cbox_rt_handle_cmd_queue(rt);
        return TRUE;
    }
    else if (!strncmp(cmd->command, "/engine/", 8))
        return cbox_execute_sub(&rt->engine->cmd_target, fb, cmd, cmd->command + 7, error);
    else
        return cbox_object_default_process_cmd(ct, fb, cmd, error);
}

struct cbox_rt *cbox_rt_new(struct cbox_document *doc)
{
    struct cbox_rt *rt = malloc(sizeof(struct cbox_rt));
    CBOX_OBJECT_HEADER_INIT(rt, cbox_rt, doc);
    rt->rb_execute = cbox_fifo_new(sizeof(struct cbox_rt_cmd_instance) * RT_CMD_QUEUE_ITEMS);
    rt->rb_cleanup = cbox_fifo_new(sizeof(struct cbox_rt_cmd_instance) * RT_CMD_QUEUE_ITEMS * 2);
    rt->io = NULL;
    rt->engine = NULL;
    rt->started = FALSE;
    rt->disconnected = FALSE;
    rt->io_env.srate = 0;
    rt->io_env.buffer_size = 0;
    rt->io_env.input_count = 0;
    rt->io_env.output_count = 0;
    
    cbox_command_target_init(&rt->cmd_target, cbox_rt_process_cmd, rt);
    CBOX_OBJECT_REGISTER(rt);
    cbox_document_set_service(doc, "rt", &rt->_obj_hdr);
    
    return rt;
}

struct cbox_objhdr *cbox_rt_newfunc(struct cbox_class *class_ptr, struct cbox_document *doc)
{
    return NULL;
}

void cbox_rt_destroyfunc(struct cbox_objhdr *obj_ptr)
{
    struct cbox_rt *rt = (void *)obj_ptr;
    cbox_fifo_destroy(rt->rb_execute);
    cbox_fifo_destroy(rt->rb_cleanup);

    free(rt);
}

static void cbox_rt_on_disconnected(void *user_data)
{
    struct cbox_rt *rt = user_data;
    rt->disconnected = TRUE;
}

static void cbox_rt_on_reconnected(void *user_data)
{
    struct cbox_rt *rt = user_data;
    rt->disconnected = FALSE;
}

static void cbox_rt_on_midi_outputs_changed(void *user_data)
{
    struct cbox_rt *rt = user_data;
    if (rt->engine)
    {
        cbox_engine_update_song_playback(rt->engine);
        cbox_engine_update_output_connections(rt->engine);
    }
}

static void cbox_rt_on_midi_inputs_changed(void *user_data)
{
    struct cbox_rt *rt = user_data;
    if (rt->engine)
        cbox_engine_update_input_connections(rt->engine);
}

static void cbox_rt_get_transport_data(void *user_data, gboolean explicit_pos, uint32_t time_samples, struct cbox_transport_position *tp)
{
    struct cbox_rt *rt = user_data;
    if (rt->engine)
    {
        struct cbox_master *master = rt->engine->master;
        uint32_t time_ppqn;
        if (explicit_pos)
            time_ppqn = cbox_master_samples_to_ppqn(master, time_samples);
        else
            time_ppqn = master->spb ? master->spb->song_pos_ppqn : 0;
        struct cbox_bbt bbt;
        struct cbox_master_track_item mti;
        cbox_master_ppqn_to_bbt(master, &bbt, time_ppqn, &mti);
        tp->bar = bbt.bar;
        tp->beat = bbt.beat;
        tp->tick = bbt.tick;
        tp->ticks_per_beat = master->ppqn_factor * 4 / mti.timesig_denom;
        tp->bar_start_tick = 0;
        tp->timesig_num = mti.timesig_num;
        tp->timesig_denom = mti.timesig_denom;
        tp->tempo = mti.tempo;
    }
}

void cbox_rt_on_update_io_env(struct cbox_rt *rt)
{
    if (rt->engine)
    {
        cbox_io_env_copy(&rt->engine->io_env, &rt->io_env);
        cbox_master_set_sample_rate(rt->engine->master, rt->io_env.srate);
    }
}

void cbox_rt_set_io(struct cbox_rt *rt, struct cbox_io *io)
{
    assert(!rt->started);
    rt->io = io;
    if (io)
    {
        cbox_io_env_copy(&rt->io_env, &io->io_env);
        cbox_rt_on_update_io_env(rt);
    }
    else
    {
        cbox_io_env_clear(&rt->io_env);
    }
}

void cbox_rt_set_offline(struct cbox_rt *rt, int sample_rate, int buffer_size)
{
    assert(!rt->started);
    rt->io = NULL;
    rt->io_env.srate = sample_rate;
    rt->io_env.buffer_size = buffer_size;
    rt->io_env.input_count = 0;
    rt->io_env.output_count = 2;
    cbox_rt_on_update_io_env(rt);
}

void cbox_rt_on_started(void *user_data)
{
    struct cbox_rt *rt = user_data;
    rt->started = 1;
}

void cbox_rt_on_stopped(void *user_data)
{
    struct cbox_rt *rt = user_data;
    rt->started = 0;
}

gboolean cbox_rt_on_transport_sync(void *user_data, enum cbox_transport_state state, uint32_t frame)
{
    struct cbox_rt *rt = user_data;
    if (!rt->engine)
        return TRUE;
    return cbox_engine_on_transport_sync(rt->engine, state, frame);
}

gboolean cbox_rt_on_tempo_sync(void *user_data, double tempo)
{
    struct cbox_rt *rt = user_data;
    if (rt->engine)
        cbox_engine_on_tempo_sync(rt->engine, tempo);
    return TRUE;
}

void cbox_rt_start(struct cbox_rt *rt, struct cbox_command_target *fb)
{
    if (rt->io)
    {
        rt->cbs = calloc(1, sizeof(struct cbox_io_callbacks));
        rt->cbs->user_data = rt;
        rt->cbs->process = cbox_rt_process;
        rt->cbs->on_started = cbox_rt_on_started;
        rt->cbs->on_stopped = cbox_rt_on_stopped;
        rt->cbs->on_disconnected = cbox_rt_on_disconnected;
        rt->cbs->on_reconnected = cbox_rt_on_reconnected;
        rt->cbs->on_midi_inputs_changed = cbox_rt_on_midi_inputs_changed;
        rt->cbs->on_midi_outputs_changed = cbox_rt_on_midi_outputs_changed;
        rt->cbs->on_transport_sync = cbox_rt_on_transport_sync;
        rt->cbs->on_tempo_sync = cbox_rt_on_tempo_sync;
        rt->cbs->get_transport_data = cbox_rt_get_transport_data;

        assert(!rt->started);
        cbox_io_start(rt->io, rt->cbs, fb);
        assert(rt->started);
    }
}

void cbox_rt_stop(struct cbox_rt *rt)
{
    if (rt->io)
    {
        assert(rt->started);
        cbox_io_stop(rt->io);
        free(rt->cbs);
        rt->cbs = NULL;
        assert(!rt->started);
    }
}

void cbox_rt_handle_cmd_queue(struct cbox_rt *rt)
{
    struct cbox_rt_cmd_instance cmd;
    
    while(cbox_fifo_read_atomic(rt->rb_cleanup, &cmd, sizeof(cmd)))
    {
        assert(!cmd.completed_ptr);
        cmd.definition->cleanup(cmd.user_data);
    }
}

static void wait_write_space(struct cbox_fifo *rb)
{
    int t = 0;
    while (cbox_fifo_writespace(rb) < sizeof(struct cbox_rt_cmd_instance))
    {
        // wait until some space frees up in the execute queue
        usleep(1000);
        t++;
        if (t >= 1000)
        {
            fprintf(stderr, "Execute queue full, waiting...\n");
            t = 0;
        }
    }
}

void cbox_rt_execute_cmd_sync(struct cbox_rt *rt, struct cbox_rt_cmd_definition *def, void *user_data)
{
    struct cbox_rt_cmd_instance cmd;
    
    if (def->prepare)
        if (def->prepare(user_data))
            return;
        
    // No realtime thread - do it all in the main thread
    if (!rt || !rt->started || rt->disconnected)
    {
        while (!def->execute(user_data))
            ;
        if (def->cleanup)
            def->cleanup(user_data);
        return;
    }
    
    int completed = 0;
    memset(&cmd, 0, sizeof(cmd));
    cmd.definition = def;
    cmd.user_data = user_data;
    cmd.completed_ptr = &completed;
    
    wait_write_space(rt->rb_execute);
    cbox_fifo_write_atomic(rt->rb_execute, &cmd, sizeof(cmd));
    do
    {
        if (completed)
        {
            if (def->cleanup)
                def->cleanup(user_data);
            break;
        }
        struct cbox_rt_cmd_instance cmd2;
        
        if (!cbox_fifo_read_atomic(rt->rb_cleanup, &cmd2, sizeof(cmd2)))
        {
            // still no result in cleanup queue - wait
            usleep(10000);
            continue;
        }
        // async command or something from outer layer - clean it up
        cmd2.definition->cleanup(cmd2.user_data);
    } while(1);
}

int cbox_rt_execute_cmd_async(struct cbox_rt *rt, struct cbox_rt_cmd_definition *def, void *user_data)
{
    struct cbox_rt_cmd_instance cmd = { def, user_data, NULL };
    
    if (def->prepare)
    {
        int error = def->prepare(user_data);
        if (error)
            return error;
    }
    // No realtime thread - do it all in the main thread
    if (!rt || !rt->started || rt->disconnected)
    {
        while (!def->execute(user_data))
            ;
        if (def->cleanup)
            def->cleanup(user_data);
    } else {
        wait_write_space(rt->rb_execute);
        cbox_fifo_write_atomic(rt->rb_execute, &cmd, sizeof(cmd));
        // will be cleaned up by next sync call or by cbox_rt_cmd_handle_queue
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////

void cbox_rt_process(void *user_data, struct cbox_io *io, uint32_t nframes)
{
    struct cbox_rt *rt = user_data;
    if (rt->engine)
        cbox_engine_process(rt->engine, io, nframes, io->output_buffers, io->io_env.output_count);
    else
        cbox_rt_handle_rt_commands(rt);
}

////////////////////////////////////////////////////////////////////////////////////////

void cbox_rt_handle_rt_commands(struct cbox_rt *rt)
{
    struct cbox_rt_cmd_instance cmd;

    // Process command queue, needs engine's MIDI aux buf to be initialised to work
    int cost = 0;
    while(cost < RT_MAX_COST_PER_CALL && cbox_fifo_peek(rt->rb_execute, &cmd, sizeof(cmd)))
    {
        int result = (cmd.definition->execute)(cmd.user_data);
        if (!result)
            break;
        cost += result;
        cbox_fifo_consume(rt->rb_execute, sizeof(cmd));
        if (cmd.completed_ptr)
            *cmd.completed_ptr = 1;
        else if (cmd.definition->cleanup)
        {
            gboolean success = cbox_fifo_write_atomic(rt->rb_cleanup, (const char *)&cmd, sizeof(cmd));
            if (!success)
                g_error("Clean-up FIFO full. Main thread deadlock?");
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////

#define cbox_rt_swap_pointers_args(ARG) ARG(void **, ptr) ARG(void *, new_value)

DEFINE_RT_FUNC(void *, cbox_rt, rt, cbox_rt_swap_pointers)
{
    void *old_value = *ptr;
    *ptr = new_value;
    return old_value;
}

#define cbox_rt_swap_pointers_into_args(ARG) ARG(void **, ptr) ARG(void *, new_value) ARG(void **, old_value_p)

DEFINE_RT_VOID_FUNC(cbox_rt, rt, cbox_rt_swap_pointers_into)
{
    *old_value_p = *ptr;
    *ptr = new_value;
}

#define cbox_rt_swap_pointers_and_update_count_args(ARG) ARG(void **, ptr) ARG(void *, new_value) ARG(uint32_t *, pcount) ARG(uint32_t, new_count)

DEFINE_RT_FUNC(void *, cbox_rt, rt, cbox_rt_swap_pointers_and_update_count)
{
    void *old_value = *ptr;
    *ptr = new_value;
    if (pcount)
        *pcount = new_count;
    return old_value;
}

////////////////////////////////////////////////////////////////////////////////////////

void cbox_rt_array_insert(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, int index, void *new_value)
{
    assert(index >= -1);
    assert(index == -1 || (uint32_t)index <= *pcount);
    assert(*pcount < (1U << 31));
    void **new_array = stm_array_clone_insert(*ptr, *pcount, index, new_value);
    free(cbox_rt_swap_pointers_and_update_count(rt, (void **)ptr, new_array, pcount, *pcount + 1));            
}

void *cbox_rt_array_remove(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, int index)
{
    if (index == -1)
        index = *pcount - 1;
    assert(index >= 0);
    assert((uint32_t)index < *pcount);
    assert(*pcount < (1U << 31));
    void *p = (*ptr)[index];
    void **new_array = stm_array_clone_remove(*ptr, *pcount, index);
    free(cbox_rt_swap_pointers_and_update_count(rt, (void **)ptr, new_array, pcount, *pcount - 1));            
    return p;
}

gboolean cbox_rt_array_remove_by_value(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, void *value_to_remove)
{
    for (uint32_t i = 0; i < *pcount; i++)
    {
        if ((*ptr)[i] == value_to_remove)
        {
            cbox_rt_array_remove(rt, ptr, pcount, i);
            return TRUE;
        }
    }
    return FALSE;
}

////////////////////////////////////////////////////////////////////////////////////////

struct cbox_midi_merger *cbox_rt_get_midi_output(struct cbox_rt *rt, struct cbox_uuid *uuid)
{
    if (rt->engine)
    {
        struct cbox_midi_merger *merger = cbox_engine_get_midi_output(rt->engine, uuid);
        if (merger)
            return merger;
    }
    if (!rt->io)
        return NULL;
    
    struct cbox_midi_output *midiout = cbox_io_get_midi_output(rt->io, NULL, uuid);    
    return midiout ? &midiout->merger : NULL;
}

////////////////////////////////////////////////////////////////////////////////////////

