#include "AudioBuffer.h"
#include <fstream>
#include <cstring>
#include <cstdint>
#include <algorithm>
#include <iostream>
#include <opus.h>
#include <ogg/ogg.h>

namespace secondvoice {

AudioBuffer::AudioBuffer(int sample_rate, int channels)
    : sample_rate_(sample_rate)
    , channels_(channels) {
}

void AudioBuffer::addSamples(const std::vector<float>& samples) {
    samples_.insert(samples_.end(), samples.begin(), samples.end());
}

std::vector<float> AudioBuffer::getSamples() const {
    return samples_;
}

void AudioBuffer::clear() {
    samples_.clear();
}

bool AudioBuffer::saveToWav(const std::string& filename) const {
    std::ofstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        return false;
    }

    // WAV header
    struct WavHeader {
        char riff[4] = {'R', 'I', 'F', 'F'};
        uint32_t file_size;
        char wave[4] = {'W', 'A', 'V', 'E'};
        char fmt[4] = {'f', 'm', 't', ' '};
        uint32_t fmt_size = 16;
        uint16_t audio_format = 1;  // PCM
        uint16_t num_channels;
        uint32_t sample_rate;
        uint32_t byte_rate;
        uint16_t block_align;
        uint16_t bits_per_sample = 16;
        char data[4] = {'d', 'a', 't', 'a'};
        uint32_t data_size;
    };

    WavHeader header;
    header.num_channels = channels_;
    header.sample_rate = sample_rate_;
    header.byte_rate = sample_rate_ * channels_ * 2;  // 16-bit = 2 bytes
    header.block_align = channels_ * 2;

    // Convert float samples to 16-bit PCM
    std::vector<int16_t> pcm_samples;
    pcm_samples.reserve(samples_.size());
    for (float sample : samples_) {
        // Clamp to [-1.0, 1.0] and convert to 16-bit
        float clamped = std::max(-1.0f, std::min(1.0f, sample));
        pcm_samples.push_back(static_cast<int16_t>(clamped * 32767.0f));
    }

    header.data_size = pcm_samples.size() * sizeof(int16_t);
    header.file_size = sizeof(WavHeader) - 8 + header.data_size;

    // Write header
    file.write(reinterpret_cast<const char*>(&header), sizeof(header));

    // Write PCM data
    file.write(reinterpret_cast<const char*>(pcm_samples.data()), header.data_size);

    file.close();
    return true;
}

