/** 
 * @file llaudiodecodemgr.cpp
 *
 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llaudiodecodemgr.h"

#include "llaudioengine.h"
#include "lllfsthread.h"
#include "llfilesystem.h"
#include "llstring.h"
#include "lldir.h"
#include "llendianswizzle.h"
#include "llassetstorage.h"
#include "llrefcount.h"
#include "threadpool.h"
#include "workqueue.h"

#include "llvorbisencode.h"

#include "vorbis/codec.h"
#include "vorbis/vorbisfile.h"
#include <iterator>
#include <deque>

extern LLAudioEngine *gAudiop;

static const S32 WAV_HEADER_SIZE = 44;


//////////////////////////////////////////////////////////////////////////////


class LLVorbisDecodeState : public LLThreadSafeRefCount
{
public:
	class WriteResponder : public LLLFSThread::Responder
	{
	public:
		WriteResponder(LLVorbisDecodeState* decoder) : mDecoder(decoder) {}
		~WriteResponder() {}
		void completed(S32 bytes)
		{
			mDecoder->ioComplete(bytes);
		}
		LLPointer<LLVorbisDecodeState> mDecoder;
	};
	
	LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename);

	BOOL initDecode();
	BOOL decodeSection(); // Return TRUE if done.
	BOOL finishDecode();

	void flushBadFile();

	void ioComplete(S32 bytes)			{ mBytesRead = bytes; }
	BOOL isValid() const				{ return mValid; }
	BOOL isDone() const					{ return mDone; }
	const LLUUID &getUUID() const		{ return mUUID; }

protected:
	virtual ~LLVorbisDecodeState();

	BOOL mValid;
	BOOL mDone;
	LLAtomicS32 mBytesRead;
	LLUUID mUUID;

	std::vector<U8> mWAVBuffer;
	std::string mOutFilename;
	LLLFSThread::handle_t mFileHandle;
	
	LLFileSystem *mInFilep;
	OggVorbis_File mVF;
	S32 mCurrentSection;
};

size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource)
{
	LLFileSystem *file = (LLFileSystem *)datasource;

	if (file->read((U8*)ptr, (S32)(size * nmemb)))	/*Flawfinder: ignore*/
	{
		S32 read = file->getLastBytesRead();
		return  read / size;	/*Flawfinder: ignore*/
	}
	else
	{
		return 0;
	}
}

S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence)
{
	LLFileSystem *file = (LLFileSystem *)datasource;

	// cache has 31-bit files
	if (offset > S32_MAX)
	{
		return -1;
	}

	S32 origin;
	switch (whence) {
	case SEEK_SET:
		origin = 0;
		break;
	case SEEK_END:
		origin = file->getSize();
		break;
	case SEEK_CUR:
		origin = -1;
		break;
	default:
		LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL;
		return -1;
	}

	if (file->seek((S32)offset, origin))
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

S32 cache_close (void *datasource)
{
	LLFileSystem *file = (LLFileSystem *)datasource;
	delete file;
	return 0;
}

long cache_tell (void *datasource)
{
	LLFileSystem *file = (LLFileSystem *)datasource;
	return file->tell();
}

LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename)
{
	mDone = FALSE;
	mValid = FALSE;
	mBytesRead = -1;
	mUUID = uuid;
	mInFilep = NULL;
	mCurrentSection = 0;
	mOutFilename = out_filename;
	mFileHandle = LLLFSThread::nullHandle();

    // No default value for mVF, it's an ogg structure?
	// Hey, let's zero it anyway, for predictability.
	memset(&mVF, 0, sizeof(mVF));
}

LLVorbisDecodeState::~LLVorbisDecodeState()
{
	if (!mDone)
	{
		delete mInFilep;
		mInFilep = NULL;
	}
}


