#include "audio.h"
#include <AL/al.h>
#include <AL/alc.h>
#include <cmath>
#include <cstdint>
#include <vector>

// ── Procedural sound synthesis ────────────────────────────────────────────────

static constexpr int   RATE = 22050;
static constexpr float PI   = 3.14159265f;

struct Rng {
    uint32_t s;
    float next() {
        s = s * 1664525u + 1013904223u;
        return (float)(s >> 16) / 32768.f - 1.f;
    }
};

static inline float lp(float x, float& st, float a) { return st += a * (x - st); }

using Buf = std::vector<int16_t>;

static inline int16_t s16(float v) {
    int i = (int)(v * 32767.f);
    if (i >  32767) i =  32767;
    if (i < -32767) i = -32767;
    return (int16_t)i;
}

// Спільний рецепт: низький синус + сильно відфільтрований шум, плавна огинаюча
// freq  — основна частота (Гц)
// dur   — тривалість (с)
// blend — частка синусу (0=чистий шум, 1=чистий синус)
// decay — швидкість згасання
static Buf genTonalThud(uint32_t seed, float freq, float dur,
                        float blend, float decay, float gainMul = 1.f) {
    Rng r{seed};
    int n = (int)(dur * RATE);
    Buf b(n);
    float st = 0.f;
    for (int i = 0; i < n; ++i) {
        float t   = i / float(n);
        float env = (t < 0.04f) ? t / 0.04f : expf(-(t - 0.04f) * decay);
        float sine  = sinf(2*PI * freq * i / RATE) * blend;
        float noise = lp(r.next() * (1.f - blend), st, 0.06f);
        b[i] = s16((sine + noise) * env * gainMul);
    }
    return b;
}

// ── Break ─────────────────────────────────────────────────────────────────────
static Buf genBreakStone() { return genTonalThud(0xDEAD1234, 85.f,  0.40f, 0.55f, 8.f,  0.80f); }
static Buf genBreakWood()  { return genTonalThud(0xBEEF5678, 110.f, 0.35f, 0.60f, 9.f,  0.75f); }
static Buf genBreakSoft()  { return genTonalThud(0xCAFE9012, 70.f,  0.30f, 0.30f, 11.f, 0.65f); }

// ── Place ─────────────────────────────────────────────────────────────────────
static Buf genPlaceStone() { return genTonalThud(0x11223344, 95.f,  0.22f, 0.55f, 16.f, 0.80f); }
static Buf genPlaceWood()  { return genTonalThud(0x55667788, 120.f, 0.20f, 0.60f, 18.f, 0.75f); }
static Buf genPlaceSoft()  { return genTonalThud(0x99AABBCC, 75.f,  0.16f, 0.25f, 20.f, 0.65f); }

// ── Footstep ─────────────────────────────────────────────────────────────────
static Buf genFootstep() {
    Rng r{0xABCD4321};
    int n = (int)(0.18f * RATE);
    Buf b(n);
    float st = 0.f;
    for (int i = 0; i < n; ++i) {
        float t   = i / float(n);
        float env = (t < 0.06f) ? t / 0.06f : expf(-(t - 0.06f) * 18.f);
        float sine  = sinf(2*PI * 72.f * i / RATE) * 0.45f;
        float noise = lp(r.next() * 0.30f, st, 0.06f);
        b[i] = s16((sine + noise) * env * 0.45f);
    }
    return b;
}

// ── Item pickup: ледь помітний низький тук ────────────────────────────────────
static Buf genPickup() {
    Rng r{0xF00D4321};
    int n = (int)(0.09f * RATE);
    Buf b(n);
    float st = 0.f;
    for (int i = 0; i < n; ++i) {
        float t   = i / float(n);
        float env = (t < 0.05f) ? t / 0.05f : expf(-(t - 0.05f) * 28.f);
        float sine  = sinf(2*PI * 110.f * i / RATE) * 0.60f;
        float noise = lp(r.next() * 0.30f, st, 0.07f);
        b[i] = s16((sine + noise) * env * 4.0f);
    }
    return b;
}

// ── Mob hit ───────────────────────────────────────────────────────────────────
static Buf genMobHit() { return genTonalThud(0x99887766, 90.f, 0.28f, 0.50f, 10.f, 0.80f); }

// ── Mob death: низький спадаючий тон ─────────────────────────────────────────
static Buf genMobDeath() {
    Rng r{0x13572468};
    int n = (int)(0.70f * RATE);
    Buf b(n);
    float st = 0.f;
    for (int i = 0; i < n; ++i) {
        float t    = i / float(n);
        float env  = (t < 0.04f) ? t / 0.04f : expf(-(t - 0.04f) * 3.5f);
        float freq = 120.f - 60.f * t;          // спадає 120 → 60 Hz
        float sine  = sinf(2*PI * freq * i / RATE) * 0.55f;
        float noise = lp(r.next() * 0.25f, st, 0.06f);
        b[i] = s16((sine + noise) * env * 0.85f);
    }
    return b;
}

