#include "simple_pipeline.h"
#include "boards/common/board.h"
#include "audio_codecs/audio_codec.h"
#include <vector>
#include <cstring>

static inline int16_t lrint16(float v){ if(v>32767.f) return 32767; if(v<-32768.f) return -32768; return (int16_t)v; }

static void resample_mono_nearest(const int16_t* in, int in_samples, int in_rate, int16_t* out, int out_samples, int out_rate){
    if(in_rate==out_rate){ for(int i=0;i<out_samples;i++) out[i]=in[i]; return; }
    float ratio = (float)in_rate/(float)out_rate;
    for(int i=0;i<out_samples;i++){ int idx = (int)(i*ratio); if(idx>=in_samples) idx=in_samples-1; out[i]=in[idx]; }
}

recorder_pipeline_handle_t recorder_pipeline_open(){
    auto h = (recorder_pipeline_handle_t)pvPortMalloc(sizeof(recorder_pipeline_t));
    h->task = nullptr;
    h->dest_rate = 16000;
    auto codec = Board::GetInstance().GetAudioCodec();
    h->src_rate = codec->input_sample_rate();
    h->channels = 1;
    h->block_bytes = (h->dest_rate/50)*sizeof(int16_t);
    codec->EnableInput(true);
    return h;
}

void recorder_pipeline_run(recorder_pipeline_handle_t){ }

void recorder_pipeline_close(recorder_pipeline_handle_t h){
    if(!h) return;
    if (h->task) {
        vTaskDelete(h->task);
    }
    auto codec = Board::GetInstance().GetAudioCodec();
    codec->EnableInput(false);
    vPortFree(h);
}

int recorder_pipeline_get_default_read_size(recorder_pipeline_handle_t h){ return h? h->block_bytes: 0; }

int recorder_pipeline_read(recorder_pipeline_handle_t h, char *buffer, int buf_size){
    if(!h || !buffer) return 0;
    auto codec = Board::GetInstance().GetAudioCodec();
    int out_samples = h->dest_rate/50;
    std::vector<int16_t> tmp;
    if(h->src_rate==h->dest_rate){ tmp.resize(out_samples); }
    else{ tmp.resize((int)((float)out_samples*h->src_rate/h->dest_rate)); }
    if(!codec->InputData(tmp)) return 0;
    std::vector<int16_t> out(out_samples);
    resample_mono_nearest(tmp.data(), (int)tmp.size(), h->src_rate, out.data(), out_samples, h->dest_rate);
    int bytes = out_samples*sizeof(int16_t);
    if(bytes>buf_size) bytes=buf_size;
    memcpy(buffer, out.data(), bytes);
    return bytes;
}

player_pipeline_handle_t player_pipeline_open(){
    auto h = (player_pipeline_handle_t)pvPortMalloc(sizeof(player_pipeline_t));
    h->task = nullptr;
    h->src_rate = 16000;
    auto codec = Board::GetInstance().GetAudioCodec();
    h->dest_rate = codec->output_sample_rate();
    h->channels = codec->output_channels();
    h->block_bytes = (h->src_rate/50)*sizeof(int16_t);
    h->fade_total = h->dest_rate / 10; // 100ms淡入
    h->fade_done = 0;
    codec->EnableOutput(true);
    return h;
}

void player_pipeline_run(player_pipeline_handle_t){ }

void player_pipeline_close(player_pipeline_handle_t h){
    if(!h) return;
    if (h->task) {
        vTaskDelete(h->task);
    }
    auto codec = Board::GetInstance().GetAudioCodec();
    codec->EnableOutput(false);
    vPortFree(h);
}

int player_pipeline_get_default_read_size(player_pipeline_handle_t h){ return h? h->block_bytes: 0; }

int player_pipeline_write(player_pipeline_handle_t h, char *buffer, int buf_size){
    if(!h || !buffer || buf_size<=0) return 0;
    int in_samples = buf_size/sizeof(int16_t);
    std::vector<int16_t> in(in_samples);
    memcpy(in.data(), buffer, buf_size);
    int out_samples = (int)((float)in_samples*h->dest_rate/h->src_rate);
    std::vector<int16_t> out(out_samples);
    resample_mono_nearest(in.data(), in_samples, h->src_rate, out.data(), out_samples, h->dest_rate);
    if (h->fade_done < h->fade_total) {
        int n = out_samples;
        for (int i = 0; i < n; ++i) {
            int done = h->fade_done + i;
            float g = done < h->fade_total ? (float)done / (float)h->fade_total : 1.0f;
            out[i] = lrint16((float)out[i] * g);
        }
        h->fade_done += n;
        if (h->fade_done > h->fade_total) h->fade_done = h->fade_total;
    }
    auto codec = Board::GetInstance().GetAudioCodec();
    if (h->channels == 2) {
        std::vector<int16_t> stereo(out_samples * 2);
        for (int i = 0, j = 0; i < out_samples; ++i) {
            stereo[j++] = out[i];
            stereo[j++] = out[i];
        }
        codec->OutputData(stereo);
    } else {
        codec->OutputData(out);
    }
    return buf_size;
}

void player_pipeline_write_play_buffer_flag(player_pipeline_handle_t){ }

void player_pipeline_set_src_rate(player_pipeline_handle_t h, int rate){
    if (!h || rate <= 0) return;
    h->src_rate = rate;
    h->block_bytes = (h->src_rate/50)*sizeof(int16_t);
}
