// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


#ifndef GNASH_NETSTREAM_H
#define GNASH_NETSTREAM_H


#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif

#include <atomic>
#include <string>
#include <memory>
#include <mutex>

#include <boost/intrusive_ptr.hpp>
#include <boost/ptr_container/ptr_deque.hpp>

#include "PlayHead.h" // for composition
#include "Relay.h" // for ActiveRelay inheritance
#include <memory>

// Forward declarations
namespace gnash {
    class InterruptableVirtualClock;
    class as_object;
    class CharacterProxy;
    class IOChannel;
    class NetConnection_as;
    class as_function;
    class DisplayObject;
    struct ObjectURI;
    namespace image {
        class GnashImage;
    }
    namespace media {
        class MediaHandler;
        class AudioDecoder;
        class AudioInfo;
        class MediaParser;
        class VideoDecoder;
        class VideoInfo;
    }
    namespace sound {
        class sound_handler;
        class InputStream;
    }
}

namespace gnash {

/// Buffered AudioStreamer
//
/// This class you create passing a sound handler, which will
/// be used to implement attach/detach and eventually throw away
/// buffers of sound when no sound handler is given.
///
/// Then you push samples to a buffer of it and can request attach/detach 
/// operations. When attached, the sound handler will fetch samples
/// from the buffer, in a thread-safe way.
///
class BufferedAudioStreamer {
public:

    /// @param handler
    ///     %Sound handler to use for attach/detach
    ///
    BufferedAudioStreamer(sound::sound_handler* handler);

    /// A buffer with a cursor state
    //
    /// @todo Make private, have ::push take a simpler
    ///       form (Buffer?)
    ///
    class CursoredBuffer
    {
    public:
        CursoredBuffer()
            :
            m_size(0),
            m_data(nullptr),
            m_ptr(nullptr)
        {}

        ~CursoredBuffer()
        {
            delete [] m_data;
        }

        /// Number of samples left in buffer starting from cursor
        std::uint32_t m_size;

        /// Actual data
        //
        /// The data must be allocated with new []
        /// as will be delete []'d by the dtor
        std::uint8_t* m_data;

        /// Cursor into the data
        std::uint8_t* m_ptr;
    };

    typedef boost::ptr_deque<CursoredBuffer> AudioQueue;

    // Delete all samples in the audio queue.
    void cleanAudioQueue();

    sound::sound_handler* _soundHandler;

    /// This is where audio frames are pushed by ::advance
    /// and consumed by sound_handler callback (audio_streamer)
    AudioQueue _audioQueue;

    /// Number of bytes in the audio queue, protected by _audioQueueMutex
    size_t _audioQueueSize;

    /// The queue needs to be protected as sound_handler callback
    /// is invoked by a separate thread (dunno if it makes sense actually)
    std::mutex _audioQueueMutex;

    // Id of an attached audio streamer, 0 if none
    sound::InputStream* _auxStreamer;

    /// Attach the aux streamer.
    //
    /// On success, _auxStreamerAttached will be set to true.
    /// Won't attach again if already attached.
    ///
    void attachAuxStreamer();

    /// Detach the aux streamer
    //
    /// _auxStreamerAttached will be set to true.
    /// Won't detach if not attached.
    ///
    void detachAuxStreamer();

    /// Fetch samples from the audio queue
    unsigned int fetch(std::int16_t* samples, unsigned int nSamples,
                    bool& eof);

    /// Fetch samples from the audio queue
    static unsigned int fetchWrapper(void* owner, std::int16_t* samples,
                    unsigned int nSamples, bool& eof);

    /// Push a buffer to the audio queue
    //
    /// @param audio
    ///     Samples buffer, ownership transferred.
    ///
    /// @todo: take something simpler (SimpleBuffer?)
    ///
    void push(CursoredBuffer* audio);

};

// -----------------------------------------------------------------

/// NetStream_as ActionScript class
//
/// This class is responsible for handlign external
/// media files. Provides interfaces for playback control.
///
class NetStream_as : public ActiveRelay
{

public:

    enum PauseMode {
      pauseModeToggle = -1,
      pauseModePause = 0,
      pauseModeUnPause = 1  
    };

    NetStream_as(as_object* owner);

    ~NetStream_as();

    PlayHead::PlaybackStatus playbackState() const {
        return _playHead.getState();
    }

    /// Get the real height of the video in pixels if the decoder exists.
    //
    /// @return the height of the video in pixels or 0 if no decoder exists.
    ///         The width returned from the decoder may also vary, and will
    ///         be 0 until it knows the width.
    int videoHeight() const;

    /// Get the real width of the video in pixels if the decoder exists.
    //
    /// @return the width of the video in pixels or 0 if no decoder exists.
    ///         The width returned from the decoder may also vary, and will
    ///         be 0 until it knows the width.
    int videoWidth() const;

    /// Closes the video session and frees all ressources used for decoding
    /// except the FLV-parser (this might not be correct).
    void close();

    /// Make audio controlled by given DisplayObject
    void setAudioController(DisplayObject* controller);
 
