#include <AL/al.h>
#include <AssetManager/AssetManager.hpp>
#include <Sound/Sound.hpp>
#include <Log/Log.hpp>
#include <AudioSource/AudioSource.hpp>

Sound Sound::NONE;

#define DECL_SOUND(name) Sound Sound::name(#name)
#include <Sound/SoundList.hpp>

std::unordered_map<std::string, Sound*> Sound::soundTable;

bool Sound::loadHeader(
    std::ifstream& ifs,
    uint16_t& channels,
    int32_t& sampleRate,
    uint16_t& bitsPerSample,
    ALsizei& size
)
{
    char buffer[5];
    uint32_t fmtSize;

    // the RIFF
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read RIFF");
        return false;
    }

    if (std::strncmp(buffer, "RIFF", 4) != 0)
    {
        Log::error("file is not a valid WAVE file (header doesn't begin with RIFF)");
        return false;
    }

    // the size of the file
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read size of file");
        return false;
    }

    // the WAVE
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read WAVE");
        return false;
    }

    if (std::strncmp(buffer, "WAVE", 4) != 0)
    {
        Log::error("file is not a valid WAVE file (header doesn't contain WAVE)");
        return false;
    }

    // "fmt/0"
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read fmt/0");
        return false;
    }

    // read the fmt chunk size
    if (!ifs.read((char*) &fmtSize, 4))
    {
        Log::error("could not read the fmt chunk size");
        return false;
    }

    if (fmtSize < 16)
    {
        Log::error(formatString("size of 'fmt' is too small ($+)", fmtSize));
        return false;
    }

    // PCM should be 1?
    if (!ifs.read(buffer, 2))
    {
        Log::error("could not read PCM");
        return false;
    }

    // the number of channels
    if (!ifs.read((char*) &channels, 2))
    {
        Log::error("could not read number of channels");
        return false;
    }

    // sample rate
    if (!ifs.read((char*) &sampleRate, 4))
    {
        Log::error("could not read sample rate");
        return false;
    }

    // (sampleRate * bitsPerSample * channels) / 8
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read (sampleRate * bitsPerSample * channels) / 8");
        return false;
    }

    // ?? dafaq
    if (!ifs.read(buffer, 2))
    {
        Log::error("could not read dafaq");
        return false;
    }

    // bitsPerSample
    if (!ifs.read((char*) &bitsPerSample, 2))
    {
        Log::error("could not read bits per sample");
        return false;
    }

    // skip remaining fmt bytes
    ifs.ignore(fmtSize - 16);

    // data chunk header "data"
    if (!ifs.read(buffer, 4))
    {
        Log::error("could not read data chunk header");
        return false;
    }

    while ((buffer[0] >= 'A' && buffer[0] <= 'Z'))
    {
        uint32_t ignoreSize;
        if (!ifs.read((char*) &ignoreSize, 4))
        {
            Log::error("error while reading chunks");
            return false;
        }

        ifs.ignore(ignoreSize);
        if (!ifs.read(buffer, 4))
        {
            Log::error("could not read data chunk header");
            return false;
        }
    }

    if (std::strncmp(buffer, "data", 4) != 0)
    {
        buffer[4] = 0;
        Log::error(formatString("file is not a valid WAVE file (encountered '$+' header when expecting 'data')", buffer));
        return false;
    }

    // size of data
    if (!ifs.read((char*) &size, 4))
    {
        Log::error("could not read data size");
        return false;
    }

    return true;
}

Sound::Sound() : buffer(0) {}
Sound::Sound(std::string name) : buffer(0), name(name) {}

void Sound::load()
{
    Log::info(formatString("Loading sound '$+' ...", name));
    soundTable[name] = this;

    std::string soundPath = assets.resolve(formatString("sounds/$+.wav", name));
    std::ifstream ifs(soundPath, std::ios::binary);

    if (!ifs.good())
    {
        crash(formatString("Failed to open sound file '$+'", soundPath));
    }

    uint16_t channels;
    int32_t sampleRate;
    uint16_t bitsPerSample;
    ALsizei size;

    if (!loadHeader(ifs, channels, sampleRate, bitsPerSample, size))
    {
        crash("Failed to load the WAV header!");
    }

    char *data = new char[size];

    if (!ifs.read(data, size))
    {
        crash("Failed to read the audio data");
    }

    ifs.close();

    alGenBuffers(1, &buffer);

    ALenum format;
    if (channels == 1 && bitsPerSample == 8)
    {
        format = AL_FORMAT_MONO8;
    }
    else if (channels == 1 && bitsPerSample == 16)
    {
        format = AL_FORMAT_MONO16;
    }
    else if (channels == 2 && bitsPerSample == 8)
    {
        format = AL_FORMAT_STEREO8;
    }
    else if (channels == 2 && bitsPerSample == 16)
    {
        format = AL_FORMAT_STEREO16;
    }
    else
    {
        crash(formatString("Unsupported audio format (channels=$+, bitsPerSample=$+)", (int) channels, (int) bitsPerSample));
    }

    alBufferData(buffer, format, data, size, sampleRate);
    delete[] data;
}

bool Sound::isNone()
{
    return this == &NONE;
}

void Sound::play(glm::vec3 pos, glm::vec3 vel, float pitch, float gain)
{
    AudioSource &source = AudioSource::borrowFromPool();
    source.setPosition(pos);
    source.setVelocity(vel);
    source.setPitch(pitch);
    source.setGain(gain);
    source.setSound(*this);
}

void Sound::play()
{
    AudioSource &source = AudioSource::borrowFromPool();
    source.setSound(*this);
}

Sound& Sound::byName(std::string name)
{
    std::unordered_map<std::string, Sound*>::iterator soundIter = soundTable.find(name);
    if (soundIter == soundTable.end())
    {
        return Sound::NONE;
    }

    return *soundIter->second;
}