//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _THEORATEXTURE_H_
#define _THEORATEXTURE_H_

#ifdef TORQUE_OGGTHEORA

#ifndef _PLATFORM_H_
   #include "platform/platform.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
   #include "gfx/gfxTextureHandle.h"
#endif
#ifndef _ASYNCPACKETQUEUE_H_
   #include "platform/async/asyncPacketQueue.h"
#endif
#ifndef _ASYNCBUFFEREDSTREAM_H_
   #include "platform/async/asyncBufferedStream.h"
#endif
#ifndef _TIMESOURCE_H_
   #include "core/util/timeSource.h"
#endif
#ifndef _THREADSAFEREFCOUNT_H_
   #include "platform/threads/threadSafeRefCount.h"
#endif
#ifndef _RAWDATA_H_
   #include "core/util/rawData.h"
#endif
#ifndef _SIMOBJECT_H_
   #include "console/simObject.h"
#endif
#ifndef _SFXSTREAM_H_
   #include "sfx/sfxStream.h"
#endif
#ifndef _OGGTHEORADECODER_H_
   #include "core/ogg/oggTheoraDecoder.h"
#endif
#ifndef _TYPETRAITS_H_
   #include "platform/typetraits.h"
#endif



class SFXDescription;
class SFXSound;
class SFXVorbisStream;

class OggInputStream;
class OggVorbisDecoder;

class Stream;


/// A single frame in the video frame stream.
///
/// Frames are uploaded directly into textures by the asynchronous
/// streaming system.  This offloads as much work as possible to the worker
/// threads and guarantees the smoothest possible playback.
///
/// Frame records are re-used and are managed directly by the video frame
/// stream.  The number of textures concurrently used by a Theora stream
/// is determined by its stream read-ahead.
class TheoraTextureFrame
{
   public:
   
      typedef void Parent;
               
      /// The texture containing the video frame.
      GFXTexHandle mTexture;
      
      /// The locked rectangle, if the texture is currently locked.
      /// Frames will remain in locked state except if currently displayed.
      GFXLockedRect* mLockedRect;
      
      ///
      U32 mFrameNumber;
      
      /// The play time in seconds at which to display this frame.
      F32 mFrameTime;
      
      /// The duration in seconds to display this frame.
      F32 mFrameDuration;
      
      TheoraTextureFrame()
         : mLockedRect( NULL )
      {
      }
};


inline void destructSingle( TheoraTextureFrame* frame )
{
   // Do nothing.
}