    /// Pauses/starts the playback of the media played by the current instance
    //
    /// @param mode
    /// Defines what mode to put the instance in.
    void pause(PauseMode mode);

    /// Starts the playback of the media
    //
    /// @param source
    /// Defines what file to play
    ///
    void play(const std::string& source);

    /// Seek in the media played by the current instance
    //
    /// @param pos
    ///     Defines in seconds where to seek to
    ///     @todo take milliseconds !!
    ///
    void seek(std::uint32_t pos);

    /// Tells where the playhead currently is
    //
    /// @return The time in milliseconds of the current playhead position
    ///
    std::int32_t time();

    /// Called at the heart-beat frequency. Used to process queued status messages
    /// and (re)start after a buffering pause. In NetStreamFfmpeg it is also
    /// used to find the next video frame to be shown, though this might
    /// change.
    void update();
    
    /// Returns the current framerate in frames per second.
    double getCurrentFPS()  { return 0; }

    /// Sets the NetConnection needed to access external files
    //
    /// @param nc
    ///     The NetConnection object to use for network access
    ///
    void setNetCon(NetConnection_as* nc) {
        _netCon = nc;
    }

    /// Return true if the NetStream has an associated NetConnection
    bool isConnected() const { return (_netCon); }

    /// Specifies the number of milliseconds to buffer before starting
    /// to display the stream.
    //
    /// @param time
    /// The time in milliseconds that should be buffered.
    ///
    void setBufferTime(std::uint32_t time);

    /// Returns what the buffer time has been set to. (100 milliseconds
    /// is default)
    //
    /// @return The size of the buffer in milliseconds.
    ///
    std::uint32_t bufferTime() { return _bufferTime; }

    /// Returns the number of bytes of the media file that have been buffered.
    long bytesLoaded();

    /// Returns the total number of bytes (size) of the media file
    //
    /// @return the total number of bytes (size) of the media file
    ///
    long bytesTotal();

    /// Returns the number of millisecond of the media file that is
    /// buffered and yet to be played
    //
    /// @return Returns the number of millisecond of the media file that is 
    /// buffered and yet to be played
    ///
    long bufferLength();

    /// Returns the video frame closest to current cursor. See time().
    //
    /// @return a image containing the video frame, a NULL unique_ptr if
    /// none were ready
    ///
    std::unique_ptr<image::GnashImage> get_video();
    
    /// Register the DisplayObject to invalidate on video updates
    void setInvalidatedVideo(DisplayObject* ch)
    {
        _invalidatedVideoCharacter = ch;
    }

    virtual void markReachableResources() const;

    /// Callback used by sound_handler to get audio data
    //
    /// This is a sound_handler::aux_streamer_ptr type.
    ///
    /// It might be invoked by a separate thread (neither main,
    /// nor decoder thread).
    ///
    static unsigned int audio_streamer(void *udata, std::int16_t* samples,
            unsigned int nSamples, bool& eof);

private:
    
    /// Status codes used for notifications
    enum StatusCode {
    
        // Internal status, not a valid ActionScript value
        invalidStatus,

        /// NetStream.Buffer.Empty (level: status)
        bufferEmpty,

        /// NetStream.Buffer.Full (level: status)
        bufferFull,

        /// NetStream.Buffer.Flush (level: status)
        bufferFlush,

        /// NetStream.Play.Start (level: status)
        playStart,

        /// NetStream.Play.Stop  (level: status)
        playStop,

        /// NetStream.Seek.Notify  (level: status)
        seekNotify,

        /// NetStream.Play.StreamNotFound (level: error)
        streamNotFound,

        /// NetStream.Seek.InvalidTime (level: error)
        invalidTime
    };

    enum DecodingState {
        DEC_NONE,
        DEC_STOPPED,
        DEC_DECODING,
        DEC_BUFFERING
    };

    typedef std::pair<std::string, std::string> NetStreamStatus;

    /// Get 'status' (first) and 'level' (second) strings for given status code
    //
    /// Any invalid code, out of bound or explicitly invalid (invalidCode) 
    /// returns two empty strings.
    ///
    void getStatusCodeInfo(StatusCode code, NetStreamStatus& info);

    /// Return a newly allocated information object for the given status
    as_object* getStatusObject(StatusCode code);

    /// Initialize video decoder and (if successful) PlayHead consumer 
    //
    /// @param info Video codec information
    ///
    void initVideoDecoder(const media::VideoInfo& info);

    /// Initialize audio decoder and (if successful) a PlayHead consumer 
    //
    /// @param info Audio codec information
    ///
    void initAudioDecoder(const media::AudioInfo& parser);

    // Setups the playback
    bool startPlayback();

    // Pauses the playhead 
    //
    // Users:
    //  - ::decodeFLVFrame() 
    //  - ::pause() 
    //  - ::play() 
    //
    void pausePlayback();

    // Resumes the playback 
    //
    // Users:
    //  - ::av_streamer() 
    //  - ::play() 
    //  - ::startPlayback() 
    //  - ::advance() 
    //
    void unpausePlayback();

