/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla code.
 *
 * The Initial Developer of the Original Code is the Mozilla Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Chris Double <chris.double@double.co.nz>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#include "nsIDOMHTMLMediaElement.h"
#include "nsGenericHTMLElement.h"
#include "nsMediaDecoder.h"
#include "nsIChannel.h"
#include "nsThreadUtils.h"

// Define to output information on decoding and painting framerate
/* #define DEBUG_FRAME_RATE 1 */

typedef PRUint16 nsMediaNetworkState;
typedef PRUint16 nsMediaReadyState;

// Object representing a single execution of the media load algorithm.
// Used by asynchronous events so that they can be cancelled when Load()
// is executed. Holds the list of candidate resources which the media
// element attempts to load, and provides iteration over that list.
class nsMediaLoad : public nsISupports
{
public:
  NS_DECL_ISUPPORTS
  nsMediaLoad() : mPosition(0) {}
  ~nsMediaLoad() {}

  // Appends a candidate resource to the candidate list. List is populated
  // by nsHTMLMediaElement::GenerateCandidates().
  void AddCandidate(nsIURI *aURI);

  // Returns the next candidate in the list, or null if at the end.
  already_AddRefed<nsIURI> GetNextCandidate();

  PRBool HasMoreCandidates() { return mPosition < mCandidates.Count(); }
private:
  // Candidate resource URIs.
  nsCOMArray<nsIURI> mCandidates;

  // Index/iterator position in mCandidates.
  PRInt32 mPosition;
};

class nsHTMLMediaElement : public nsGenericHTMLElement
{
public:
  nsHTMLMediaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
  virtual ~nsHTMLMediaElement();

  /**
   * This is used when the browser is constructing a video element to play
   * a channel that we've already started loading. The src attribute and
   * <source> children are ignored. 
   * @param aChannel the channel to use
   * @param aListener returns a stream listener that should receive
   * notifications for the stream
   */ 
  nsresult LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener);

  // nsIDOMHTMLMediaElement
  NS_DECL_NSIDOMHTMLMEDIAELEMENT

  virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                nsIAtom* aAttribute,
                                const nsAString& aValue,
                                nsAttrValue& aResult);
  // SetAttr override.  C++ is stupid, so have to override both
  // overloaded methods.
  nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                   const nsAString& aValue, PRBool aNotify)
  {
    return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
  }
  virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                           nsIAtom* aPrefix, const nsAString& aValue,
                           PRBool aNotify);

  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                              nsIContent* aBindingParent,
                              PRBool aCompileEventHandlers);
  virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                              PRBool aNullParent = PR_TRUE);

  virtual PRBool IsDoneAddingChildren();
  virtual nsresult DoneAddingChildren(PRBool aHaveNotified);
  virtual void DestroyContent();

  // Called by the video decoder object, on the main thread,
  // when it has read the metadata containing video dimensions,
  // etc.
  void MetadataLoaded();

  // Called by the video decoder object, on the main thread,
  // when it has read the first frame of the video
  void FirstFrameLoaded();

  // Called by the video decoder object, on the main thread,
  // when the resource has completed downloading.
  void ResourceLoaded();

  // Called by the video decoder object, on the main thread,
  // when the resource has a network error during loading.
  void NetworkError();

  // Called by the video decoder object, on the main thread,
  // when the video playback has ended.
  void PlaybackEnded();

  // Called by the video decoder object, on the main thread,
  // when the resource has started seeking.
  void SeekStarted();

  // Called by the video decoder object, on the main thread,
  // when the resource has completed seeking.
  void SeekCompleted();

  // Draw the latest video data. See nsMediaDecoder for 
  // details.
  void Paint(gfxContext* aContext, const gfxRect& aRect);

  // Dispatch events
  nsresult DispatchSimpleEvent(const nsAString& aName);
  nsresult DispatchProgressEvent(const nsAString& aName);
  nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
  nsresult DispatchAsyncProgressEvent(const nsAString& aName);

  // Called by the decoder when some data has been downloaded or
  // buffering/seeking has ended. aNextFrameAvailable is true when
  // the data for the next frame is available. This method will
  // decide whether to set the ready state to HAVE_CURRENT_DATA,
  // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
  enum NextFrameStatus {
    // The next frame of audio/video is available
    NEXT_FRAME_AVAILABLE,
    // The next frame of audio/video is unavailable because the decoder
    // is paused while it buffers up data
    NEXT_FRAME_UNAVAILABLE_BUFFERING,
    // The next frame of audio/video is unavailable for some other reasons
    NEXT_FRAME_UNAVAILABLE
  };
  void UpdateReadyStateForData(NextFrameStatus aNextFrame);

  // Use this method to change the mReadyState member, so required
  // events can be fired.
  void ChangeReadyState(nsMediaReadyState aState);

  // Gets the pref media.enforce_same_site_origin, which determines
  // if we should check Access Controls, or allow cross domain loads.
  PRBool ShouldCheckAllowOrigin();

  // Is the media element potentially playing as defined by the HTML 5 specification.
  // http://www.whatwg.org/specs/web-apps/current-work/#potentially-playing
  PRBool IsPotentiallyPlaying() const;

  // Has playback ended as defined by the HTML 5 specification.
  // http://www.whatwg.org/specs/web-apps/current-work/#ended
  PRBool IsPlaybackEnded() const;

  // principal of the currently playing stream
  nsIPrincipal* GetCurrentPrincipal();

  // Update the visual size of the media. Called from the decoder on the
  // main thread when/if the size changes.
  void UpdateMediaSize(nsIntSize size);

  // Handle moving into and out of the bfcache by pausing and playing
  // as needed.
  void Freeze();
  void Thaw();

  // Returns true if we can handle this MIME type in a <video> or <audio>
  // element.
  // If it returns true, then it also returns a null-terminated list
  // of supported codecs in *aSupportedCodecs, and a null-terminated list
  // of codecs that *may* be supported in *aMaybeSupportedCodecs. These
  // lists should not be freed, they area static data.
  static PRBool CanHandleMediaType(const char* aMIMEType,
                                   const char*** aSupportedCodecs,
                                   const char*** aMaybeSupportedCodecs);

  /**
   * Initialize data for available media types
   */
  static void InitMediaTypes();
  /**
   * Shutdown data for available media types
   */
  static void ShutdownMediaTypes();

  /**
   * Called when a child source element is added to this media element. This
   * may queue a load() task if appropriate.
   */
  void NotifyAddedSource();

  virtual PRBool IsNodeOfType(PRUint32 aFlags) const;

  /**
   * Returns the current nsMediaLoad object. Asynchronous events store a
   * reference to the nsMediaLoad object that was current when they were
   * enqueued, and if it has changed when they come to fire, they consider
   * themselves cancelled, and don't fire.
   */
  nsMediaLoad* GetCurrentMediaLoad() { return mCurrentLoad; }