/// TheoraTexture decodes Ogg Theora files, and their audio.
///
/// TheoraTexture objects can be used similarly to TextureObjects. Just
/// set the video, call play(), and then refresh every frame to get the
/// latest video. Audio happens automagically.
///
/// @note Uses Theora and ogg libraries which are Copyright (C) Xiph.org Foundation
class TheoraTexture : private IOutputStream< TheoraTextureFrame* >,
                      public IPositionable< U32 >
{
   public:
   
      typedef void Parent;
      
   protected:
         
      typedef IPositionable< U32 > TimeSourceType;
      typedef GenericTimeSource<> TimerType;      
      typedef AsyncPacketQueue< TheoraTextureFrame*, TimeSourceType*, IOutputStream< TheoraTextureFrame* >*, F32 > PlaybackQueueType;
      
      class FrameStream;
      class AsyncState;
      friend class GuiTheoraCtrl; // accesses OggTheoraDecoder to set transcoder
      
      /// Parameters for tuning streaming behavior.
      enum
      {
         /// Number of textures to load in background.
         FRAME_READ_AHEAD = 6,
      };
            
      /// WorkItem that reads a frame from a Theora decoder and uploads it into a TheoraTextureFrame.
      ///
      /// Loading directly into textures moves the costly uploads out of the main thread into worker
      /// threads.  The downside to this is that since we cannot do GFX work on the worker threads,
      /// we need to expect textures to get to us in locked state.
      class FrameReadItem : public ThreadWorkItem
      {
         public:
         
            typedef ThreadWorkItem Parent;
            
         protected:
         
            /// The asynchronous state we belong to.  This reference
            /// ensures that all our streaming state stays live for as long as our
            /// work item is in the pipeline.
            ThreadSafeRef< AsyncState > mAsyncState;
            
            ///
            FrameStream* mFrameStream;
            
            /// The frame texture we are loading.
            TheoraTextureFrame* mFrame;
            
            // WorkItem.
            virtual void execute();
            
         public:
         
            ///
            FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream,
                           ThreadPool::Context* context );
      };
      
      /// Stream filter that turns a stream of OggTheoraFrames into a buffered background stream of TheoraTextureFrame
      /// records.  
      ///
      /// This streams allocates a fixed amount 'M' of TheoraTextureFrames.  Reading the n-th frame from the stream, will
      /// automatically invalidate the (n-M)-th frame.
      class FrameStream : public AsyncSingleBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >*, FrameReadItem >
      {
         public:
         
            typedef AsyncSingleBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >*, FrameReadItem > Parent;
            
         protected:
         
            friend class FrameReadItem;
            
            enum
            {
               /// Number of TheoraTextureFrame records to allocate.
               ///
               /// We need to pre-allocate TheoraTextureFrames as we cannot do GFX operations
               /// on the fly on worker threads.  This number corresponds to the length of the
               /// buffering queue plus one record that will be returned to the user as the
               /// current frame record.
               NUM_FRAME_RECORDS = FRAME_READ_AHEAD + 1
            };
            
            /// Asynchronous state of the texture object.
            /// This is *NOT* a ThreadSafeRef to not create a cycle.
            AsyncState* mAsyncState;
            
            /// Wrap-around index into "mFrames."
            U32 mFrameIndex;
            
            /// The pre-allocated TheoraTextureFrames.
            TheoraTextureFrame mFrames[ NUM_FRAME_RECORDS ];
                     
         public:
         
            ///
            FrameStream( AsyncState* asyncState, bool looping = false );
            
            ///
            void acquireTextureLocks();
            
            ///
            void releaseTextureLocks();
      };
      
      /// Encapsulation of compound asynchronous state.  Allows releasing
      /// the entire state in one go.
      class AsyncState :   public ThreadSafeRefCount< AsyncState >,
                           private IPositionable< U32 >
      {
         public:
         
            typedef void Parent;
            friend class FrameStream; // mDecoderBufferStream
         
         protected:
         
            typedef AsyncSingleBufferedInputStream< OggTheoraFrame* > DecoderBufferStream;
         
            /// Last synchronization position in the video stream.  This is what the
            /// Theora decoder gets passed to see if frames are outdated.
            U32 mCurrentTime;
         
            /// The Ogg master stream.
            ThreadSafeRef< OggInputStream > mOggStream;
         
            /// The raw video decoding stream.
            OggTheoraDecoder* mTheoraDecoder;
            
            /// The raw sound decoding stream; NULL if no Vorbis in video or if
            /// Vorbis is streamed separately.
            OggVorbisDecoder* mVorbisDecoder;
            
            /// The background-buffered frame stream.
            ThreadSafeRef< FrameStream > mFrameStream;
            
         public:
            
            ///
            AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping = false );
            
            /// Return the Theora decoder substream.
            OggTheoraDecoder* getTheora() const { return mTheoraDecoder; }
            
            /// Return the Vorbis decoder substream.
            /// @note If Vorbis streaming is split out into a separate physical substream,
            ///   this method will always return NULL even if Vorbis sound is being used.
            OggVorbisDecoder* getVorbis() const { return mVorbisDecoder; }
            
            ///
            const ThreadSafeRef< FrameStream >& getFrameStream() const { return mFrameStream; }
                        
            ///
            TheoraTextureFrame* readNextFrame();
                        
            ///
            void start();
            
            ///
            void stop();
            
            ///
            bool isAtEnd();
            
            ///
            void syncTime( U32 ms ) { mCurrentTime = ms; }
            
         private:
         
            // IPositionable.
            virtual U32 getPosition() const { return mCurrentTime; }
            virtual void setPosition( U32 pos ) {}
      };
      
      /// The Theora video file.
      String mFilename;
      
      /// The SFXDescription used for sound playback.  Synthesized if not provided.
      SimObjectPtr< SFXDescription > mSFXDescription;
                  
      /// If there's a Vorbis stream, this is the sound source used for playback.
      /// Playback will synchronize to this source then.
      SimObjectPtr< SFXSound > mSFXSource;
      
      /// The current frame.
      TheoraTextureFrame* mCurrentFrame;
                        
      /// The queue that synchronizes the writing of frames to the TheoraTexture.
      PlaybackQueueType* mPlaybackQueue;
      
      /// The timer for synchronizing playback when there is no audio stream
      /// to synchronize to.
      TimerType mPlaybackTimer;
                  
      /// Our threaded state.
      ThreadSafeRef< AsyncState > mAsyncState;
      
      ///
      bool mIsPaused;
      
      ///
      U32 mLastFrameNumber;
      
      /// Number of frames we have dropped so far.
      U32 mNumDroppedFrames;

      /// Release all dynamic playback state.
      void _reset();
      
      /// Initialize video playback.
      void _initVideo();
      
      /// Initialize audio playback.
      void _initAudio( const ThreadSafeRef< SFXStream >& stream = NULL );
      
      /// Return the Theora decoder stream or NULL.
      OggTheoraDecoder* _getTheora() const { return ( mAsyncState != NULL ? mAsyncState->getTheora() : NULL ); }
      
      /// Return the Vorbis decoder stream or NULL.
      OggVorbisDecoder* _getVorbis() const { return ( mAsyncState != NULL ? mAsyncState->getVorbis() : NULL ); }
      
      /// Return the object that is acting as our time source.
      TimeSourceType* _getTimeSource() const;
      
      ///
      void _onTextureEvent( GFXTexCallbackCode code );
                  
      // IOutputStream.
      virtual void write( TheoraTextureFrame* const* frames, U32 num );

   public:
   
      ///
      TheoraTexture();
            
      ~TheoraTexture();

      /// Return the width of a single video frame in pixels.
      U32 getWidth() const;
      
      /// Return the height of a single video frame in pixels.
      U32 getHeight() const;
      
      /// Return the filename of the Theora video file loaded by the player.
      const String& getFilename() const { return mFilename; }

      /// Load the given Theora video file.  Set up audio using the given SFXDescription (must have
      /// streaming enabled).  If no SFXDescription is provided, a default description is used.
      bool setFile( const String& filename, SFXDescription* desc = NULL );
      
      /// Start video playback.
      void play();
      
      /// Pause video playback.
      void pause();
      
      /// Stop video playback.
      void stop();
      
      /// Refresh the current frame.  This should be called before getTexture() to ensure that
      /// the texture returned by getTexture() contains up-to-date contents.
      void refresh();
      
      /// Return true if a video file has been loaded and is ready for playback.
      bool isReady() const { return ( mAsyncState != NULL ); }
      
      /// Return true if the video is currently playing.
      bool isPlaying() const;
      
      /// Return true if the video is currently paused.
      bool isPaused() const { return mIsPaused; }
      
      /// Return the sequence number of the current frame.  Starts at 0.
      U32 getFrameNumber() const { return mCurrentFrame->mFrameNumber; }
      
      /// Return the playback position of the current frame.
      F32 getFrameTime() const { return mCurrentFrame->mFrameTime; }
      
      /// Return the number of frames that have been dropped so far.
      U32 getNumDroppedFrames() const { return mNumDroppedFrames; }
      
      /// Return the texture containing the current frame.  Call refresh() first
      /// to ensure the texture contents are up-to-date.
      const GFXTexHandle& getTexture() const { return mCurrentFrame->mTexture; }
      GFXTexHandle& getTexture() { return mCurrentFrame->mTexture; }
      
      // IPositionable.
      virtual U32 getPosition() const { return _getTimeSource()->getPosition(); }
      virtual void setPosition( U32 pos ) {} // Not (yet?) implemented.
};

#endif // TORQUE_OGGTHEORA
#endif // !_THEORATEXTURE_H_
