/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2009, 2010 Wincent Balin
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#include "pdbox.h"

#include "PDa/src/m_pd.h"
#include "PDa/src/s_stuff.h"

/* Welcome to the PDBox plugin */
PLUGIN_HEADER
PLUGIN_IRAM_DECLARE

/* Name of the file to open. */
char* filename;

/* Running time. */
uint64_t runningtime IBSS_ATTR = 0;

/* Variables for Pure Data. */
int sys_verbose;
int sys_noloadbang;
t_symbol *sys_libdir;
t_namelist *sys_openlist;
int sys_soundindevlist[MAXAUDIOINDEV];
int sys_chinlist[MAXAUDIOINDEV];
int sys_soundoutdevlist[MAXAUDIOOUTDEV];
int sys_choutlist[MAXAUDIOOUTDEV];
t_binbuf* inbinbuf;

/* References for scheduler variables and functions. */
extern t_time sys_time;
extern t_time sys_time_per_dsp_tick;
extern void sched_tick(t_time next_sys_time);

/* LATER consider making this variable.  It's now the LCM of all sample
rates we expect to see: 32000, 44100, 48000, 88200, 96000. */
#define TIMEUNITPERSEC (32.*441000.)


/* Quit flag. */
bool quit IBSS_ATTR = false;

/* Stack sizes for threads. */
#define CORESTACKSIZE (1 * 1024 * 1024)
#define GUISTACKSIZE (512 * 1024)

/* Thread stacks. */
void* core_stack IBSS_ATTR;
void* gui_stack IBSS_ATTR;

/* Thread IDs. */
unsigned int core_thread_id IBSS_ATTR;
unsigned int gui_thread_id IBSS_ATTR;


/* GUI thread */
void gui_thread(void)
{
    struct pd_widget widget[128];
    unsigned int widgets = 0;
    struct datagram dg;
    bool update;

    /* Initialize GUI. */
    pd_gui_init();

    /* Load PD patch. */
    widgets = pd_gui_load_patch(widget,
                                sizeof(widget) / sizeof(struct pd_widget));

    /* Draw initial state of UI. */
    pd_gui_draw(widget, widgets);

    /* GUI loop */
    while(!quit)
    {
        /* Reset update flag. */
        update = false;

        /* Apply timer to widgets. */
        update |=
        pd_gui_apply_timeouts(widget, widgets);

        /* Process buttons. */
        update |=
        pd_gui_parse_buttons(widgets);

        /* Receive and parse datagrams. */
        while(RECEIVE_FROM_CORE(&dg))
        {
            update = true;
            pd_gui_parse_message(&dg, widget, widgets);
        }

        /* If there is something to update in GUI, do so. */
        if(update)
            pd_gui_draw(widget, widgets);

        rb->sleep(1);
    }

    rb->thread_exit();
}

/* Core thread */
void core_thread(void)
{
    /* Add the directory the called .pd file resides in to lib directories. */
    sys_findlibdir(filename);

    /* Open the PD design file. */
    sys_openlist = namelist_append(sys_openlist, filename);

    /* Fake a GUI start. */
    sys_startgui(NULL);

    /* Core scheduler loop */
    while(!quit)
    {
        /* Receive datagrams. */
        struct datagram dg;

        while(RECEIVE_TO_CORE(&dg))
            rockbox_receive_callback(&dg);

        /* Use sys_send_dacs() function as timer. */
        while(sys_send_dacs() != SENDDACS_NO)
            sched_tick(sys_time + sys_time_per_dsp_tick);

        rb->sleep(1);
    }

    rb->thread_exit();
}



/* Plug-in entry point */
enum plugin_status plugin_start(const void* parameter)
{
    PLUGIN_IRAM_INIT(rb)

    /* Memory pool variables. */
    size_t mem_size;
    void* mem_pool;

    /* Get the file name; check whether parameter contains no file name. */
    filename = (char*) parameter;
    if(strlen(filename) == 0)
    {
        rb->splash(HZ, "Play a .pd file!");
        return PLUGIN_ERROR;
    }

    /* Initialize memory pool. */
    mem_pool = rb->plugin_get_audio_buffer(&mem_size);
    if(mem_size < MIN_MEM_SIZE)
    {
        rb->splash(HZ, "Not enough memory!");
        return PLUGIN_ERROR;
    }

    init_memory_pool(mem_size, mem_pool);

    /* Initialize net. */
    net_init();

    /* Initialize Pure Data, as does sys_main in s_main.c */
    pd_init();

    /* Set audio API. */
    sys_set_audio_api(API_ROCKBOX);

    /* Initialize audio subsystem. */
    sys_open_audio(0, /* No sound input yet */
                   sys_soundindevlist,
                   0, /* No sound input yet */
                   sys_chinlist,
                   1, /* One sound output device */
                   sys_soundoutdevlist,
                   -1, /* Use the default amount (2) of channels */
                   sys_choutlist,
                   PD_SAMPLERATE, /* Sample rate */
                   DEFAULTADVANCE, /* Scheduler advance */
                   1 /* Enable */);

    /* Initialize scheduler time variables. */
    sys_time = 0;
    sys_time_per_dsp_tick = (TIMEUNITPERSEC) *
                            ((double) sys_schedblocksize) / sys_dacsr;


    /* Create stacks for threads. */
    core_stack = getbytes(CORESTACKSIZE);
    gui_stack = getbytes(GUISTACKSIZE);
    if(core_stack == NULL || gui_stack == NULL)
    {
        rb->splash(HZ, "Not enough memory!");
        return PLUGIN_ERROR;
    }

#ifdef HAVE_SCHEDULER_BOOSTCTRL
    /* Boost CPU. */
    rb->trigger_cpu_boost();
#endif

    /* Start threads. */
    core_thread_id =
        rb->create_thread(&core_thread,
                          core_stack,
                          CORESTACKSIZE,
                          0, /* FIXME Which flags? */
                          "PD core"
                          IF_PRIO(, PRIORITY_REALTIME)
                          IF_COP(, COP));

    gui_thread_id =
        rb->create_thread(&gui_thread,
                          gui_stack,
                          GUISTACKSIZE,
                          0, /* FIXME Which flags? */
                          "PD GUI"
                          IF_PRIO(, PRIORITY_USER_INTERFACE)
                          IF_COP(, CPU));

    /* If having an error creating threads, bail out. */
    if(core_thread_id == 0 || gui_thread_id == 0)
        return PLUGIN_ERROR;

    /* Main loop. */
    while(!quit)
    {
        /* Add time slice in milliseconds. */
        runningtime += (1000 / HZ);

        /* Sleep to the next time slice. */
        rb->sleep(1);
    }

    /* Wait for threads to complete. */
    rb->thread_wait(gui_thread_id);
    rb->thread_wait(core_thread_id);

#ifdef HAVE_SCHEDULER_BOOSTCTRL
    /* Unboost CPU. */
    rb->cancel_cpu_boost();
#endif

    /* Close audio subsystem. */
    sys_close_audio();

    /* Destroy net. */
    net_destroy();

    /* Clear memory pool. */
    destroy_memory_pool(mem_pool);

    return PLUGIN_OK;
}