protected:
  class MediaLoadListener;
  class LoadNextCandidateEvent;

  /**
   * Figure out which resource to load (either the 'src' attribute or a
   * <source> child) and return the associated URI.
   */
  nsresult PickMediaElement(nsIURI** aURI);
  /**
   * Create a decoder for the given aMIMEType. Returns false if we
   * were unable to create the decoder.
   */
  PRBool CreateDecoder(const nsACString& aMIMEType);
  /**
   * Initialize a decoder to load the given channel. The decoder's stream
   * listener is returned via aListener.
   */
  nsresult InitializeDecoderForChannel(nsIChannel *aChannel,
                                       nsIStreamListener **aListener);
  /**
   * Execute the initial steps of the load algorithm that ensure existing
   * loads are aborted, the element is emptied, and a new load object is
   * created. Returns true if the current load aborts due to a new load being
   * started in event handlers triggered during this function call.
   */
  PRBool AbortExistingLoads();

  /**
   * Create a URI for the given aURISpec string.
   */
  nsresult NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI);

  /**
   * Does step 12 of the media load() algorithm, sends error/emptied events to
   * to the media element, and reset network/begun state.
   */
  void NoSupportedMediaError();

  // Performs "the candidate loop" step in the load algorithm. Attempts to
  // load candidates in the candidate media resource list until a channel opens
  // to a resource, then it returns. If the resource download fails, it will
  // set a callback to this function. Do not call this directly, call
  // QueueLoadNextCandidateTask() instead.
  void LoadNextCandidate();

  // Populates the candidate resource list in mCurrentLoad.
  void GenerateCandidates();

  // Enqueues an event to call Load() on the main thread. This will begin
  // the process of attempting to load candidate media resources from the
  // candidate resource list.
  void QueueLoadTask();

  // Enqueues an event to resume trying to open resources in the candidate
  // loop.
  void QueueLoadNextCandidateTask();

  nsRefPtr<nsMediaDecoder> mDecoder;

  nsCOMPtr<nsIChannel> mChannel;

  // Error attribute
  nsCOMPtr<nsIDOMHTMLMediaError> mError;

  // The current media load object. Stores the list of candidate media
  // resources that we are attempting to load.
  nsRefPtr<nsMediaLoad> mCurrentLoad;

  // Media loading flags. See: 
  //   http://www.whatwg.org/specs/web-apps/current-work/#video)
  nsMediaNetworkState mNetworkState;
  nsMediaReadyState mReadyState;

  // Value of the volume before it was muted
  float mMutedVolume;

  // Size of the media. Updated by the decoder on the main thread if
  // it changes. Defaults to a width and height of -1 if not set.
  nsIntSize mMediaSize;

  // If true then we have begun downloading the media content.
  // Set to false when completed, or not yet started.
  PRPackedBool mBegun;

  // True when the decoder has loaded enough data to display the
  // first frame of the content.
  PRPackedBool mLoadedFirstFrame;

  // Indicates whether current playback is a result of user action
  // (ie. calling of the Play method), or automatic playback due to
  // the 'autoplay' attribute being set. A true value indicates the 
  // latter case.
  // The 'autoplay' HTML attribute indicates that the video should
  // start playing when loaded. The 'autoplay' attribute of the object
  // is a mirror of the HTML attribute. These are different from this
  // 'mAutoplaying' flag, which indicates whether the current playback
  // is a result of the autoplay attribute.
  PRPackedBool mAutoplaying;

  // Indicates whether |autoplay| will actually autoplay based on the pref 
  // media.autoplay.enabled
  PRPackedBool mAutoplayEnabled;

  // Playback of the video is paused either due to calling the
  // 'Pause' method, or playback not yet having started.
  PRPackedBool mPaused;

  // True if the sound is muted
  PRPackedBool mMuted;

  // Flag to indicate if the child elements (eg. <source/>) have been
  // parsed.
  PRPackedBool mIsDoneAddingChildren;

  // If TRUE then the media element was actively playing before the currently
  // in progress seeking. If FALSE then the media element is either not seeking
  // or was not actively playing before the current seek. Used to decide whether
  // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
  PRPackedBool mPlayingBeforeSeek;

  // PR_TRUE if the video was paused before Freeze was called. This is checked
  // to ensure that the playstate doesn't change when the user goes Forward/Back
  // from the bfcache.
  PRPackedBool mPausedBeforeFreeze;
  
  // PR_TRUE if we've reported a "waiting" event since the last
  // readyState change to HAVE_CURRENT_DATA.
  PRPackedBool mWaitingFired;

  // PR_TRUE if we're in BindToTree().
  PRPackedBool mIsBindingToTree;
};