BOOL LLVorbisDecodeState::initDecode()
{
	ov_callbacks cache_callbacks;
	cache_callbacks.read_func = cache_read;
	cache_callbacks.seek_func = cache_seek;
	cache_callbacks.close_func = cache_close;
	cache_callbacks.tell_func = cache_tell;

	LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL;

	mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND);
	if (!mInFilep || !mInFilep->getSize())
	{
		LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL;
		delete mInFilep;
		mInFilep = NULL;
		return FALSE;
	}

	S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks);
	if(r < 0) 
	{
		LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL;
		return(FALSE);
	}
	
	S32 sample_count = (S32)ov_pcm_total(&mVF, -1);
	size_t size_guess = (size_t)sample_count;
	vorbis_info* vi = ov_info(&mVF, -1);
	size_guess *= (vi? vi->channels : 1);
	size_guess *= 2;
	size_guess += 2048;
	
	bool abort_decode = false;
	
	if (vi)
	{
		if( vi->channels < 1 || vi->channels > LLVORBIS_CLIP_MAX_CHANNELS )
		{
			abort_decode = true;
			LL_WARNS("AudioEngine") << "Bad channel count: " << vi->channels << LL_ENDL;
		}
	}
	else // !vi
	{
		abort_decode = true;
		LL_WARNS("AudioEngine") << "No default bitstream found" << LL_ENDL;	
	}
	
	if( (size_t)sample_count > LLVORBIS_CLIP_REJECT_SAMPLES ||
	    (size_t)sample_count <= 0)
	{
		abort_decode = true;
		LL_WARNS("AudioEngine") << "Illegal sample count: " << sample_count << LL_ENDL;
	}
	
	if( size_guess > LLVORBIS_CLIP_REJECT_SIZE )
	{
		abort_decode = true;
		LL_WARNS("AudioEngine") << "Illegal sample size: " << size_guess << LL_ENDL;
	}
	
	if( abort_decode )
	{
		LL_WARNS("AudioEngine") << "Canceling initDecode. Bad asset: " << mUUID << LL_ENDL;
		vorbis_comment* comment = ov_comment(&mVF,-1);
		if (comment && comment->vendor)
		{
			LL_WARNS("AudioEngine") << "Bad asset encoded by: " << comment->vendor << LL_ENDL;
		}
		delete mInFilep;
		mInFilep = NULL;
		return FALSE;
	}

	try
	{
		mWAVBuffer.reserve(size_guess);
		mWAVBuffer.resize(WAV_HEADER_SIZE);
	}
	catch (std::bad_alloc&)
	{
		LL_WARNS("AudioEngine") << "Out of memory when trying to alloc buffer: " << size_guess << LL_ENDL;
		delete mInFilep;
		mInFilep = NULL;
		return FALSE;
	}

	{
		// write the .wav format header
		//"RIFF"
		mWAVBuffer[0] = 0x52;
		mWAVBuffer[1] = 0x49;
		mWAVBuffer[2] = 0x46;
		mWAVBuffer[3] = 0x46;

		// length = datalen + 36 (to be filled in later)
		mWAVBuffer[4] = 0x00;
		mWAVBuffer[5] = 0x00;
		mWAVBuffer[6] = 0x00;
		mWAVBuffer[7] = 0x00;

		//"WAVE"
		mWAVBuffer[8] = 0x57;
		mWAVBuffer[9] = 0x41;
		mWAVBuffer[10] = 0x56;
		mWAVBuffer[11] = 0x45;

		// "fmt "
		mWAVBuffer[12] = 0x66;
		mWAVBuffer[13] = 0x6D;
		mWAVBuffer[14] = 0x74;
		mWAVBuffer[15] = 0x20;

		// chunk size = 16
		mWAVBuffer[16] = 0x10;
		mWAVBuffer[17] = 0x00;
		mWAVBuffer[18] = 0x00;
		mWAVBuffer[19] = 0x00;

		// format (1 = PCM)
		mWAVBuffer[20] = 0x01;
		mWAVBuffer[21] = 0x00;

		// number of channels
		mWAVBuffer[22] = 0x01;
		mWAVBuffer[23] = 0x00;

		// samples per second
		mWAVBuffer[24] = 0x44;
		mWAVBuffer[25] = 0xAC;
		mWAVBuffer[26] = 0x00;
		mWAVBuffer[27] = 0x00;

		// average bytes per second
		mWAVBuffer[28] = 0x88;
		mWAVBuffer[29] = 0x58;
		mWAVBuffer[30] = 0x01;
		mWAVBuffer[31] = 0x00;

		// bytes to output at a single time
		mWAVBuffer[32] = 0x02;
		mWAVBuffer[33] = 0x00;
		 
		// 16 bits per sample
		mWAVBuffer[34] = 0x10;
		mWAVBuffer[35] = 0x00;

		// "data"
		mWAVBuffer[36] = 0x64;
		mWAVBuffer[37] = 0x61;
		mWAVBuffer[38] = 0x74;
		mWAVBuffer[39] = 0x61;

		// these are the length of the data chunk, to be filled in later
		mWAVBuffer[40] = 0x00;
		mWAVBuffer[41] = 0x00;
		mWAVBuffer[42] = 0x00;
		mWAVBuffer[43] = 0x00;
	}
	
	//{
		//char **ptr=ov_comment(&mVF,-1)->user_comments;
