/*
Calf Box, an open source musical instrument.
Copyright (C) 2010-2011 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 "config-api.h"
#include "errors.h"
#include "instr.h"
#include "layer.h"
#include "midi.h"
#include "module.h"
#include "rt.h"
#include "scene.h"
#include <glib.h>

gboolean cbox_layer_load(struct cbox_layer *layer, const char *name, GError **error)
{
    const char *cv = NULL;
    struct cbox_instrument *instr = NULL;
    gchar *section = g_strdup_printf("layer:%s", name);
    
    if (!cbox_config_has_section(section))
    {
        g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Missing section for layer %s", name);
        goto error;
    }
    
    cv = cbox_config_get_string(section, "instrument");
    if (cv)
    {
        instr = cbox_scene_get_instrument_by_name(layer->scene, cv, TRUE, error);
        if (!instr)
        {
            cbox_force_error(error);
            g_prefix_error(error, "Cannot get instrument %s for layer %s: ", cv, name);
            goto error;
        }
    }

    layer->enabled = cbox_config_get_int(section, "enabled", TRUE);
    layer->low_note = 0;
    layer->high_note = 127;
    
    cv = cbox_config_get_string(section, "low_note");
    if (cv)
        layer->low_note = note_from_string(cv);
    
    cv = cbox_config_get_string(section, "high_note");
    if (cv)
        layer->high_note = note_from_string(cv);
    
    layer->transpose = cbox_config_get_int(section, "transpose", 0);
    layer->fixed_note = cbox_config_get_int(section, "fixed_note", -1);
    layer->in_channel = cbox_config_get_int(section, "in_channel", 0) - 1;
    layer->out_channel = cbox_config_get_int(section, "out_channel", 0) - 1;
    layer->disable_aftertouch = !cbox_config_get_int(section, "aftertouch", TRUE);
    layer->invert_sustain = cbox_config_get_int(section, "invert_sustain", FALSE);
    layer->consume = cbox_config_get_int(section, "consume", FALSE);
    layer->ignore_scene_transpose = cbox_config_get_int(section, "ignore_scene_transpose", FALSE);
    layer->ignore_program_changes = cbox_config_get_int(section, "ignore_program_changes", FALSE);
    layer->external_output_set = FALSE;
    g_free(section);
    
    cbox_layer_set_instrument(layer, instr);    
    
    return 1;

error:
    if (instr)
        cbox_instrument_destroy_if_unused(instr);

    g_free(section);
    return 0;
}

void cbox_layer_set_instrument(struct cbox_layer *layer, struct cbox_instrument *instrument)
{
    if (layer->instrument)
    {
        layer->instrument->refcount--;
        cbox_instrument_destroy_if_unused(layer->instrument);
        layer->instrument = NULL;
    }
    layer->instrument = instrument;
    if (layer->instrument)
        layer->instrument->refcount++;
}

static gboolean cbox_layer_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error);

static void cbox_layer_destroyfunc(struct cbox_objhdr *objhdr)
{
    struct cbox_layer *layer = CBOX_H2O(objhdr);
    if (layer->instrument && !--(layer->instrument->refcount))
    {
        if (layer->instrument->scene)
            cbox_scene_remove_instrument(layer->instrument->scene, layer->instrument);
        
        cbox_instrument_destroy_if_unused(layer->instrument);
    }
    if (layer->external_merger) {
        cbox_midi_merger_disconnect(layer->external_merger, &layer->output_buffer, layer->scene->rt);
    }
    free(layer);
}

gboolean cbox_layer_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
    struct cbox_layer *layer = ct->user_data;
    if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
    {
        if (!cbox_check_fb_channel(fb, cmd->command, error))
            return FALSE;

        if (!(cbox_execute_on(fb, NULL, "/enable", "i", error, (int)layer->enabled) && 
            (layer->instrument
              ? cbox_execute_on(fb, NULL, "/instrument_name", "s", error, layer->instrument->module->instance_name) &&
                cbox_execute_on(fb, NULL, "/instrument_uuid", "o", error, layer->instrument)
              : (layer->external_output_set
               ? cbox_uuid_report_as(&layer->external_output, "/external_output", fb, error)
               : TRUE)) &&
            cbox_execute_on(fb, NULL, "/consume", "i", error, (int)layer->consume) && 
            cbox_execute_on(fb, NULL, "/ignore_scene_transpose", "i", error, (int)layer->ignore_scene_transpose) && 
            cbox_execute_on(fb, NULL, "/ignore_program_changes", "i", error, (int)layer->ignore_program_changes) && 
            cbox_execute_on(fb, NULL, "/disable_aftertouch", "i", error, (int)layer->disable_aftertouch) && 
            cbox_execute_on(fb, NULL, "/transpose", "i", error, (int)layer->transpose) && 
            cbox_execute_on(fb, NULL, "/fixed_note", "i", error, (int)layer->fixed_note) && 
            cbox_execute_on(fb, NULL, "/low_note", "i", error, (int)layer->low_note) && 
            cbox_execute_on(fb, NULL, "/high_note", "i", error, (int)layer->high_note) && 
            cbox_execute_on(fb, NULL, "/in_channel", "i", error, layer->in_channel + 1) && 
            cbox_execute_on(fb, NULL, "/out_channel", "i", error, layer->out_channel + 1) &&
            CBOX_OBJECT_DEFAULT_STATUS(layer, fb, error)))
            return FALSE;
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/enable") && !strcmp(cmd->arg_types, "i"))
    {
        layer->enabled = 0 != CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/consume") && !strcmp(cmd->arg_types, "i"))
    {
        layer->consume = 0 != CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/ignore_scene_transpose") && !strcmp(cmd->arg_types, "i"))
    {
        layer->ignore_scene_transpose = 0 != CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/ignore_program_changes") && !strcmp(cmd->arg_types, "i"))
    {
        layer->ignore_program_changes = 0 != CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/disable_aftertouch") && !strcmp(cmd->arg_types, "i"))
    {
        layer->disable_aftertouch = 0 != CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/transpose") && !strcmp(cmd->arg_types, "i"))
    {
        layer->transpose = CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/fixed_note") && !strcmp(cmd->arg_types, "i"))
    {
        layer->fixed_note = CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/low_note") && !strcmp(cmd->arg_types, "i"))
    {
        layer->low_note = CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/high_note") && !strcmp(cmd->arg_types, "i"))
    {
        layer->high_note = CBOX_ARG_I(cmd, 0);
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/in_channel") && !strcmp(cmd->arg_types, "i"))
    {
        layer->in_channel = CBOX_ARG_I(cmd, 0) - 1;
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/out_channel") && !strcmp(cmd->arg_types, "i"))
    {
        layer->out_channel = CBOX_ARG_I(cmd, 0) - 1;
        return TRUE;
    }
    else if (!strcmp(cmd->command, "/external_output") && !strcmp(cmd->arg_types, "s"))
    {
        if (*CBOX_ARG_S(cmd, 0))
        {
            if (cbox_uuid_fromstring(&layer->external_output, CBOX_ARG_S(cmd, 0), error)) {
                layer->external_output_set = TRUE;
            }
        }
        else {
            layer->external_output_set = FALSE;
        }
        cbox_scene_update_connected_outputs(layer->scene);
        return TRUE;
    }
    else // otherwise, treat just like an command on normal (non-aux) output
        return cbox_object_default_process_cmd(ct, fb, cmd, error);
}

CBOX_CLASS_DEFINITION_ROOT(cbox_layer)

struct cbox_layer *cbox_layer_new(struct cbox_scene *scene)
{
    struct cbox_document *doc = CBOX_GET_DOCUMENT(scene);
    struct cbox_layer *l = malloc(sizeof(struct cbox_layer));
    
    CBOX_OBJECT_HEADER_INIT(l, cbox_layer, doc);
    cbox_command_target_init(&l->cmd_target, cbox_layer_process_cmd, l);
    l->enabled = TRUE;
    l->instrument = NULL;
    l->low_note = 0;
    l->high_note = 127;
    
    l->transpose = 0;
    l->fixed_note = -1;
    l->in_channel = -1;
    l->out_channel = -1;
    l->disable_aftertouch = FALSE;
    l->invert_sustain = FALSE;
    l->consume = FALSE;
    l->ignore_scene_transpose = FALSE;
    l->ignore_program_changes = FALSE;
    l->scene = scene;
    cbox_uuid_clear(&l->external_output);
    l->external_output_set = FALSE;
    l->external_merger = NULL;
    CBOX_OBJECT_REGISTER(l);
    return l;
}

struct cbox_layer *cbox_layer_new_with_instrument(struct cbox_scene *scene, const char *instrument_name, GError **error)
{
    struct cbox_layer *layer = cbox_layer_new(scene);
    struct cbox_instrument *instr = NULL;
    if (!layer) goto error;
    instr = cbox_scene_get_instrument_by_name(scene, instrument_name, TRUE, error);
    if (!instr)
    {
        cbox_force_error(error);
        g_prefix_error(error, "Cannot get instrument %s for new layer: ", instrument_name);
        CBOX_DELETE(layer);
        return NULL;
    }
    cbox_layer_set_instrument(layer, instr);
    return layer;

error:
    CBOX_DELETE(layer);
    if (instr)
        cbox_instrument_destroy_if_unused(instr);
    return NULL;
}

struct cbox_layer *cbox_layer_new_from_config(struct cbox_scene *scene, const char *layer_name, GError **error)
{
    struct cbox_layer *layer = cbox_layer_new(scene);
    if (!layer)
        goto error;

    layer->scene = scene;
    if (!cbox_layer_load(layer, layer_name, error))
        goto error;

    return layer;

error:
    CBOX_DELETE(layer);
    return NULL;
}


