// sb-send: Sonobus audio sender

// Audio functions used by multiple backends and primary audio
// processing thread.

#include "sb-send.h"

AudioRing g_ring;

void ring_init(AudioRing *r, int channels, bool backpressure) {
    memset(r, 0, sizeof(*r));
    r->channels = channels;
    r->backpressure = backpressure;
    pthread_mutex_init(&r->mutex, NULL);
    pthread_cond_init(&r->cond, NULL);
    atomic_store(&r->done, 0);
}

void ring_destroy(AudioRing *r) {
    pthread_mutex_destroy(&r->mutex);
    pthread_cond_destroy(&r->cond);
}

void ring_signal_done(AudioRing *r) {
    atomic_store(&r->done, 1);
    pthread_mutex_lock(&r->mutex);
    pthread_cond_broadcast(&r->cond);
    pthread_mutex_unlock(&r->mutex);
}

int ring_available(AudioRing *r) {
    pthread_mutex_lock(&r->mutex);
    int avail = r->head - r->tail;
    pthread_mutex_unlock(&r->mutex);
    return avail;
}

void ring_drain(AudioRing *r) {
    pthread_mutex_lock(&r->mutex);
    r->tail = r->head;
    pthread_mutex_unlock(&r->mutex);
}

void ring_put_float(AudioRing *r, const float *data, int samples) {
    SB_ASSERT(samples > 0);
    SB_ASSERT(r->channels > 0);
    pthread_mutex_lock(&r->mutex);
    if (r->backpressure) {
        while (STDIN_RING_SIZE - (r->head - r->tail) < samples) {
            if (atomic_load(&r->done)) { pthread_mutex_unlock(&r->mutex); return; }
            struct timespec ts;
            clock_gettime(CLOCK_REALTIME, &ts);
            ts.tv_sec += 1;
            pthread_cond_timedwait(&r->cond, &r->mutex, &ts);
        }
    } else {
        int space = STDIN_RING_SIZE - (r->head - r->tail);
        if (space < samples) {
            int drop = samples - space;
            r->tail += drop;
        }
    }
    for (int i = 0; i < samples; i++) {
        r->buf[(r->head + i) % STDIN_RING_SIZE] = data[i];
    }
    r->head += samples;
    pthread_cond_signal(&r->cond);
    pthread_mutex_unlock(&r->mutex);
}