//		vorbis_info *vi=ov_info(&vf,-1);
		//while(*ptr){
		//	fprintf(stderr,"%s\n",*ptr);
		//	++ptr;
		//}
//    fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
//    fprintf(stderr,"\nDecoded length: %ld samples\n", (long)ov_pcm_total(&vf,-1));
//    fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor);
	//}
	return TRUE;
}

BOOL LLVorbisDecodeState::decodeSection()
{
	if (!mInFilep)
	{
		LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL;
		return TRUE;
	}
	if (mDone)
	{
// 		LL_WARNS("AudioEngine") << "Already done with decode, aborting!" << LL_ENDL;
		return TRUE;
	}
	char pcmout[4096];	/*Flawfinder: ignore*/

	BOOL eof = FALSE;
	long ret=ov_read(&mVF, pcmout, sizeof(pcmout), 0, 2, 1, &mCurrentSection);
	if (ret == 0)
	{
		/* EOF */
		eof = TRUE;
		mDone = TRUE;
		mValid = TRUE;
//			LL_INFOS("AudioEngine") << "Vorbis EOF" << LL_ENDL;
	}
	else if (ret < 0)
	{
		/* error in the stream.  Not a problem, just reporting it in
		   case we (the app) cares.  In this case, we don't. */

		LL_WARNS("AudioEngine") << "BAD vorbis decode in decodeSection." << LL_ENDL;

		mValid = FALSE;
		mDone = TRUE;
		// We're done, return TRUE.
		return TRUE;
	}
	else
	{
//			LL_INFOS("AudioEngine") << "Vorbis read " << ret << "bytes" << LL_ENDL;
		/* we don't bother dealing with sample rate changes, etc, but.
		   you'll have to*/
		std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer));
	}
	return eof;
}

