/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include <os/os.h>
#include <stdint.h>
#include <string.h>
#include <syscfg/syscfg.h>

#include "console/console.h"
#include "host/ble_hs.h"
#include "host/ble_iso.h"

#include "app_priv.h"

struct chan {
    void *encoder;
    uint16_t handle;
} chans[AUDIO_CHANNELS];

#if MYNEWT_VAL(AUDIO_USB)
#include <common/tusb_fifo.h>
#include <class/audio/audio_device.h>
#include <usb_audio.h>

#include <lc3.h>
#include <samplerate.h>
#include <nrf_clock.h>

#include "host/ble_gap.h"
#include "os/os_cputime.h"

#define AUDIO_BUF_SIZE      1024

static uint8_t g_usb_enabled;

static void usb_data_func(struct os_event *ev);

static struct os_event usb_data_ev = {
    .ev_cb = usb_data_func,
};

static uint32_t frame_bytes_lc3;
static uint16_t big_sdu;

static int16_t out_buf[AUDIO_BUF_SIZE];
/* Reserve twice the size of input, so we'll always have space for resampler output */
static uint8_t encoded_frame[155];
static int out_idx = 0;
#if MYNEWT_VAL(ISO_HCI_FEEDBACK)
static int samples_idx = 0;
static int16_t samples_read[AUDIO_BUF_SIZE];
/* 155 is maximum value Octets Per Codec Frame described in Table 3.5 of BAP specification */
static float samples_read_float[AUDIO_BUF_SIZE];
static float resampled_float[AUDIO_BUF_SIZE];
float resampler_in_rate = MYNEWT_VAL(USB_AUDIO_OUT_SAMPLE_RATE);
float resampler_out_rate = LC3_SAMPLING_FREQ;
float resampler_ratio;
SRC_STATE *resampler_state;
static struct ble_gap_event_listener feedback_listener;
#endif
static uint32_t pkt_counter = 0;


#if MYNEWT_VAL(ISO_HCI_FEEDBACK)
static int
ble_hs_gap_event_handler(struct ble_gap_event *event, void *arg)
{
    struct ble_hci_vs_subev_iso_hci_feedback *feedback_pkt;
    float adjust = 0;

    if (event->type == BLE_GAP_EVENT_UNHANDLED_HCI_EVENT &&
        event->unhandled_hci.is_le_meta == false &&
        event->unhandled_hci.is_vs == true) {
        const struct ble_hci_ev_vs *ev = event->unhandled_hci.ev;

        if (ev->id == BLE_HCI_VS_SUBEV_ISO_HCI_FEEDBACK) {
            feedback_pkt = (struct ble_hci_vs_subev_iso_hci_feedback *) ev->data;
            assert(feedback_pkt->count == MYNEWT_VAL(BLE_ISO_MAX_BIGS));
            /* There is only one BIG in this sample */
            if (feedback_pkt->feedback[0].diff > 0) {
                adjust += 10;
            } else if (feedback_pkt->feedback[0].diff < 0) {
                adjust -= 10;
            }
            resampler_ratio = (resampler_out_rate + adjust) / resampler_in_rate;
        }
    }

    return 0;
}

static void
resample(void)
{
    static int resampled_len;
    int samples_consumed;
    int samples_left;
    SRC_DATA sd;
    int resample_avail = ARRAY_SIZE(out_buf) - out_idx;
    int rc;

    src_short_to_float_array(samples_read, samples_read_float, samples_idx);

    sd.data_in = samples_read_float;
    sd.data_out = resampled_float;
    sd.input_frames = samples_idx / AUDIO_CHANNELS;
    sd.output_frames = resample_avail / AUDIO_CHANNELS;
    sd.end_of_input = 0;
    sd.input_frames_used = 0;
    sd.output_frames_gen = 0;
    sd.src_ratio = resampler_ratio;

    rc = src_process(resampler_state, &sd);
    assert(rc == 0);

    resampled_len = sd.output_frames_gen * AUDIO_CHANNELS;

    assert(resampled_len <= resample_avail);

    src_float_to_short_array(resampled_float,
                             &out_buf[out_idx],
                             resampled_len);

    out_idx += resampled_len;

    samples_consumed = sd.input_frames_used * AUDIO_CHANNELS;
    samples_left = samples_idx - samples_consumed;
    memmove(samples_read, &samples_read[samples_consumed], samples_left);
    samples_idx -= samples_consumed;
}
#endif

