/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/ynamic    \____/niversal  /__\  /____\usic   /|  .  . ibliotheque
 *                                                      /  \
 *                                                     / .  \
 * makeduh.c - Function to construct a DUH from       / / \  \
 *             its components.                       | <  /   \_
 *                                                   |  \/ /\   /
 * By entheh.                                         \_  /  > /
 *                                                      | \ / /
 *                                                      |  ' /
 *                                                       \__/
 */

#include <stdlib.h>
#include <string.h>

#include "dumb.h"
#include "internal/dumb.h"

static DUH_SIGNAL *make_signal(DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata) {
    DUH_SIGNAL *signal;

    ASSERT((desc->start_sigrenderer && desc->end_sigrenderer) ||
           (!desc->start_sigrenderer && !desc->end_sigrenderer));
    ASSERT(desc->sigrenderer_generate_samples &&
           desc->sigrenderer_get_current_sample);

    signal = malloc(sizeof(*signal));

    if (!signal) {
        if (desc->unload_sigdata)
            if (sigdata)
                (*desc->unload_sigdata)(sigdata);
        return NULL;
    }

    signal->desc = desc;
    signal->sigdata = sigdata;

    return signal;
}

DUH *make_duh(dumb_off_t length, int n_tags, const char *const tags[][2],
              int n_signals, DUH_SIGTYPE_DESC *desc[], sigdata_t *sigdata[]) {
    DUH *duh = malloc(sizeof(*duh));
    int i;
    int fail;

    if (duh) {
        duh->n_signals = n_signals;

        duh->signal = malloc(n_signals * sizeof(*duh->signal));

        if (!duh->signal) {
            free(duh);
            duh = NULL;
        }
    }

    if (!duh) {
        for (i = 0; i < n_signals; i++)
            if (desc[i]->unload_sigdata)
                if (sigdata[i])
                    (*desc[i]->unload_sigdata)(sigdata[i]);
        return NULL;
    }

    duh->n_tags = 0;
    duh->tag = NULL;

    fail = 0;

    for (i = 0; i < n_signals; i++) {
        duh->signal[i] = make_signal(desc[i], sigdata[i]);
        if (!duh->signal[i])
            fail = 1;
    }

    if (fail) {
        unload_duh(duh);
        return NULL;
    }

    duh->length = length;

    {
        int mem = n_tags * 2; /* account for NUL terminators here */
        char *ptr;

        for (i = 0; i < n_tags; i++)
            mem += strlen(tags[i][0]) + strlen(tags[i][1]);

        if (mem <= 0)
            return duh;

        duh->tag = malloc(n_tags * sizeof(*duh->tag));
        if (!duh->tag)
            return duh;
        duh->tag[0][0] = malloc(mem);
        if (!duh->tag[0][0]) {
            free(duh->tag);
            duh->tag = NULL;
            return duh;
        }
        duh->n_tags = n_tags;
        ptr = duh->tag[0][0];
        for (i = 0; i < n_tags; i++) {
            duh->tag[i][0] = ptr;
            strcpy(ptr, tags[i][0]);
            ptr += strlen(tags[i][0]) + 1;
            duh->tag[i][1] = ptr;
            strcpy(ptr, tags[i][1]);
            ptr += strlen(tags[i][1]) + 1;
        }
    }

    return duh;
}

int duh_add_signal(DUH *duh, DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata) {
    DUH_SIGNAL **signal;

    if (!duh || !desc || !sigdata)
        return -1;

    signal = (DUH_SIGNAL **)realloc(duh->signal, (duh->n_signals + 1) *
                                                     sizeof(*duh->signal));
    if (!signal)
        return -1;
    duh->signal = signal;

    memmove(signal + 1, signal, duh->n_signals * sizeof(*signal));
    duh->n_signals++;

    signal[0] = make_signal(desc, sigdata);
    if (!signal[0])
        return -1;

    return 0;
}
