/*
 * Copyright (C) 2018 BlueKitchen GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 4. Any redistribution, use, or modification is done solely for
 *    personal benefit and not for any commercial purpose or for
 *    monetary gain.
 *
 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
 * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Please inquire about commercial licensing options at 
 * contact@bluekitchen-gmbh.com
 *
 */

#define BTSTACK_FILE__ "btstack_audio_embedded.c"

/*
 *  btstack_audio_embedded.c
 *
 *  Implementation of btstack_audio.h using hal_audio.h for embedded run loop
 *
 */

#include "btstack_config.h"

// allow to compile all files in embedded folder even if there's no audio support
#ifdef HAVE_HAL_AUDIO

#include "btstack_debug.h"
#include "btstack_audio.h"
#include "btstack_run_loop_embedded.h"
#include "hal_audio.h"

#include <stddef.h>

// client
static void (*playback_callback)(int16_t * buffer, uint16_t num_samples, const btstack_audio_context_t * context);
static void (*recording_callback)(const int16_t * buffer, uint16_t num_samples, const btstack_audio_context_t * context);

// timer to fill output ring buffer
static btstack_timer_source_t  driver_timer_sink;
static btstack_timer_source_t  driver_timer_source;

static volatile unsigned int output_buffer_to_play;
static          unsigned int output_buffer_to_fill;
static          unsigned int output_buffer_count;
static          unsigned int output_buffer_samples;

static volatile int       input_buffer_ready;
static volatile const int16_t * input_buffer_samples;
static volatile uint16_t  input_buffer_num_samples;

static btstack_data_source_t btstack_audio_embedded_sink_data_source;
static btstack_data_source_t btstack_audio_embedded_source_data_source;

static int source_active;
static int sink_active;

// Support for stereo-only audio hal
#ifdef HAVE_HAL_AUDIO_SINK_STEREO_ONLY
static bool output_duplicate_samples;
#endif

static void btstack_audio_audio_played(uint8_t buffer_index){
    output_buffer_to_play = (buffer_index + 1) % output_buffer_count;
    btstack_run_loop_poll_data_sources_from_irq();
}

static void btstack_audio_audio_recorded(const int16_t * samples, uint16_t num_samples){
    input_buffer_samples     = samples;
    input_buffer_num_samples = num_samples;
    input_buffer_ready       = 1;
    btstack_run_loop_poll_data_sources_from_irq();
}

static void btstack_audio_embedded_sink_handler(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
    UNUSED(ds);
    switch (callback_type) {
        case DATA_SOURCE_CALLBACK_POLL:
            // playback buffer ready to fill
            if (output_buffer_to_play != output_buffer_to_fill){
                int16_t * buffer = hal_audio_sink_get_output_buffer(output_buffer_to_fill);
                const btstack_audio_context_t * context = NULL;
#ifdef HAVE_HAL_AUDIO_SINK_BUFFER_CONTEXT
                context = hal_audio_sink_get_buffer_context(output_buffer_to_fill);
#endif
                (*playback_callback)(buffer, output_buffer_samples, context);

#ifdef HAVE_HAL_AUDIO_SINK_STEREO_ONLY
                if (output_duplicate_samples){
                    unsigned int i = output_buffer_samples;
                    do {
                        i--;
                        int16_t sample = buffer[i];
                        buffer[2*i + 0] = sample;
                        buffer[2*i + 1] = sample;
                    } while ( i > 0 );
                }
#endif

                // next
                output_buffer_to_fill = (output_buffer_to_fill + 1 ) % output_buffer_count;
            }
    break;
        default:
            break;
    }
}

static void btstack_audio_embedded_source_handler(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
    UNUSED(ds);
    switch (callback_type) {
        case DATA_SOURCE_CALLBACK_POLL:
            // deliver samples if ready
            if (input_buffer_ready){
                const btstack_audio_context_t * context = NULL;
#ifdef HAVE_HAL_AUDIO_SOURCE_BUFFER_CONTEXT
                context = hal_audio_source_get_audio_context(output_buffer_to_fill);
#endif
                (*recording_callback)((const int16_t *)input_buffer_samples, input_buffer_num_samples, context);
                input_buffer_ready = 0;
            }
            break;
        default:
            break;
    }
}


static uint32_t sink_samplerate = 0;

static int btstack_audio_embedded_sink_init(
    uint8_t channels,
    uint32_t samplerate,
    void (*playback)(int16_t * buffer, uint16_t num_samples, const btstack_audio_context_t * context)
){
    btstack_assert(playback != NULL);
    btstack_assert(channels != 0);

#ifdef HAVE_HAL_AUDIO_SINK_STEREO_ONLY
    // always use stereo from hal, duplicate samples if needed
    output_duplicate_samples = channels != 2;
    channels = 2;
#endif

    playback_callback = playback;
    sink_samplerate = samplerate;
    hal_audio_sink_init(channels, samplerate, &btstack_audio_audio_played);

    return 0;
}

static uint32_t btstack_audio_embedded_sink_get_samplerate() {
    return hal_audio_sink_get_frequency();
}