int ring_get_float(AudioRing *r, float *out, int samples) {
    SB_ASSERT(samples > 0);
    SB_ASSERT(r->channels > 0);
    pthread_mutex_lock(&r->mutex);
    while (r->head - r->tail < samples) {
        if (atomic_load(&r->done)) {
            int avail = r->head - r->tail;
            if (avail > samples) { avail = samples; }
            for (int i = 0; i < avail; i++) {
                out[i] = r->buf[(r->tail + i) % STDIN_RING_SIZE];
            }
            r->tail += avail;
            pthread_mutex_unlock(&r->mutex);
            return avail / r->channels;
        }
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        ts.tv_nsec += 50000000;
        while (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
        int rc = pthread_cond_timedwait(&r->cond, &r->mutex, &ts);
        (void)rc;
    }
    for (int i = 0; i < samples; i++) {
        out[i] = r->buf[(r->tail + i) % STDIN_RING_SIZE];
    }
    r->tail += samples;
    pthread_cond_signal(&r->cond);
    pthread_mutex_unlock(&r->mutex);
    return samples / r->channels;
}

void ring_shutdown(AudioRing *r) {
    ring_signal_done(r);
}

typedef struct { int32_t sink_id, src_id, salt, seq; struct sockaddr_in addr; } PeerInfo;

static int send_opus_to_peers(AppState *state, const unsigned char *data, int data_len) {
    PeerInfo infos[256];
    int info_count = 0;

    pthread_mutex_lock(&state->peer_mutex);
    for (int i = 0; i < state->peers.count && info_count < 256; i++) {
        Peer *p = &state->peers.items[i];
        if (p->active && p->connected && !p->is_sb_send &&
            p->remote_sink_id != 0 &&
            p->our_source_id != 0 && p->salt != 0 &&
            p->salt_initialized && p->format_sent) {
            infos[info_count].sink_id = p->remote_sink_id;
            infos[info_count].src_id = p->our_source_id;
            infos[info_count].salt = p->salt;
            infos[info_count].seq = atomic_fetch_add(&p->sequence, 1);
            infos[info_count].addr = p->addr;
            info_count++;
        }
    }
    pthread_mutex_unlock(&state->peer_mutex);

    unsigned char sendbuf[MAX_PACKET_SIZE];
    for (int i = 0; i < info_count; i++) {
        SB_ASSERT(infos[i].sink_id != 0);
        SB_ASSERT(infos[i].src_id != 0);
        int len = build_data_msg(infos[i].sink_id, infos[i].src_id, infos[i].salt,
                                 infos[i].seq, data, data_len, sendbuf);
        if (len <= 0) { continue; }
        SB_ASSERT(len <= MAX_PACKET_SIZE);
        sendto(state->udp_sock, (const char*)sendbuf, len, 0,
               (struct sockaddr *)&infos[i].addr, sizeof(infos[i].addr));
    }
    return info_count;
}

void *audio_thread(void *arg) {
    AppState *state = (AppState *)arg;
    SB_ASSERT(state->channels > 0);
    SB_ASSERT(state->channels <= 2);

    while (g_running && atomic_load(&state->running) && !atomic_load(&state->group_joined)) {
        SLEEP_MS(100);
    }
    if (!g_running || !atomic_load(&state->running)) { return NULL; }

    if (state->use_encoded) {
        fprintf(stderr, "sb-send: reading pre-encoded opus from stdin\n");
        unsigned char pkt[MAX_OPUS_FRAME];
        float *pcm = malloc(OPUS_MAX_SAMPLES * state->channels * sizeof(float));
        if (!pcm) {
            fprintf(stderr, "sb-send: pcm buffer alloc failed\n");
            atomic_store(&state->running, false);
            return NULL;
        }

        while (g_running && atomic_load(&state->running)) {
            uint32_t pkt_len = 0;
            if (fread(&pkt_len, 4, 1, stdin) != 1) {
                break;
            }
            if (pkt_len == 0 || pkt_len > sizeof(pkt)) {
                break;
            }
            if (fread(pkt, 1, pkt_len, stdin) != pkt_len) {
                break;
            }

            if (state->opus_output_fd >= 0) {
                ssize_t written = 0;
                while (written < 4) {
                    ssize_t n = write(state->opus_output_fd, (char *)&pkt_len + written, 4 - written);
                    if (n <= 0) { break; }
                    written += n;
                }
                written = 0;
                while (written < (ssize_t)pkt_len) {
                    ssize_t n = write(state->opus_output_fd, (char *)pkt + written, pkt_len - written);
                    if (n <= 0) { break; }
                    written += n;
                }
            }

            if (state->output_fd >= 0 && state->decoder) {
                int decoded = opus_decode_to_s16(state->decoder, pkt, pkt_len,
                                                 state->decode_s16, pcm, state->channels);
                if (decoded > 0) {
                    ssize_t written = 0;
                    ssize_t want = decoded * state->channels * sizeof(int16_t);
                    while (written < want) {
                        ssize_t n = write(state->output_fd, (char *)state->decode_s16 + written, want - written);
                        if (n <= 0) { break; }
                        written += n;
                    }
                } else {
                    fprintf(stderr, "sb-send: opus decode failed (err=%d, pkt_len=%u)\n",
                            decoded, pkt_len);
                }
            }

            send_opus_to_peers(state, pkt, pkt_len);
        }
        free(pcm);
        return NULL;
    }

    int bufsize = state->frame_size * state->channels;
    float *pcm = malloc(bufsize * sizeof(float));
    unsigned char opus[MAX_OPUS_FRAME];
    pthread_t ring_tid = 0;

    bool use_ring = state->use_stdin;
    bool ring_needs_pacing = state->use_stdin;
    int ring_channels = state->channels;
#ifdef _WIN32
    if (state->use_wasapi) {
        use_ring = true;
        ring_needs_pacing = false;
        ring_channels = state->wasapi_channels;
    }
#endif

    int prefill = PREFILL_SAMPLES_STDIN;
#ifdef _WIN32
    if (state->use_wasapi) { prefill = PREFILL_SAMPLES_WASAPI; }
#endif

    if (use_ring) {
        ring_init(&g_ring, ring_channels, true);
#ifdef _WIN32
        if (state->use_wasapi) {
            fprintf(stderr, "Starting WASAPI capture...\n");
            pthread_create(&ring_tid, NULL, wasapi_reader_thread, state);
        } else {
            fprintf(stderr, "Starting stdin capture...\n");
            pthread_create(&ring_tid, NULL, stdin_reader_thread, &g_ring);
        }
#else
        pthread_create(&ring_tid, NULL, stdin_reader_thread, &g_ring);
#endif
        fprintf(stderr, "Prefilling audio buffer (%d ms)...\n",
                prefill * 1000 / SAMPLE_RATE);
        while (g_running && atomic_load(&state->running) &&
               ring_available(&g_ring) < prefill * ring_channels) {
            SLEEP_MS(5);
        }
        if (!g_running || !atomic_load(&state->running)) {
            free(pcm); return NULL;
        }
        fprintf(stderr, "Buffer ready (%d samples), starting stream\n",
                ring_available(&g_ring));
    }

    struct timespec next_time;
    clock_gettime(CLOCK_MONOTONIC, &next_time);
    next_time.tv_nsec += (long long)state->frame_size * 1000000000LL / SAMPLE_RATE;
    while (next_time.tv_nsec >= 1000000000L) {
        next_time.tv_sec++;
        next_time.tv_nsec -= 1000000000L;
    }

    while (g_running && atomic_load(&state->running)) {
        int frames = 0;

        if (use_ring) {
            int ring_bufsize = state->frame_size * ring_channels;
            float *ringtmp = (ring_channels != state->channels) ?
                             malloc(ring_bufsize * sizeof(float)) : pcm;
            int got = ring_get_float(&g_ring, ringtmp, ring_bufsize);
            if (got <= 0) {
                if (ringtmp != pcm) {
                    free(ringtmp);
                }
                atomic_store(&state->running, false);
                break;
            }
            if (got < state->frame_size) {
                if (atomic_load(&g_ring.done)) {
                    for (int i = got * ring_channels; i < ring_bufsize; i++) {
                        ringtmp[i] = 0.0f;
                    }
                    got = state->frame_size;
                } else {
                    if (ringtmp != pcm) {
                        free(ringtmp);
                    }
                    fprintf(stderr, "Buffer underrun (%d/%d frames), refilling...\n",
                            got, state->frame_size);
                    ring_drain(&g_ring);
                    while (g_running && atomic_load(&state->running) &&
                           ring_available(&g_ring) < prefill * ring_channels) {
                        SLEEP_MS(5);
                    }
                    if (!g_running || !atomic_load(&state->running)) {
                        break;
                    }
                    fprintf(stderr, "Buffer refilled (%d samples), resyncing\n",
                            ring_available(&g_ring));
                    continue;
                }
            }
            frames = got;
            if (ring_channels != state->channels) {
                for (int f = 0; f < frames; f++) {
                    if (ring_channels > state->channels) {
                        float sum = 0;
                        for (int c = 0; c < ring_channels; c++) {
                            sum += ringtmp[f * ring_channels + c];
                        }
                        for (int c = 0; c < state->channels; c++) {
                            pcm[f * state->channels + c] = sum / ring_channels;
                        }
                    } else {
                        for (int c = 0; c < state->channels; c++) {
                            pcm[f * state->channels + c] = ringtmp[f * ring_channels + c];
                        }
                    }
                }
                if (ringtmp != pcm) {
                    free(ringtmp);
                }
            }
            if (ring_needs_pacing) {
                struct timespec now;
                clock_gettime(CLOCK_MONOTONIC, &now);
                long diff_sec = next_time.tv_sec - now.tv_sec;
                long diff_nsec = next_time.tv_nsec - now.tv_nsec;
                if (diff_nsec < 0) {
                    diff_sec--;
                    diff_nsec += 1000000000L;
                }
                if (diff_sec > 0 || (diff_sec == 0 && diff_nsec > 0)) {
                    long total_ms = diff_sec * 1000 + diff_nsec / 1000000;
                    if (total_ms > 0) {
                        SLEEP_MS(total_ms);
                    }
                } else {
                    SLEEP_MS(1);
                }
                next_time.tv_nsec += (long long)state->frame_size * 1000000000LL / SAMPLE_RATE;
                while (next_time.tv_nsec >= 1000000000L) {
                    next_time.tv_sec++;
                    next_time.tv_nsec -= 1000000000L;
                }
            }
        } else {
#ifdef ALSA
            frames = snd_pcm_readi(state->capture, pcm, state->frame_size);
            if (frames < 0) {
                frames = snd_pcm_recover(state->capture, frames, 0);
                if (frames < 0) { SLEEP_MS(1); continue; }
            }
#endif
            if (frames != state->frame_size) { SLEEP_MS(1); continue; }
            SLEEP_MS(1);
        }

        if (state->output_fd >= 0) {
            int16_t s16[state->frame_size * 2];
            int total = frames * state->channels;
            if (total > state->frame_size * 2) { total = state->frame_size * 2; }
            for (int i = 0; i < total; i++) {
                float v = pcm[i];
                if (v > 1.0f) { v = 1.0f; }
                else if (v < -1.0f) { v = -1.0f; }
                s16[i] = (int16_t)(v * 32767.0f);
            }
            ssize_t written = 0;
            ssize_t want = total * sizeof(int16_t);
            while (written < want) {
                ssize_t n = write(state->output_fd, (char *)s16 + written, want - written);
                if (n <= 0) { break; }
                written += n;
            }
        }

        int opus_len = opus_multistream_encode_float(state->encoder, pcm, state->frame_size,
                                                     opus, MAX_OPUS_ENCODE);
        if (opus_len <= 0) {
            dbg(state, "audio: opus encode failed (%d)\n", opus_len);
            continue;
        }

        if (state->opus_output_fd >= 0) {
            uint32_t pkt_len = (uint32_t)opus_len;
            ssize_t written = 0;
            while (written < 4) {
                ssize_t n = write(state->opus_output_fd, (char *)&pkt_len + written, 4 - written);
                if (n <= 0) { break; }
                written += n;
            }
            written = 0;
            while (written < opus_len) {
                ssize_t n = write(state->opus_output_fd, (char *)opus + written, opus_len - written);
                if (n <= 0) { break; }
                written += n;
            }
        }

        send_opus_to_peers(state, opus, opus_len);
    }

    if (use_ring) {
        ring_shutdown(&g_ring);
        pthread_join(ring_tid, NULL);
        ring_destroy(&g_ring);
    }
    free(pcm);
    return NULL;
}
// EOF