BOOL LLVorbisDecodeState::finishDecode()
{
	if (!isValid())
	{
		LL_WARNS("AudioEngine") << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << LL_ENDL;
		return TRUE; // We've finished
	}

	if (mFileHandle == LLLFSThread::nullHandle())
	{
		ov_clear(&mVF);
  
		// write "data" chunk length, in little-endian format
		S32 data_length = mWAVBuffer.size() - WAV_HEADER_SIZE;
		mWAVBuffer[40] = (data_length) & 0x000000FF;
		mWAVBuffer[41] = (data_length >> 8) & 0x000000FF;
		mWAVBuffer[42] = (data_length >> 16) & 0x000000FF;
		mWAVBuffer[43] = (data_length >> 24) & 0x000000FF;
		// write overall "RIFF" length, in little-endian format
		data_length += 36;
		mWAVBuffer[4] = (data_length) & 0x000000FF;
		mWAVBuffer[5] = (data_length >> 8) & 0x000000FF;
		mWAVBuffer[6] = (data_length >> 16) & 0x000000FF;
		mWAVBuffer[7] = (data_length >> 24) & 0x000000FF;

		//
		// FUDGECAKES!!! Vorbis encode/decode messes up loop point transitions (pop)
		// do a cheap-and-cheesy crossfade 
		//
		{
			S16 *samplep;
			S32 i;
			S32 fade_length;
			char pcmout[4096];		/*Flawfinder: ignore*/ 	

			fade_length = llmin((S32)128,(S32)(data_length-36)/8);			
			if((S32)mWAVBuffer.size() >= (WAV_HEADER_SIZE + 2* fade_length))
			{
				memcpy(pcmout, &mWAVBuffer[WAV_HEADER_SIZE], (2 * fade_length));	/*Flawfinder: ignore*/
			}
			llendianswizzle(&pcmout, 2, fade_length);
	
			samplep = (S16 *)pcmout;
			for (i = 0 ;i < fade_length; i++)
			{
				*samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
				samplep++;
			}

			llendianswizzle(&pcmout, 2, fade_length);			
			if((WAV_HEADER_SIZE+(2 * fade_length)) < (S32)mWAVBuffer.size())
			{
				memcpy(&mWAVBuffer[WAV_HEADER_SIZE], pcmout, (2 * fade_length));	/*Flawfinder: ignore*/
			}
			S32 near_end = mWAVBuffer.size() - (2 * fade_length);
			if ((S32)mWAVBuffer.size() >= ( near_end + 2* fade_length))
			{
				memcpy(pcmout, &mWAVBuffer[near_end], (2 * fade_length));	/*Flawfinder: ignore*/
			}
			llendianswizzle(&pcmout, 2, fade_length);

			samplep = (S16 *)pcmout;
			for (i = fade_length-1 ; i >=  0; i--)
			{
				*samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
				samplep++;
			}
	
			llendianswizzle(&pcmout, 2, fade_length);			
			if (near_end + (2 * fade_length) < (S32)mWAVBuffer.size())
			{
				memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/
			}
		}

		if (36 == data_length)
		{
			LL_WARNS("AudioEngine") << "BAD Vorbis decode in finishDecode!" << LL_ENDL;
			mValid = FALSE;
			return TRUE; // we've finished
		}
		mBytesRead = -1;
		mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, mWAVBuffer.size(),
							 new WriteResponder(this));
	}

	if (mFileHandle != LLLFSThread::nullHandle())
	{
		if (mBytesRead >= 0)
		{
			if (mBytesRead == 0)
			{
				LL_WARNS("AudioEngine") << "Unable to write file in LLVorbisDecodeState::finishDecode" << LL_ENDL;
				mValid = FALSE;
				return TRUE; // we've finished
			}
		}
		else
		{
			return FALSE; // not done
		}
	}
	
	mDone = TRUE;

	LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL;

	return TRUE;
}

void LLVorbisDecodeState::flushBadFile()
{
	if (mInFilep)
	{
		LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL;
		mInFilep->remove();

		// <FS:ND> FIRE-15975; Delete the current file, or we might end with stale locks during the re-transfer
		delete mInFilep;
		mInFilep = NULL;
		// </FS:ND>
	}
}

//////////////////////////////////////////////////////////////////////////////

class LLAudioDecodeMgr::Impl
{
    friend class LLAudioDecodeMgr;
    Impl();
  public:

    void processQueue();

    void startMoreDecodes();
    void enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state);
    void checkDecodesFinished();

  protected:
    std::deque<LLUUID> mDecodeQueue;
    std::map<LLUUID, LLPointer<LLVorbisDecodeState>> mDecodes;
};