static uint32_t source_samplerate = 0;

static int btstack_audio_embedded_source_init(
    uint8_t channels,
    uint32_t samplerate,
    void (*recording)(const int16_t * buffer, uint16_t num_samples, const btstack_audio_context_t * context)
){
    if (!recording){
        log_error("No recording callback");
        return 1;
    }

    recording_callback = recording;
    source_samplerate = samplerate;
    hal_audio_source_init(channels, samplerate, &btstack_audio_audio_recorded);

    return 0;
}

static uint32_t btstack_audio_embedded_source_get_samplerate() {
    return source_samplerate;
}

static void btstack_audio_embedded_sink_set_volume(uint8_t volume){
#ifdef HAVE_HAL_AUDIO_SINK_VOLUME_CONTROL
    hal_audio_sink_set_volume(volume);
#else
    UNUSED(volume);
#endif
}

static void btstack_audio_embedded_source_set_gain(uint8_t gain){
    UNUSED(gain);
#ifdef HAVE_HAL_AUDIO_SOURCE_GAIN_CONTROL
    hal_audio_source_set_gain(gain);
#else
    UNUSED(gain);
#endif
}

static void btstack_audio_embedded_sink_start_stream(void){
    output_buffer_count   = hal_audio_sink_get_num_output_buffers();
    output_buffer_samples = hal_audio_sink_get_num_output_buffer_samples();

    btstack_assert(output_buffer_samples > 0);

    // pre-fill HAL buffers
    uint16_t i;
    for (i=0;i<output_buffer_count;i++){
        int16_t * buffer = hal_audio_sink_get_output_buffer(i);
        (*playback_callback)(buffer, output_buffer_samples, 0);
    }

    output_buffer_to_play = 0;
    output_buffer_to_fill = 0;

    // set up polling data_source
    btstack_run_loop_set_data_source_handler(&btstack_audio_embedded_sink_data_source, &btstack_audio_embedded_sink_handler);
    btstack_run_loop_enable_data_source_callbacks(&btstack_audio_embedded_sink_data_source, DATA_SOURCE_CALLBACK_POLL);
    btstack_run_loop_add_data_source(&btstack_audio_embedded_sink_data_source);

    // start playback
    hal_audio_sink_start();

    // state
    sink_active = 1;
}

static void btstack_audio_embedded_source_start_stream(void){
    // just started, no data ready
    input_buffer_ready = 0;

    // set up polling data_source
    btstack_run_loop_set_data_source_handler(&btstack_audio_embedded_source_data_source, &btstack_audio_embedded_source_handler);
    btstack_run_loop_enable_data_source_callbacks(&btstack_audio_embedded_source_data_source, DATA_SOURCE_CALLBACK_POLL);
    btstack_run_loop_add_data_source(&btstack_audio_embedded_source_data_source);

    // start recording
    hal_audio_source_start();

    // state
    source_active = 1;
}

static void btstack_audio_embedded_sink_stop_stream(void){
    // stop stream
    hal_audio_sink_stop();
    // stop timer
    btstack_run_loop_remove_timer(&driver_timer_sink);
    // state
    sink_active = 0;
}

static void btstack_audio_embedded_source_stop_stream(void){
    // stop stream
    hal_audio_source_stop();
    // stop timer
    btstack_run_loop_remove_timer(&driver_timer_source);
    // state
    source_active = 0;
}

static void btstack_audio_embedded_sink_close(void){
    // stop stream if needed
    if (sink_active){
        btstack_audio_embedded_sink_stop_stream();
    }
    // close HAL
    hal_audio_sink_close();
}

static void btstack_audio_embedded_source_close(void){
    // stop stream if needed
    if (source_active){
        btstack_audio_embedded_source_stop_stream();
    }
    // close HAL
    hal_audio_source_close();
}

static const btstack_audio_sink_t btstack_audio_embedded_sink = {
        .init           = &btstack_audio_embedded_sink_init,
        .get_samplerate = &btstack_audio_embedded_sink_get_samplerate,
        .set_volume     = &btstack_audio_embedded_sink_set_volume,
        .start_stream   = &btstack_audio_embedded_sink_start_stream,
        .stop_stream    = &btstack_audio_embedded_sink_stop_stream,
        .close          = &btstack_audio_embedded_sink_close
};

static const btstack_audio_source_t btstack_audio_embedded_source = {
        .init           = &btstack_audio_embedded_source_init,
        .get_samplerate = &btstack_audio_embedded_source_get_samplerate,
        .set_gain       = &btstack_audio_embedded_source_set_gain,
        .start_stream   = &btstack_audio_embedded_source_start_stream,
        .stop_stream    = &btstack_audio_embedded_source_stop_stream,
        .close          = &btstack_audio_embedded_source_close
};

const btstack_audio_sink_t * btstack_audio_embedded_sink_get_instance(void){
    return &btstack_audio_embedded_sink;
}

const btstack_audio_source_t * btstack_audio_embedded_source_get_instance(void){
    return &btstack_audio_embedded_source;
}

#endif 
