#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_STREAMING

#include "fmod_async.h"
#include "fmod_codeci.h"
#include "fmod_channel_stream.h"
#include "fmod_file.h"
#include "fmod_sound_sample.h"
#include "fmod_sound_stream.h"
#include "fmod_systemi.h"

namespace FMOD
{


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
Stream::Stream()
{
    mLastPos          = 0;
    mSubSound         = 0;
    mLoopCountCurrent = -1;
    mBlockSize        = 1;
    mChannel          = 0;
    mInitialPosition  = 0;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT Stream::fill(unsigned int offset, unsigned int length, unsigned int *read_out, bool calledfromsentence)
{
    FMOD_RESULT result = FMOD_OK;
    unsigned int read = 0;
    Sample *sample = mSample;

    if (read_out)
    {
        *read_out = 0;
    }

    if (mSubSoundParent)
    {
        sample = ((Stream *)mSubSoundParent)->mSample;
    }

    if (!(mFlags & FMOD_SOUND_FLAG_FINISHED))
    {
        unsigned int r;
        unsigned int size;
        unsigned int len;

#ifdef FMOD_SUPPORT_SENTENCING
        if (mSubSoundList)
        {
            Stream *stream = this;
            int count = 0;

            do
            {       
                stream = SAFE_CAST(Stream, mSubSound[mSubSoundList[mChannel->mSubSoundListCurrent].mIndex]);
                if (!stream)
                {
                    if (count >= mSubSoundListNum)
                    {
                        return FMOD_ERR_SUBSOUNDS;
                    }
                    
                    count++;
                    mChannel->mSubSoundListCurrent++;
                    if (mChannel->mSubSoundListCurrent >= mSubSoundListNum)
                    {
                        if (!(mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent))
                        {
                            mChannel->mSubSoundListCurrent = mSubSoundListNum - 1;
                            mPosition = mLength;
                            mFlags |= FMOD_SOUND_FLAG_FINISHED;
                            return FMOD_ERR_FILE_EOF;
                        }
                        else
                        {
                            mChannel->mSubSoundListCurrent = 0;
                            mPosition = 0;
                        }
                    }
                }
            } while (!stream);

            len = length;
            do
            {
                unsigned int r;
                unsigned int endpoint;

                if (mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent)
                {
                    endpoint = mLoopStart + mLoopLength - 1;
                }
                else
                {
                    if (sample->mCodec->mFlags & FMOD_CODEC_ACCURATELENGTH)
                    {
                        endpoint = mLength - 1;
                    }
                    else
                    {
                        endpoint = (unsigned int)-1;
                    }
                }

                size = len;
                if (mPosition > endpoint)
                {
                    size = 0;
                }
                else if (mPosition + size > endpoint)
                {
                    size = (endpoint - mPosition) + 1;
                }

                result = stream->fill(offset, size, &r, true);
			    if ((result != FMOD_OK) && (result != FMOD_ERR_FILE_EOF))
			    {
				    return result;
			    }
                
                read += r;
                len -= r;
                size -= r;
                offset += r;

                mPosition += r;

                if (mPosition > endpoint || result == FMOD_ERR_FILE_EOF)
                {                
                    bool finished = false;
                
                    if (mPosition > endpoint)
                    {
                        if (mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent)
                        {
                            setPosition(mLoopStart, FMOD_TIMEUNIT_PCM);

                            if (mLoopCountCurrent > 0)
                            {
                                mLoopCountCurrent--;
                            }
                            
                            stream = SAFE_CAST(Stream, mSubSound[mSubSoundIndex]);
                        }
                        else
                        {
                            mPosition = mLength;
                            mFlags |= FMOD_SOUND_FLAG_FINISHED;
                            break;
                        }
                    }
                    else
                    {
                        do 
                        {
                            mChannel->mSubSoundListCurrent++;
                            if (mChannel->mSubSoundListCurrent >= mSubSoundListNum)
                            {
                                if (!(mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent))
                                {
                                    finished = true;
                                    mChannel->mSubSoundListCurrent = mSubSoundListNum - 1;
                                    mPosition = mLength;
                                    mFlags |= FMOD_SOUND_FLAG_FINISHED;
                                    break;
                                }
                                else
                                {
                                    mChannel->mSubSoundListCurrent = 0;
                                    mPosition = 0;
                                }
                            }

                            mSubSoundIndex = mSubSoundList[mChannel->mSubSoundListCurrent].mIndex;

                            stream = SAFE_CAST(Stream, mSubSound[mSubSoundIndex]);
                        
                            if (mSubSoundShared)
                            {
                                stream->updateSubSound(mSubSoundIndex, true);
                            }

                        } while (!stream && !finished);                             /* While loop skips past null entries. */

                        if (finished)
                        {
                            break;
                        }

                        if (stream)
                        {
                            sample->mCodec = stream->mCodec;   /* Switch parent level codec to the new substream's codec */
                            if (mCodec != sample->mCodec)
                            {
                                result = sample->seek(-1, 0);
                            }
                            else
                            {
                                result = sample->seek(mSubSoundIndex, 0);
                            }
                            stream->mPosition = 0;
                            stream->mFlags &= ~FMOD_SOUND_FLAG_FINISHED;    /* If it was read and finished before, the seek to 0 will unfinish it. */
                        }
                    }
                }

            } while (len);
        }
		else
#endif
        {
			len = length;
			do 
			{
				unsigned int endpoint, streamlength = mLength;
				Stream *stream = this;
                unsigned int bytestoread = 0;

				if (mSubSound)
				{
					stream = SAFE_CAST(Stream, mSubSound[mSubSoundIndex]);
					if (!stream)
					{
						break;
					}
				}
                
				if (mSubSoundShared)
				{
					FMOD_CODEC_WAVEFORMAT waveformat;

					stream->mCodec->mDescription.getwaveformat(stream->mCodec, mSubSoundIndex, &waveformat);

					streamlength = waveformat.lengthpcm;
				}
				else
				{
					streamlength = stream->mLength;
				}

				if (mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent && !calledfromsentence)   /* Ignore loop modes/points in a sentence. */
				{
					endpoint = stream->mLoopStart + stream->mLoopLength - 1;
				}
				else
				{
					if (sample->mCodec->mFlags & FMOD_CODEC_ACCURATELENGTH)
					{
						endpoint = streamlength - 1;
					}
					else
					{
						endpoint = (unsigned int)-1;
					}
				}

				size = len;

				if (offset + size > sample->mLength)
				{
					size = sample->mLength - offset;
				}
				if (stream->mPosition > endpoint)
				{
					size = 0;
				}
				else if (stream->mPosition + size > endpoint)
				{
					size = (endpoint - stream->mPosition) + 1;
				}

                /*
                    Handle unaligned read at the end.
                */
                getBytesFromSamples(size, &bytestoread, false);
                if (!bytestoread)
                {
                    r = 0;
                    result = FMOD_ERR_FILE_EOF;
                }
                else
                {
				    result = sample->read(offset, size, &r);
				    if ((result != FMOD_OK) && (result != FMOD_ERR_FILE_EOF) && (result != FMOD_ERR_FILE_DISKEJECTED))
				    {
					    return result;
				    }
                }
            
				if (result == FMOD_OK && !r && size)
				{
					break;
				}
            
				read += r;
				len -= r;
				size -= r;
				offset += r;
				if (offset >= sample->mLength)
				{
					offset = 0;
				}

				stream->mLastPos = stream->mPosition;
				stream->mPosition += r;

				if (read_out)
				{
					*read_out = read;
				}

				if (stream->mPosition > endpoint || result == FMOD_ERR_FILE_EOF)
				{
					if (calledfromsentence)
					{
						return FMOD_ERR_FILE_EOF; /* Let the parent sentence behavior take care of the transition. */
					}

					if (mMode & FMOD_LOOP_NORMAL && mLoopCountCurrent)
					{
						stream->mPosition = mLoopStart;

						if (mLength != (unsigned int)-1)
						{
							result = sample->seek(mSubSoundIndex, stream->mPosition);
							if (result != FMOD_OK)
							{
								return result;
							}

							stream->mPosition = sample->mPosition;
						}

						if (mLoopCountCurrent > 0)
						{
							mLoopCountCurrent--;
						}
					}
					else
					{
						if (stream != this)
						{
							mPosition = mLength;
							mFlags |= FMOD_SOUND_FLAG_FINISHED;
						}
						stream->mPosition = streamlength;
						stream->mFlags |= FMOD_SOUND_FLAG_FINISHED;

                        if (stream->mSubSoundParent)
                        {
                            stream->mSubSoundParent->mFlags |= FMOD_SOUND_FLAG_FINISHED;
                        }
						break;
					}
				}
				else
				{
					if (!r)
					{
						break;
					}
				}

			} while (len);
		}
    }

    if (read < length)
    {
        unsigned int size;
        unsigned int len;
                
        len = length - read;
        do 
        {
            size = len;
            if (offset + size > sample->mLength)
            {
                size = sample->mLength - offset;
            }

            sample->clear(offset, size);

            len -= size;
            offset += size;
            if (offset >= sample->mLength)
            {
                offset = 0;
            }
        } while (len);
    }

    return result;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT Stream::flush()
{
    if (mSample)
    {
        FMOD_RESULT result;
        unsigned int filllength;

        if (!(mMode & FMOD_OPENUSER) && mLength <= mSample->mLength && !mSubSoundList && mSample->mMode & FMOD_SOFTWARE)
        {
            filllength = mLength;
        }
        else
        {
            filllength = mSample->mLength;
        }

        result = fill(0, filllength);
        if (result != FMOD_OK && result != FMOD_ERR_FILE_EOF)
        {
            return result;
        }

        if (filllength < mSample->mLength)
        {
            result = mSample->clear(filllength, mSample->mLength - filllength);
        }
    }

    mFlags &= ~FMOD_SOUND_FLAG_WANTSTOFLUSH;

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT Stream::setPosition(unsigned int position, FMOD_TIMEUNIT postype)
{
    FMOD_RESULT result = FMOD_OK;
    bool seekable = true;
    unsigned int endpoint;

    if (postype == FMOD_TIMEUNIT_PCM)
    {  
        if (mMode & FMOD_LOOP_OFF)
        {
            endpoint = mLength - 1;
        }
        else
        {
            endpoint = mLoopStart + mLoopLength - 1;
        }

        if (position > endpoint)
        {
            return FMOD_ERR_INVALID_POSITION;
        }
    }

    if (mCodec->mFile)
    {
        seekable = mCodec->mFile->mFlags & FMOD_FILE_SEEKABLE ? true : false;
    }

    mFlags &= ~(FMOD_SOUND_FLAG_FINISHED | FMOD_SOUND_FLAG_THREADFINISHED);
    if (mSubSoundParent)
    {
        mSubSoundParent->mFlags &= ~(FMOD_SOUND_FLAG_FINISHED | FMOD_SOUND_FLAG_THREADFINISHED);
    }

    if (seekable)
    {
#ifdef FMOD_SUPPORT_SENTENCING
        if (mSubSound && mSubSoundList && postype == FMOD_TIMEUNIT_PCM)
        {
            int count;
            unsigned int offset = 0;

            for (count = 0; count < mSubSoundListNum; count++)
            {     
                SoundI *subsound = mSubSound[mSubSoundList[count].mIndex];

                if (subsound)
                {
                    FMOD_RESULT result;

                    if (!mSubSoundList)
                    {
                        return FMOD_ERR_INVALID_PARAM;
                    }

                    if (position >= offset && position < offset + mSubSoundList[count].mLength)
                    {
                        Stream *substream = SAFE_CAST(Stream, subsound);

                        mChannel->mSubSoundListCurrent = count;
                        mSubSoundIndex       = mSubSoundList[count].mIndex;

                        if (mSubSoundShared)    /* Same codec. */
                        {
                            result = substream->updateSubSound(mSubSoundIndex, true);
                        }
                        else
                        {
                            substream->mSubSoundIndex = mSubSoundIndex;
                            mSample->mCodec = substream->mCodec;   /* Switch parent level codec to the new substream's codec */
                        }
                        
                        result = substream->setPosition(position - offset, postype);
                        break;
                    }

                    offset += mSubSoundList[count].mLength;
                }
            }
        }
        else 
#endif
        if (mSubSound && postype == FMOD_TIMEUNIT_PCM)
        {
            SoundI *subsound = mSubSound[mSubSoundIndex];

            if (subsound)
            {
                Stream *substream = SAFE_CAST(Stream, subsound);

                result = substream->setPosition(position, postype);
            }
        }
        else
        {
            mCodec->reset();

            result = mCodec->setPosition(((mSubSoundParent && mSubSoundParent->mNumSubSounds) || mNumSubSounds) ? mSubSoundIndex : 0, position, postype);
            if (result != FMOD_OK)
            {
                return result;
            }

            if (mSubSoundParent)
            {
                mSubSoundParent->mSubSoundIndex = mSubSoundIndex;
            }
        }

        if (mSample && mSample->mPostSetPositionCallback)
        {
            mSample->mPostSetPositionCallback((FMOD_SOUND *)this, mSubSoundIndex, position, postype);
        }

        if (postype != FMOD_TIMEUNIT_MS && postype != FMOD_TIMEUNIT_PCM && postype != FMOD_TIMEUNIT_PCMBYTES)
        {
            position = 0;
        }

        mLastPos = mPosition = position;
    }
    else
    {
        if (mLastPos != 0 || position != 0)
        {
            return FMOD_ERR_FILE_COULDNOTSEEK;
        }
    }

    return result;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT Stream::getPosition(unsigned int *position, FMOD_TIMEUNIT postype)
{
    FMOD_RESULT result;

    if (mOpenState != FMOD_OPENSTATE_READY && mOpenState != FMOD_OPENSTATE_SETPOSITION)
    {
        return FMOD_ERR_NOTREADY;
    }

    if (!position)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    if (postype == (FMOD_TIMEUNIT)(FMOD_TIMEUNIT_SENTENCE_SUBSOUND | FMOD_TIMEUNIT_BUFFERED))
    {
        *position = mChannel->mSubSoundListCurrent;
    }
    else if (postype == FMOD_TIMEUNIT_PCM || postype == FMOD_TIMEUNIT_PCMBYTES || postype == FMOD_TIMEUNIT_MS)
    {
        if (postype == FMOD_TIMEUNIT_PCM)
        {
            *position = mLastPos;
        }
        else if (postype == FMOD_TIMEUNIT_MS)
        {
            *position = (unsigned int)((float)mLastPos / 1000.0f * mDefaultFrequency);
        }
        else if (postype == FMOD_TIMEUNIT_PCM)
        {            
            getBytesFromSamples(mLastPos, position);
        }
    }
    else
    {
        result = mCodec->getPosition(position, postype);
        if (result != FMOD_OK)
        {
            return result;
        }
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT Stream::setLoopCount(int loopcount)
{
    if (mOpenState != FMOD_OPENSTATE_READY && mOpenState != FMOD_OPENSTATE_SETPOSITION)
    {
        return FMOD_ERR_NOTREADY;
    }

    mLoopCountCurrent = loopcount;
    mLoopCount = loopcount;

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
#ifdef FMOD_SUPPORT_MEMORYTRACKER

FMOD_RESULT Stream::getMemoryUsedImpl(MemoryTracker *tracker)
{
    tracker->add(false, FMOD_MEMBITS_SOUND, sizeof(Stream) - sizeof(SoundI));

    if (mSample && (!mSubSoundParent || (mSubSoundParent && ((Stream *)mSubSoundParent)->mSample != mSample)))
    {
        CHECK_RESULT(mSample->getMemoryUsed(tracker));
    }

    if (mChannel && (!mSubSoundParent || (mSubSoundParent && ((Stream *)mSubSoundParent)->mChannel != mChannel)))
    {
        tracker->add(false, FMOD_MEMBITS_SOUND, sizeof(ChannelStream));
    }

    return SoundI::getMemoryUsedImpl(tracker);
}

#endif

}

#endif  /* #ifdef FMOD_SUPPORT_STREAMING */