LLAudioDecodeMgr::Impl::Impl()
{
}

// Returns the in-progress decode_state, which may be an empty LLPointer if
// there was an error and there is no more work to be done.
LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id);

// Return true if finished
bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state);

void LLAudioDecodeMgr::Impl::processQueue()
{
    // First, check if any audio from in-progress decodes are ready to play. If
    // so, mark them ready for playback (or errored, in case of error).
    checkDecodesFinished();

    // Second, start as many decodes from the queue as permitted
    startMoreDecodes();
}

void LLAudioDecodeMgr::Impl::startMoreDecodes()
{
    llassert_always(gAudiop);

    LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
    // *NOTE: main_queue->postTo casts this refcounted smart pointer to a weak
    // pointer
    LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General");
    const LL::ThreadPool::ptr_t general_thread_pool = LL::ThreadPool::getInstance("General");
    llassert_always(main_queue);
    llassert_always(general_queue);
    llassert_always(general_thread_pool);
    // Set max decodes to double the thread count of the general work queue.
    // This ensures the general work queue is full, but prevents theoretical
    // buildup of buffers in memory due to disk writes once the
    // LLVorbisDecodeState leaves the worker thread (see
    // LLLFSThread::sLocal->write). This is probably as fast as we can get it
    // without modifying/removing LLVorbisDecodeState, at which point we should
    // consider decoding the audio during the asset download process.
    // -Cosmic,2022-05-11
    const size_t max_decodes = general_thread_pool->getWidth() * 2;

    while (!mDecodeQueue.empty() && mDecodes.size() < max_decodes)
    {
        const LLUUID decode_id = mDecodeQueue.front();
        mDecodeQueue.pop_front();

        // Don't decode the same file twice
        if (mDecodes.find(decode_id) != mDecodes.end())
        {
            continue;
        }
        if (gAudiop->hasDecodedFile(decode_id))
        {
            continue;
        }

        // Kick off a decode
        mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
        try
        {
            main_queue->postTo(
                general_queue,
                [decode_id]() // Work done on general queue
                {
                    LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);

                    if (!decode_state)
                    {
                        if (gAudiop)
                            gAudiop->markSoundCorrupt(decode_id);

                        // Audio decode has errored
                        return decode_state;
                    }

                    // Disk write of decoded audio is now in progress off-thread
                    return decode_state;
                },
                [decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
                mutable {
                    if (!gAudiop)
                    {
                        // There is no LLAudioEngine anymore. This might happen if
                        // an audio decode is enqueued just before shutdown.
                        return;
                    }

                    // At this point, we can be certain that the pointer to "this"
                    // is valid because the lifetime of "this" is dependent upon
                    // the lifetime of gAudiop.

                    enqueueFinishAudio(decode_id, decode_state);
                });
        }
        catch (const LLThreadSafeQueueInterrupt&)
        {
            // Shutdown
            // Consider making processQueue() do a cleanup instead
            // of starting more decodes
            LL_WARNS() << "Tried to start decoding on shutdown" << LL_ENDL;
        }
    }
}

LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;

    LL_DEBUGS() << "Decoding " << decode_id << " from audio queue!" << LL_ENDL;

    // <FS:Ansariel> Sound cache
    //std::string                    d_path       = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, decode_id.asString()) + ".dsf";
    std::string                    d_path       = gDirUtilp->getExpandedFilename(LL_PATH_FS_SOUND_CACHE, decode_id.asString()) + ".dsf";
    // </FS:Ansariel>
    LLPointer<LLVorbisDecodeState> decode_state = new LLVorbisDecodeState(decode_id, d_path);

    if (!decode_state->initDecode())
    {
        return NULL;
    }

    // Decode in a loop until we're done
    while (!decode_state->decodeSection())
    {
        // decodeSection does all of the work above
    }

    if (!decode_state->isDone())
    {
        // Decode stopped early, or something bad happened to the file
        // during decoding.
        LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data or decode has been canceled, aborting decode" << LL_ENDL;
        decode_state->flushBadFile();
        return NULL;
    }

    if (!decode_state->isValid())
    {
        // We had an error when decoding, abort.
        LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data, aborting decode" << LL_ENDL;
        decode_state->flushBadFile();
        return NULL;
    }

    // Kick off the writing of the decoded audio to the disk cache.
    // The receiving thread can then cheaply call finishDecode() again to check
    // if writing has finished. Someone has to hold on to the refcounted
    // decode_state to prevent it from getting destroyed during write.
    decode_state->finishDecode();

    return decode_state;
}