bool AudioBuffer::saveToOpus(const std::string& filename) const {
    if (samples_.empty()) {
        std::cerr << "AudioBuffer::saveToOpus: No samples to encode" << std::endl;
        return false;
    }

    // Create Opus encoder
    int error;
    OpusEncoder* encoder = opus_encoder_create(
        sample_rate_,
        channels_,
        OPUS_APPLICATION_VOIP,  // Optimized for voice
        &error
    );

    if (error != OPUS_OK || !encoder) {
        std::cerr << "Failed to create Opus encoder: " << opus_strerror(error) << std::endl;
        return false;
    }

    // Configure encoder for best quality/compression tradeoff
    opus_encoder_ctl(encoder, OPUS_SET_BITRATE(32000));  // 32 kbps (good for voice)
    opus_encoder_ctl(encoder, OPUS_SET_VBR(1));  // Variable bitrate
    opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(5));  // Medium complexity

    // Open output file
    std::ofstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        opus_encoder_destroy(encoder);
        return false;
    }

    // Initialize Ogg stream
    ogg_stream_state stream_state;
    ogg_page page;
    ogg_packet packet;

    // Random serial number for Ogg stream
    ogg_stream_init(&stream_state, 12345);

    // Create Opus header (OpusHead)
    unsigned char header_data[19] = {
        'O', 'p', 'u', 's', 'H', 'e', 'a', 'd',  // Magic signature
        1,  // Version
        static_cast<unsigned char>(channels_),  // Channel count
        0, 0,  // Pre-skip (little-endian, 0)
        static_cast<unsigned char>(sample_rate_ & 0xFF),
        static_cast<unsigned char>((sample_rate_ >> 8) & 0xFF),
        static_cast<unsigned char>((sample_rate_ >> 16) & 0xFF),
        static_cast<unsigned char>((sample_rate_ >> 24) & 0xFF),
        0, 0,  // Output gain (little-endian, 0)
        0  // Channel mapping family (0 = mono/stereo)
    };

    packet.packet = header_data;
    packet.bytes = 19;
    packet.b_o_s = 1;  // Beginning of stream
    packet.e_o_s = 0;
    packet.granulepos = 0;
    packet.packetno = 0;

    ogg_stream_packetin(&stream_state, &packet);

    // Write header pages
    while (ogg_stream_flush(&stream_state, &page) != 0) {
        file.write(reinterpret_cast<char*>(page.header), page.header_len);
        file.write(reinterpret_cast<char*>(page.body), page.body_len);
    }

    // Create Opus comment header (OpusTags)
    const char* vendor = "SecondVoice";
    size_t vendor_len = strlen(vendor);
    size_t comment_header_size = 8 + 4 + vendor_len + 4;
    std::vector<unsigned char> comment_header(comment_header_size);

    memcpy(&comment_header[0], "OpusTags", 8);
    comment_header[8] = vendor_len & 0xFF;
    comment_header[9] = (vendor_len >> 8) & 0xFF;
    comment_header[10] = (vendor_len >> 16) & 0xFF;
    comment_header[11] = (vendor_len >> 24) & 0xFF;
    memcpy(&comment_header[12], vendor, vendor_len);
    // Number of user comments (0)
    comment_header[12 + vendor_len] = 0;
    comment_header[13 + vendor_len] = 0;
    comment_header[14 + vendor_len] = 0;
    comment_header[15 + vendor_len] = 0;

    packet.packet = comment_header.data();
    packet.bytes = comment_header_size;
    packet.b_o_s = 0;
    packet.e_o_s = 0;
    packet.granulepos = 0;
    packet.packetno = 1;

    ogg_stream_packetin(&stream_state, &packet);

    while (ogg_stream_flush(&stream_state, &page) != 0) {
        file.write(reinterpret_cast<char*>(page.header), page.header_len);
        file.write(reinterpret_cast<char*>(page.body), page.body_len);
    }

    // Encode audio in frames
    // Opus recommends 20ms frames: frame_size = sample_rate * 0.02
    int frame_size = sample_rate_ / 50;  // 20ms
    int frame_samples = frame_size * channels_;

    std::vector<unsigned char> encoded_data(4000);  // Max packet size for Opus
    int64_t packetno = 2;
    int64_t granulepos = 0;

    for (size_t i = 0; i < samples_.size(); i += frame_samples) {
        // Get frame (pad with zeros if incomplete)
        std::vector<float> frame(frame_samples, 0.0f);
        size_t samples_to_copy = std::min(frame_samples, static_cast<int>(samples_.size() - i));
        std::copy(samples_.begin() + i, samples_.begin() + i + samples_to_copy, frame.begin());

        // Encode frame
        int encoded_bytes = opus_encode_float(
            encoder,
            frame.data(),
            frame_size,
            encoded_data.data(),
            encoded_data.size()
        );

        if (encoded_bytes < 0) {
            std::cerr << "Opus encoding failed: " << opus_strerror(encoded_bytes) << std::endl;
            opus_encoder_destroy(encoder);
            ogg_stream_clear(&stream_state);
            return false;
        }

        // Create Ogg packet
        granulepos += frame_size;
        packet.packet = encoded_data.data();
        packet.bytes = encoded_bytes;
        packet.b_o_s = 0;
        packet.e_o_s = (i + frame_samples >= samples_.size()) ? 1 : 0;
        packet.granulepos = granulepos;
        packet.packetno = packetno++;

        ogg_stream_packetin(&stream_state, &packet);

        // Write pages
        while (ogg_stream_pageout(&stream_state, &page) != 0) {
            file.write(reinterpret_cast<char*>(page.header), page.header_len);
            file.write(reinterpret_cast<char*>(page.body), page.body_len);
        }
    }

    // Flush remaining pages
    while (ogg_stream_flush(&stream_state, &page) != 0) {
        file.write(reinterpret_cast<char*>(page.header), page.header_len);
        file.write(reinterpret_cast<char*>(page.body), page.body_len);
    }

    // Cleanup
    opus_encoder_destroy(encoder);
    ogg_stream_clear(&stream_state);
    file.close();

    std::cout << "Saved Opus file: " << filename << std::endl;
    return true;
}

} // namespace secondvoice