static void
usb_data_func(struct os_event *ev)
{
    int ch_idx;
    unsigned int num_bytes;
    unsigned int num_samples;
    unsigned int num_frames;
    int read;
    int skip;

    if (!g_usb_enabled) {
        tud_audio_clear_ep_out_ff();
        return;
    }

    while ((num_bytes = tud_audio_available()) > 0) {
        num_frames = num_bytes / (AUDIO_CHANNELS * AUDIO_SAMPLE_SIZE);
        num_samples = num_frames * AUDIO_CHANNELS;
        if (out_idx + num_samples >= ARRAY_SIZE(out_buf)) {
            num_samples = ARRAY_SIZE(out_buf) - out_idx - 1;
        }
        num_frames = num_samples / AUDIO_CHANNELS;
        num_bytes = num_frames * AUDIO_SAMPLE_SIZE * AUDIO_CHANNELS;

#if MYNEWT_VAL(ISO_HCI_FEEDBACK)
        assert(samples_idx + num_samples < ARRAY_SIZE(samples_read));
        read = tud_audio_read(&samples_read[samples_idx], num_bytes);
        samples_idx += num_samples;
        resample();
#else
        assert(out_idx + num_samples < ARRAY_SIZE(out_buf));
        read = tud_audio_read(&out_buf[out_idx], num_bytes);
        out_idx += num_samples;
#endif
        assert(read == num_bytes);

        assert((out_idx & 0x80000000) == 0);
        if (out_idx / AUDIO_CHANNELS >= LC3_FPDT) {
            pkt_counter++;
            skip = 0;

            for (ch_idx = 0; ch_idx < AUDIO_CHANNELS; ch_idx++) {
                if (chans[ch_idx].handle == 0) {
                    skip = 1;
                    continue;
                }
            }

            if (!skip) {
                memset(encoded_frame, 0, sizeof(encoded_frame));
                lc3_encode(chans[0].encoder, LC3_PCM_FORMAT_S16, out_buf + 0,
                           AUDIO_CHANNELS, (int) frame_bytes_lc3,
                           encoded_frame);
                if (AUDIO_CHANNELS == 2) {
                    if (MYNEWT_VAL(BIG_NUM_BIS) == 1) {
                        lc3_encode(chans[0].encoder, LC3_PCM_FORMAT_S16,
                                   out_buf + 1, AUDIO_CHANNELS,
                                   (int) frame_bytes_lc3, encoded_frame + big_sdu / 2);
                        ble_iso_tx(chans[0].handle, encoded_frame, big_sdu);
                    } else {
                        ble_iso_tx(chans[0].handle, encoded_frame, big_sdu);
                        memset(encoded_frame, 0, sizeof(encoded_frame));
                        lc3_encode(chans[1].encoder, LC3_PCM_FORMAT_S16, out_buf + 1,
                                   AUDIO_CHANNELS, (int) frame_bytes_lc3,
                                   encoded_frame);
                        ble_iso_tx(chans[1].handle, encoded_frame, big_sdu);
                    }
                } else {
                    ble_iso_tx(chans[0].handle, encoded_frame, big_sdu);
                }

                if (out_idx / AUDIO_CHANNELS >= LC3_FPDT) {
                    out_idx -= LC3_FPDT * AUDIO_CHANNELS;
                    memmove(out_buf, &out_buf[LC3_FPDT * AUDIO_CHANNELS],
                            out_idx * AUDIO_SAMPLE_SIZE);
                } else {
                    out_idx = 0;
                }
            }
        }
    }
}

bool
tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received,
                               uint8_t func_id, uint8_t ep_out,
                               uint8_t cur_alt_setting)
{
    (void)rhport;
    (void)n_bytes_received;
    (void)func_id;
    (void)ep_out;
    (void)cur_alt_setting;

    if (!usb_data_ev.ev_queued) {
        os_eventq_put(os_eventq_dflt_get(), &usb_data_ev);
    }

    return true;
}

