#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include "audio.h"
#include "audio_internal.h"

AudioPipelineStatus audio_pipeline_start(AudioPipeline *pipeline) {
    AudioPipelineStatus result = pipeline->source->start(pipeline->source);

    pipeline->status = result;
    if (pipeline->statusCallback != NULL) {
        pipeline->statusCallback(pipeline, result);
    }

    return result;
}

AudioPipelineStatus audio_pipeline_reset(AudioPipeline *pipeline) {
    AudioPipelineStatus result = pipeline->source->reset(pipeline->source);

    pipeline->status = result;
    if (pipeline->statusCallback != NULL) {
        pipeline->statusCallback(pipeline, result);
    }

    return result;
}

AudioPipelineStatus audio_pipeline_stop(AudioPipeline *pipeline) {
    AudioPipelineStatus result = pipeline->source->stop(pipeline->source);

    pipeline->status = result;
    if (pipeline->statusCallback != NULL) {
        pipeline->statusCallback(pipeline, result);
    }

    return result;
}

AudioPipeline *audio_pipeline_assemble(AudioPipelineElement *source, ...) {
    va_list list, list_backup;
    AudioPipelineElement *item, *last;
    AudioPipeline *pipeline = malloc(sizeof(AudioPipeline));
   
    pipeline->start = audio_pipeline_start;
    pipeline->reset = audio_pipeline_reset;
    pipeline->stop = audio_pipeline_stop;
    pipeline->source = source;
    pipeline->status = PipelineStopped;
    pipeline->statusCallback = NULL;

    va_start(list, source);
    va_copy(list_backup, list);

    if (source == NULL) {
        fprintf(stderr, "ERROR: Source did not initialize, giving up!\n");
        goto fail;
    }
    source->pipeline = pipeline;
    last = source;

    item = va_arg(list, AudioPipelineElement *);
    while (item != NULL) {
        if (item->type == AudioElementSource) {
            fprintf(stderr, "ERROR: You cannot have a source in the middle of the pipeline, offending element: %s\n", item->describe(item));
            goto fail;
        }
        AudioPipelineStatus result = item->link(item, last);
        if (result != PipelineStopped) {
            fprintf(stderr, "ERROR: Could not link audio pipeline elements %s and %s\n", last->describe(last), item->describe(item));
            goto fail;
        }
        item->pipeline = pipeline;
        last = item;
        if (item->type == AudioElementSink) {
            break;
        }
        item = va_arg(list, AudioPipelineElement *);
    }

    if (last->type != AudioElementSink) {
        fprintf(stderr, "ERROR: Pipeline has no sink!\n");
        goto fail;
    }
    
    pipeline->sink = last;
    va_end(list);
    va_end(list_backup);

    return pipeline;

fail:
    va_end(list);
    item = va_arg(list_backup, AudioPipelineElement *);

    while (item != NULL) {
        item->destroy(item);
        item = va_arg(list_backup, AudioPipelineElement *);
    }
    if (pipeline->source != NULL) {
        pipeline->source->destroy(pipeline->source);
    }
    free(pipeline);
    va_end(list_backup);

    return NULL;
}

void audio_pipeline_destroy(AudioPipeline *pipeline) {
    pipeline->stop(pipeline);

    AudioPipelineElement *item = pipeline->source->next;
    while (item != NULL) {
        AudioPipelineElement *next = item->next;
        item->destroy(item);
        item = next;
    }
    pipeline->source->destroy(pipeline->source);

    free(pipeline);
}

AudioBuffer *alloc_audio_buffer(uint32_t size) {
    AudioBuffer *buffer = malloc(sizeof(AudioBuffer));
    if (buffer == NULL) {
        return NULL;
    }
    buffer->buf_size = size;
    
    if (size > 0) {
        buffer->data = calloc(1, size);
        if (buffer->data == NULL) {
            free(buffer);
            return NULL;
        }
    } else {
        buffer->data = NULL;
    }
    return buffer;
}

void free_audio_buffer(AudioBuffer *buffer) {
    if (buffer == NULL) {
        return;
    }
    if (buffer->data != NULL) {
        free(buffer->data);
    }
    free(buffer);
}