// ── UI click: м'який низький поп ─────────────────────────────────────────────
static Buf genUIClick() {
    int n = (int)(0.10f * RATE);
    Buf b(n);
    for (int i = 0; i < n; ++i) {
        float t   = i / float(n);
        float env = (t < 0.04f) ? t / 0.04f : expf(-(t - 0.04f) * 22.f);
        b[i] = s16(sinf(2*PI * 200.f * i / RATE) * env * 0.45f);
    }
    return b;
}

// ── Audio class ──────────────────────────────────────────────────────────────

bool Audio::init() {
    dev_ = alcOpenDevice(nullptr);
    if (!dev_) return false;
    ctx_ = alcCreateContext(dev_, nullptr);
    if (!ctx_) { alcCloseDevice(dev_); dev_ = nullptr; return false; }
    alcMakeContextCurrent(ctx_);

    alGenBuffers(SND_COUNT, bufs_);
    alGenSources(NUM_SRC, srcs_);

    for (int i = 0; i < NUM_SRC; ++i) {
        alSourcei(srcs_[i], AL_SOURCE_RELATIVE, AL_TRUE);
        alSource3f(srcs_[i], AL_POSITION,        0, 0, 0);
        alSourcef(srcs_[i], AL_ROLLOFF_FACTOR,   0.f);
    }

    alListener3f(AL_POSITION, 0, 0, 0);
    alListener3f(AL_VELOCITY, 0, 0, 0);

    generateAll();
    return true;
}

void Audio::generateAll() {
    auto fill = [&](int idx, Buf&& data) {
        alBufferData(bufs_[idx], AL_FORMAT_MONO16,
                     data.data(), (ALsizei)(data.size() * 2), RATE);
    };
    fill(SND_BREAK_STONE, genBreakStone());
    fill(SND_BREAK_WOOD,  genBreakWood());
    fill(SND_BREAK_SOFT,  genBreakSoft());
    fill(SND_PLACE_STONE, genPlaceStone());
    fill(SND_PLACE_WOOD,  genPlaceWood());
    fill(SND_PLACE_SOFT,  genPlaceSoft());
    fill(SND_FOOTSTEP,    genFootstep());
    fill(SND_PICKUP,      genPickup());
    fill(SND_MOB_HIT,     genMobHit());
    fill(SND_MOB_DEATH,   genMobDeath());
    fill(SND_UI_CLICK,    genUIClick());
}

void Audio::cleanup() {
    if (!ctx_) return;
    alSourceStopv(NUM_SRC, srcs_);
    alDeleteSources(NUM_SRC, srcs_);
    alDeleteBuffers(SND_COUNT, bufs_);
    alcMakeContextCurrent(nullptr);
    alcDestroyContext(ctx_);
    alcCloseDevice(dev_);
    ctx_ = nullptr;
    dev_ = nullptr;
}

void Audio::playBuf(int snd, float gain, float pitch) {
    if (!ctx_) return;
    ALuint src = srcs_[srcIdx_];
    srcIdx_ = (srcIdx_ + 1) % NUM_SRC;
    alSourceStop(src);
    alSourcei(src, AL_BUFFER,   (ALint)bufs_[snd]);
    alSourcef(src, AL_GAIN,     gain);
    alSourcef(src, AL_PITCH,    pitch);
    alSourcePlay(src);
}

// ── Public API ────────────────────────────────────────────────────────────────

static float randPitch(float base, float range) {
    static int c = 0;
    float v = (float)((c++) % 7) / 7.f;
    return base + v * range;
}

void Audio::playBlockBreak(BlockType bt) {
    using BT = BlockType;
    float p = randPitch(0.72f, 0.18f);
    switch (bt) {
        case BT::Stone: case BT::Gravel: case BT::Sandstone: case BT::Ice:
            playBuf(SND_BREAK_STONE, 0.90f, p); break;
        case BT::Wood: case BT::Leaves:
            playBuf(SND_BREAK_WOOD,  0.85f, p); break;
        default:
            playBuf(SND_BREAK_SOFT,  0.80f, p); break;
    }
}

void Audio::playBlockPlace(BlockType bt) {
    using BT = BlockType;
    float p = randPitch(0.74f, 0.14f);
    switch (bt) {
        case BT::Stone: case BT::Gravel: case BT::Sandstone: case BT::Ice:
            playBuf(SND_PLACE_STONE, 0.90f, p); break;
        case BT::Wood: case BT::Leaves:
            playBuf(SND_PLACE_WOOD,  0.85f, p); break;
        default:
            playBuf(SND_PLACE_SOFT,  0.80f, p); break;
    }
}

void Audio::playFootstep()   { playBuf(SND_FOOTSTEP,  0.55f, randPitch(0.72f, 0.18f)); }
void Audio::playItemPickup() { playBuf(SND_PICKUP,    1.0f, 1.0f); }
void Audio::playMobHit()     { playBuf(SND_MOB_HIT,   0.80f, randPitch(0.74f, 0.14f)); }
void Audio::playMobDeath()   { playBuf(SND_MOB_DEATH, 0.85f, randPitch(0.76f, 0.12f)); }
void Audio::playUIClick()    { playBuf(SND_UI_CLICK,  0.50f, 1.0f); }
