/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/HTMLTrackElement.h"
#include "mozilla/dom/TextTrackCue.h"
#include "mozilla/dom/TextTrackList.h"
#include "mozilla/dom/TextTrackRegion.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/ClearOnShutdown.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrackCue,
                                   DOMEventTargetHelper,
                                   mDocument,
                                   mTrack,
                                   mTrackElement,
                                   mDisplayState,
                                   mRegion)

NS_IMPL_ADDREF_INHERITED(TextTrackCue, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(TextTrackCue, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextTrackCue)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

StaticRefPtr<nsIWebVTTParserWrapper> TextTrackCue::sParserWrapper;

// Set cue setting defaults based on step 19 & seq.
// in http://dev.w3.org/html5/webvtt/#parsing
void
TextTrackCue::SetDefaultCueSettings()
{
  mPositionIsAutoKeyword = true;
  mPositionAlign = PositionAlignSetting::Center;
  mSize = 100.0;
  mPauseOnExit = false;
  mSnapToLines = true;
  mLineIsAutoKeyword = true;
  mAlign = AlignSetting::Center;
  mLineAlign = LineAlignSetting::Start;
  mVertical = DirectionSetting::_empty;
  mActive = false;
}

TextTrackCue::TextTrackCue(nsPIDOMWindowInner* aOwnerWindow,
                           double aStartTime,
                           double aEndTime,
                           const nsAString& aText,
                           ErrorResult& aRv)
  : DOMEventTargetHelper(aOwnerWindow)
  , mText(aText)
  , mStartTime(aStartTime)
  , mEndTime(aEndTime)
  , mReset(false, "TextTrackCue::mReset")
  , mHaveStartedWatcher(false)
  , mWatchManager(this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other))
{
  SetDefaultCueSettings();
  MOZ_ASSERT(aOwnerWindow);
  if (NS_FAILED(StashDocument())) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
  }
}

TextTrackCue::TextTrackCue(nsPIDOMWindowInner* aOwnerWindow,
                           double aStartTime,
                           double aEndTime,
                           const nsAString& aText,
                           HTMLTrackElement* aTrackElement,
                           ErrorResult& aRv)
  : DOMEventTargetHelper(aOwnerWindow)
  , mText(aText)
  , mStartTime(aStartTime)
  , mEndTime(aEndTime)
  , mTrackElement(aTrackElement)
  , mReset(false, "TextTrackCue::mReset")
  , mHaveStartedWatcher(false)
  , mWatchManager(this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other))
{
  SetDefaultCueSettings();
  MOZ_ASSERT(aOwnerWindow);
  if (NS_FAILED(StashDocument())) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
  }
}

TextTrackCue::~TextTrackCue()
{
}

/** Save a reference to our creating document so we don't have to
 *  keep getting it from our window.
 */
nsresult
TextTrackCue::StashDocument()
{
  nsPIDOMWindowInner* window = GetOwner();
  if (!window) {
    return NS_ERROR_NO_INTERFACE;
  }
  mDocument = window->GetDoc();
  if (!mDocument) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return NS_OK;
}

already_AddRefed<DocumentFragment>
TextTrackCue::GetCueAsHTML()
{
  // mDocument may be null during cycle collector shutdown.
  // See bug 941701.
  if (!mDocument) {
    return nullptr;
  }

  if (!sParserWrapper) {
    nsresult rv;
    nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
      do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, &rv);
    if (NS_FAILED(rv)) {
      return mDocument->CreateDocumentFragment();
    }
    sParserWrapper = parserWrapper;
    ClearOnShutdown(&sParserWrapper);
  }

  nsPIDOMWindowInner* window = mDocument->GetInnerWindow();
  if (!window) {
    return mDocument->CreateDocumentFragment();
  }

  nsCOMPtr<nsIDOMDocumentFragment> frag;
  sParserWrapper->ConvertCueToDOMTree(window, this,
                                      getter_AddRefs(frag));
  if (!frag) {
    return mDocument->CreateDocumentFragment();
  }
  return frag.forget().downcast<DocumentFragment>();
}

void
TextTrackCue::SetTrackElement(HTMLTrackElement* aTrackElement)
{
  mTrackElement = aTrackElement;
}

JSObject*
TextTrackCue::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return VTTCueBinding::Wrap(aCx, this, aGivenProto);
}

TextTrackRegion*
TextTrackCue::GetRegion()
{
  return mRegion;
}

void
TextTrackCue::SetRegion(TextTrackRegion* aRegion)
{
  if (mRegion == aRegion) {
    return;
  }
  mRegion = aRegion;
  mReset = true;
}

double
TextTrackCue::ComputedLine()
{
  // See spec https://w3c.github.io/webvtt/#cue-computed-line
  if (!mLineIsAutoKeyword && !mSnapToLines &&
      (mLine < 0.0 || mLine > 100.0)) {
    return 100.0;
  } else if (!mLineIsAutoKeyword) {
    return mLine;
  } else if (mLineIsAutoKeyword && !mSnapToLines) {
    return 100.0;
  } else if (!mTrack ||
             !mTrack->GetTextTrackList() ||
             !mTrack->GetTextTrackList()->GetMediaElement()) {
    return -1.0;
  }

  RefPtr<TextTrackList> trackList = mTrack->GetTextTrackList();
  bool dummy;
  uint32_t showingTracksNum = 0;
  for (uint32_t idx = 0; idx < trackList->Length(); idx++) {
    RefPtr<TextTrack> track = trackList->IndexedGetter(idx, dummy);
    if (track->Mode() == TextTrackMode::Showing) {
      showingTracksNum++;
    }

    if (mTrack == track) {
      break;
    }
  }

  return (-1.0) * showingTracksNum;
}

double
TextTrackCue::ComputedPosition()
{
  // See spec https://w3c.github.io/webvtt/#cue-computed-position
  if (!mPositionIsAutoKeyword) {
    return mPosition;
  } else if (mAlign == AlignSetting::Left) {
    return 0.0;
  } else if (mAlign == AlignSetting::Right) {
    return 100.0;
  }
  return 50.0;
}

PositionAlignSetting
TextTrackCue::ComputedPositionAlign()
{
  // See spec https://w3c.github.io/webvtt/#cue-computed-position-alignment
  if (mPositionAlign != PositionAlignSetting::Auto) {
    return mPositionAlign;
  } else if (mAlign == AlignSetting::Left) {
    return PositionAlignSetting::Line_left;
  } else if (mAlign == AlignSetting::Right) {
    return PositionAlignSetting::Line_right;
  }
  return PositionAlignSetting::Center;
}

void
TextTrackCue::NotifyDisplayStatesChanged()
{
  if (!mReset) {
    return;
  }

  if (!mTrack ||
      !mTrack->GetTextTrackList() ||
      !mTrack->GetTextTrackList()->GetMediaElement()) {
    return;
  }

  mTrack->GetTextTrackList()->GetMediaElement()->NotifyCueDisplayStatesChanged();
}

} // namespace dom
} // namespace mozilla
