#include "SoundManager.h"

#include "ifilesystem.h"
#include "icommandsystem.h"
#include "ideclmanager.h"

#include "os/path.h"
#include "os/fs.h"
#include "string/case_conv.h"

#include <algorithm>
#include "itextstream.h"

#include "WavFileLoader.h"
#include "OggFileLoader.h"
#include "decl/DeclarationCreator.h"

namespace sound
{

namespace
{

/// Sound directory name
constexpr const char* const SOUND_FOLDER = "sound/";
constexpr const char* const SOUND_FILE_EXTENSION = ".sndshd";

// Load the given file, trying different extensions (first OGG, then WAV) as fallback
ArchiveFilePtr openSoundFile(const std::string& fileName)
{
    // Try to open the file as it is
    auto file = GlobalFileSystem().openFile(fileName);

    if (file)
    {
        // File found, play it
        return file;
    }

    file = GlobalFileSystem().openFile(os::replaceExtension(fileName, ".ogg"));

    if (file)
    {
        return file;
    }

    // Try to open the file with .wav extension
    return GlobalFileSystem().openFile(os::replaceExtension(fileName, ".wav"));
}

}

SoundManager::SoundManager()
{
    decl::DeclarationBlockSyntax defaultBlock;
    defaultBlock.fileInfo = vfs::FileInfo("sound/", "_autogenerated_by_darkradiant_.sndshd", vfs::Visibility::HIDDEN);

    _emptyShader = std::make_unique<SoundShader>("");
    _emptyShader->setBlockSyntax(defaultBlock);
}

void SoundManager::forEachShader(std::function<void(const ISoundShader::Ptr&)> functor)
{
    GlobalDeclarationManager().foreachDeclaration(decl::Type::SoundShader,
        [&](const decl::IDeclaration::Ptr& soundShader)
        {
            functor(std::static_pointer_cast<ISoundShader>(soundShader));
        });
}

bool SoundManager::playSound(const std::string& fileName)
{
	return playSound(fileName, false);
}

bool SoundManager::playSound(const std::string& fileName, bool loopSound)
{
    auto file = openSoundFile(fileName);

	if (file && _soundPlayer)
	{
		_soundPlayer->play(*file, loopSound);
		return true;
	}

    return false;
}

void SoundManager::stopSound()
{
	if (_soundPlayer) _soundPlayer->stop();
}

ISoundShader::Ptr SoundManager::getSoundShader(const std::string& shaderName)
{
    auto found = std::static_pointer_cast<ISoundShader>(
        GlobalDeclarationManager().findDeclaration(decl::Type::SoundShader, shaderName)
    );

    return found ? found : _emptyShader;
}

const std::string& SoundManager::getName() const
{
	static std::string _name(MODULE_SOUNDMANAGER);
	return _name;
}

const StringSet& SoundManager::getDependencies() const
{
    static StringSet _dependencies
    { 
        MODULE_VIRTUALFILESYSTEM, 
        MODULE_COMMANDSYSTEM,
        MODULE_DECLMANAGER,
    };

	return _dependencies;
}

void SoundManager::initialiseModule(const IApplicationContext& ctx)
{
    // For now, redirect reloadSounds to the generic reloadDecls
    GlobalCommandSystem().addStatement("ReloadSounds", "ReloadDecls", false);

    // Create the SoundPlayer if sound is not disabled
    const auto& args = ctx.getCmdLineArgs();

    if (std::find(args.begin(), args.end(), "--disable-sound") == args.end())
    {
        rMessage() << "SoundManager: initialising sound playback" << std::endl;
        _soundPlayer = std::make_unique<SoundPlayer>();
    }
    else
    {
        rMessage() << "SoundManager: sound output disabled" << std::endl;
    }

    // Register the creator capable of constructing shaders and point the decl manager to the sound folder
    GlobalDeclarationManager().registerDeclType("sound", std::make_shared<decl::DeclarationCreator<SoundShader>>(decl::Type::SoundShader));
    GlobalDeclarationManager().registerDeclFolder(decl::Type::SoundShader, SOUND_FOLDER, SOUND_FILE_EXTENSION);

    // Route the decls reloaded signal to the local signal
    GlobalDeclarationManager().signal_DeclsReloaded(decl::Type::SoundShader).connect(
        [this]() { _sigSoundShadersReloaded.emit(); }
    );
}

float SoundManager::getSoundFileDuration(const std::string& vfsPath)
{
    auto file = openSoundFile(vfsPath);

    if (!file)
    {
        throw std::out_of_range("Could not resolve sound file " + vfsPath);
    }

    auto extension = string::to_lower_copy(os::getExtension(file->getName()));

    try
    {
        if (extension == "wav")
        {
            return WavFileLoader::GetDuration(file->getInputStream());
        }
        else if (extension == "ogg")
        {
            return OggFileLoader::GetDuration(*file);
        }
    }
    catch (const std::runtime_error& ex)
    {
        rError() << "Error determining sound file duration " << ex.what() << std::endl;
    }

    return 0.0f;
}

void SoundManager::reloadSounds()
{
    GlobalDeclarationManager().reloadDeclarations();
}

} // namespace sound