void LLAudioDecodeMgr::Impl::enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state)
{
    // Assumed fast
    if (tryFinishAudio(decode_id, decode_state))
    {
        // Done early!
        auto decode_iter = mDecodes.find(decode_id);
        llassert(decode_iter != mDecodes.end());
        mDecodes.erase(decode_iter);
        return;
    }

    // Not done yet... enqueue it
    mDecodes[decode_id] = decode_state;
}

void LLAudioDecodeMgr::Impl::checkDecodesFinished()
{
    auto decode_iter = mDecodes.begin();
    while (decode_iter != mDecodes.end())
    {
        const LLUUID& decode_id = decode_iter->first;
        const LLPointer<LLVorbisDecodeState>& decode_state = decode_iter->second;
        if (tryFinishAudio(decode_id, decode_state))
        {
            decode_iter = mDecodes.erase(decode_iter);
        }
        else
        {
            ++decode_iter;
        }
    }
}

bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state)
{
    // decode_state is a file write in progress unless finished is true
    bool finished = decode_state && decode_state->finishDecode();
    if (!finished)
    {
        return false;
    }

    llassert_always(gAudiop);

    LLAudioData *adp = gAudiop->getAudioData(decode_id);
    if (!adp)
    {
        LL_WARNS("AudioEngine") << "Missing LLAudioData for decode of " << decode_id << LL_ENDL;
        return true;
    }

    bool valid = decode_state && decode_state->isValid();
    // Mark current decode finished regardless of success or failure
    adp->setHasCompletedDecode(true);
    // Flip flags for decoded data
    adp->setHasDecodeFailed(!valid);
    adp->setHasDecodedData(valid);
    // When finished decoding, there will also be a decoded wav file cached on
    // disk with the .dsf extension
    if (valid)
    {
        adp->setHasWAVLoadFailed(false);
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////////

LLAudioDecodeMgr::LLAudioDecodeMgr()
{
    mImpl = new Impl();
}

LLAudioDecodeMgr::~LLAudioDecodeMgr()
{
    delete mImpl;
    mImpl = nullptr;
}

void LLAudioDecodeMgr::processQueue()
{
    mImpl->processQueue();
}

BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
{
	// <FS:ND> Protect against corrupted sounds. Just do a quit exit instead of trying to decode over and over.
	if (gAudiop && gAudiop->isCorruptSound(uuid))
		return FALSE;
	// </FS:ND>

	if (gAudiop && gAudiop->hasDecodedFile(uuid))
	{
		// Already have a decoded version, don't need to decode it.
		LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has decoded file already" << LL_ENDL;
		return TRUE;
	}

	if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
	{
		// Just put it on the decode queue.
		LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has local asset file already" << LL_ENDL;
		// <FS:Ansariel> FIRE-480: Opening multiple instances causes sound failures
		//mImpl->mDecodeQueue.push_back(uuid);
		// ...only add it if it's note already in the queue
		if (std::find(mImpl->mDecodeQueue.begin(), mImpl->mDecodeQueue.end(), uuid) == mImpl->mDecodeQueue.end())
		{
			mImpl->mDecodeQueue.push_back(uuid);
		}
		// </FS:Ansariel>
		return TRUE;
	}

	LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " no file available" << LL_ENDL;
	return FALSE;
}