    /// Update the image/videoframe to be returned by next get_video() call.
    //
    /// Used by update().
    ///
    /// Note that get_video will be called by Video::display(), which
    /// is usually called right after Video::advance(), so the result
    /// is that refreshVideoFrame() is called right before
    /// get_video(). This is important
    /// to ensure timing is correct..
    ///
    /// @param alsoIfPaused
    /// If true, video is consumed/refreshed even if playhead is paused.
    /// By default this is false, but will be used on ::seek (user-reguested)
    ///
    void refreshVideoFrame(bool alsoIfPaused = false);

    /// Refill audio buffers, so to contain new frames since last run
    /// and up to current timestamp
    void refreshAudioBuffer();

    /// Decode next video frame fetching it MediaParser cursor
    //
    /// @return 0 on EOF or error, a decoded video otherwise
    ///
    std::unique_ptr<image::GnashImage> decodeNextVideoFrame();

    /// Decode next audio frame fetching it MediaParser cursor
    //
    /// @return 0 on EOF or error, a decoded audio frame otherwise
    ///
    BufferedAudioStreamer::CursoredBuffer* decodeNextAudioFrame();

    /// \brief
    /// Decode input audio frames with timestamp <= ts
    /// and push them to the output audio queue
    void pushDecodedAudioFrames(std::uint32_t ts);

    /// Decode input frames up to the one with timestamp <= ts.
    //
    /// Decoding starts from "next" element in the parser cursor.
    ///
    /// Return 0 if:
    /// 1. there's no parser active.
    /// 2. parser cursor is already on last frame.
    /// 3. next element in cursor has timestamp > tx
    /// 4. there was an error decoding
    ///
    std::unique_ptr<image::GnashImage> getDecodedVideoFrame(std::uint32_t ts);

    DecodingState decodingStatus(DecodingState newstate = DEC_NONE);

    /// Parse a chunk of input
    /// Currently blocks, ideally should parse as much
    /// as possible w/out blocking
    void parseNextChunk();

    /// Set stream status.
    //
    /// Valid statuses are:
    ///
    /// Status level:
    ///  - NetStream.Buffer.Empty
    ///  - NetStream.Buffer.Full
    ///  - NetStream.Buffer.Flush
    ///  - NetStream.Play.Start
    ///  - NetStream.Play.Stop 
    ///  - NetStream.Seek.Notify 
    ///
    /// Error level:
    ///  - NetStream.Play.StreamNotFound
    ///  - NetStream.Seek.InvalidTime
    ///
    /// This method locks the statusMutex during operations
    ///
    void setStatus(StatusCode code);

    /// \brief
    /// Call any onStatus event handler passing it
    /// any queued status change, see _statusQueue
    //
    /// Will NOT lock the statusMutex itself, rather it will
    /// iteratively call the popNextPendingStatusNotification()
    /// private method, which will take care of locking it.
    /// This is to make sure onStatus handler won't call methods
    /// possibly trying to obtain the lock again (::play, ::pause, ...)
    ///
    void processStatusNotifications();

    /// Unplug the advance timer callback
    void stopAdvanceTimer();

    /// Register the advance timer callback
    void startAdvanceTimer();

    NetConnection_as* _netCon;

    std::unique_ptr<CharacterProxy> _audioController;
    
    // The size of the buffer in milliseconds
    std::uint32_t _bufferTime;

    // Mutex to insure we don't corrupt the image
    std::mutex image_mutex;

    // The image/videoframe which is given to the renderer
    std::unique_ptr<image::GnashImage> _imageframe;

    // The video URL
    std::string url;

    // The input media parser
    std::unique_ptr<media::MediaParser> _parser;

    // The position in the inputfile, only used when not playing a FLV
    long _inputPos;

    /// The DisplayObject to invalidate on video updates
    DisplayObject* _invalidatedVideoCharacter;

    /// Stores DecodingState
    std::atomic<int> _decoding_state;

    /// Video decoder
    std::unique_ptr<media::VideoDecoder> _videoDecoder;

    /// True if video info are known
    bool _videoInfoKnown;

    /// Audio decoder
    std::unique_ptr<media::AudioDecoder> _audioDecoder;

    /// True if an audio info are known
    bool _audioInfoKnown;

    /// Virtual clock used as playback clock source
    std::unique_ptr<InterruptableVirtualClock> _playbackClock;

    /// Playback control device 
    PlayHead _playHead;

    // Current sound handler
    sound::sound_handler* _soundHandler;

    // Current media handler
    media::MediaHandler* _mediaHandler;

    /// Input stream
    //
    /// This should just be a temporary variable, transferred
    /// to MediaParser constructor.
    ///
    std::unique_ptr<IOChannel> _inputStream;

    /// The buffered audio streamer
    BufferedAudioStreamer _audioStreamer;

    /// List of status messages to be processed
    //
    /// Stores StatusCode
    std::atomic<int> _statusCode;
};

void netstream_class_init(as_object& global, const ObjectURI& uri);

void registerNetStreamNative(as_object& global);

} // gnash namespace

#endif