static void
audio_usb_init(void)
{
    /* Need to reference those explicitly, so they are always pulled by linker
     * instead of weak symbols in tinyusb.
     */
    (void)tud_audio_rx_done_post_read_cb;

    usb_desc_sample_rate_set(AUDIO_PCM_SAMPLE_RATE);

    assert(LC3_FPDT == lc3_frame_samples(LC3_FRAME_DURATION,
                                         AUDIO_PCM_SAMPLE_RATE));

    unsigned esize = lc3_encoder_size(LC3_FRAME_DURATION,
                                      AUDIO_PCM_SAMPLE_RATE);
    for (int i = 0; i < AUDIO_CHANNELS; i++) {
        chans[i].encoder = calloc(1, esize);
        lc3_setup_encoder(LC3_FRAME_DURATION, LC3_SAMPLING_FREQ,
                          AUDIO_PCM_SAMPLE_RATE, chans[i].encoder);
    }

    g_usb_enabled = 1;

    frame_bytes_lc3 = lc3_frame_bytes(LC3_FRAME_DURATION, LC3_BITRATE);
    big_sdu = frame_bytes_lc3 *
              (1 + ((AUDIO_CHANNELS == 2) && (BIG_NUM_BIS == 1)));

#if MYNEWT_VAL(ISO_HCI_FEEDBACK)
    int rc;

    assert(resampler_state == NULL);
    resampler_state = src_new(SRC_SINC_FASTEST, AUDIO_CHANNELS, NULL);
    assert(resampler_state != NULL);

    rc = ble_gap_event_listener_register(&feedback_listener,
                                         ble_hs_gap_event_handler, NULL);
    assert(rc == 0);

    resampler_ratio = resampler_out_rate / resampler_in_rate;
#endif
}
#else
#include "audio_data.h"

#define BROADCAST_MAX_SDU 120

static int audio_data_offset;
static struct os_callout audio_broadcast_callout;

static void
audio_broadcast_event_cb(struct os_event *ev)
{
    assert(ev != NULL);
    uint32_t ev_start_time = os_cputime_ticks_to_usecs(os_cputime_get32());

#if MYNEWT_VAL(AURACAST_CHAN_NUM) > 1
    if (audio_data_offset + BROADCAST_MAX_SDU >= sizeof(audio_data)) {
        audio_data_offset = 0;
    }

    if (chans[0].handle != BLE_HS_CONN_HANDLE_NONE) {
        ble_iso_tx(chans[0].handle, (void *) (audio_data + audio_data_offset),
                   BROADCAST_MAX_SDU);
    }
    if (chans[1].handle != BLE_HS_CONN_HANDLE_NONE) {
        ble_iso_tx(chans[1].handle, (void *) (audio_data + audio_data_offset),
                   BROADCAST_MAX_SDU);
    }
#else
    if (audio_data_offset + 2 * BROADCAST_MAX_SDU >= sizeof(audio_data)) {
        audio_data_offset = 0;
    }

    uint8_t lr_payload[BROADCAST_MAX_SDU * 2];
    memcpy(lr_payload, audio_data + audio_data_offset, BROADCAST_MAX_SDU);
    memcpy(lr_payload + BROADCAST_MAX_SDU, audio_data + audio_data_offset,
           BROADCAST_MAX_SDU);

    if (chans[0].handle != BLE_HS_CONN_HANDLE_NONE) {
        ble_iso_tx(chans[0].handle, (void *) (lr_payload),
                   BROADCAST_MAX_SDU * 2);
    }
#endif
    audio_data_offset += BROADCAST_MAX_SDU;

    /** Use cputime to time LC3_FRAME_DURATION, as these ticks are more
     *  accurate than os_time ones. This assures that we do not push
     *  LC3 data to ISO before interval, which could lead to
     *  controller running out of buffers. This is only needed because
     *  we already have coded data in an array - in real world application
     *  we usually wait for new audio to arrive, and lose time to code it too.
     */
    while (os_cputime_ticks_to_usecs(os_cputime_get32()) - ev_start_time <
           (MYNEWT_VAL(LC3_FRAME_DURATION)));

    os_callout_reset(&audio_broadcast_callout, 0);
}

static void
audio_dummy_init(void)
{
    os_callout_init(&audio_broadcast_callout, os_eventq_dflt_get(),
                    audio_broadcast_event_cb, NULL);

    os_callout_reset(&audio_broadcast_callout, 0);
}
#endif /* AUDIO_USB */

void
audio_init(void)
{
    for (size_t i = 0; i < ARRAY_SIZE(chans); i++) {
        chans[i].handle = BLE_HS_CONN_HANDLE_NONE;
    }

#if MYNEWT_VAL(AUDIO_USB)
    audio_usb_init();
#else
    audio_dummy_init();
#endif /* AUDIO_USB */
}

void
audio_chan_set_conn_handle(uint8_t chan_idx, uint16_t conn_handle)
{
    assert(chan_idx < ARRAY_SIZE(chans));
    chans[chan_idx].handle = conn_handle;
}
