/* -*- 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 "WebVTTLoadListener.h"
#include "mozilla/dom/TextTrackCue.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "VideoUtils.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_1(WebVTTLoadListener, mElement)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTLoadListener)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(WebVTTLoadListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTLoadListener)

#ifdef PR_LOGGING
PRLogModuleInfo* gTextTrackLog;
# define LOG(...) PR_LOG(gTextTrackLog, PR_LOG_DEBUG, (__VA_ARGS__))
#else
# define LOG(msg)
#endif

WebVTTLoadListener::WebVTTLoadListener(HTMLTrackElement* aElement)
  : mElement(aElement)
{
  MOZ_ASSERT(mElement, "Must pass an element to the callback");
#ifdef PR_LOGGING
  if (!gTextTrackLog) {
    gTextTrackLog = PR_NewLogModule("TextTrack");
  }
#endif
  LOG("WebVTTLoadListener created.");
}

WebVTTLoadListener::~WebVTTLoadListener()
{
  LOG("WebVTTLoadListener destroyed.");
}

nsresult
WebVTTLoadListener::LoadResource()
{
  if (!HTMLTrackElement::IsWebVTTEnabled()) {
    NS_WARNING("WebVTT support disabled."
               " See media.webvtt.enabled in about:config. ");
    return NS_ERROR_FAILURE;
  }

  LOG("Loading text track resource.");
  webvtt_parser_t* parser = nullptr;
  webvtt_status status = webvtt_create_parser(&OnParsedCueWebVTTCallBack,
                                              &OnReportErrorWebVTTCallBack,
                                              this, &parser);

  if (status != WEBVTT_SUCCESS) {
    NS_ENSURE_TRUE(status != WEBVTT_OUT_OF_MEMORY,
                   NS_ERROR_OUT_OF_MEMORY);
    NS_ENSURE_TRUE(status != WEBVTT_INVALID_PARAM,
                   NS_ERROR_INVALID_ARG);
    return NS_ERROR_FAILURE;
  }

  mParser.own(parser);
  NS_ENSURE_TRUE(mParser != nullptr, NS_ERROR_FAILURE);

  mElement->mReadyState = HTMLTrackElement::LOADING;
  return NS_OK;
}

NS_IMETHODIMP
WebVTTLoadListener::OnStartRequest(nsIRequest* aRequest,
                                   nsISupports* aContext)
{
  return NS_OK;
}

NS_IMETHODIMP
WebVTTLoadListener::OnStopRequest(nsIRequest* aRequest,
                                  nsISupports* aContext,
                                  nsresult aStatus)
{
  webvtt_finish_parsing(mParser);
  if(mElement->mReadyState != HTMLTrackElement::ERROR) {
    mElement->mReadyState = HTMLTrackElement::LOADED;
  }
  return NS_OK;
}

NS_IMETHODIMP
WebVTTLoadListener::OnDataAvailable(nsIRequest* aRequest,
                                    nsISupports* aContext,
                                    nsIInputStream* aStream,
                                    uint64_t aOffset,
                                    uint32_t aCount)
{
  uint32_t count = aCount;
  while (count > 0) {
    uint32_t read;
    nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
    NS_ENSURE_SUCCESS(rv, rv);
    if (!read) {
      return NS_ERROR_FAILURE;
    }
    count -= read;
  }

  return NS_OK;
}

NS_IMETHODIMP
WebVTTLoadListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                           nsIChannel* aNewChannel,
                                           uint32_t aFlags,
                                           nsIAsyncVerifyRedirectCallback* cb)
{
  if (mElement) {
    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
  }
  return NS_OK;
}

NS_IMETHODIMP
WebVTTLoadListener::GetInterface(const nsIID &aIID,
                                 void** aResult)
{
  return QueryInterface(aIID, aResult);
}

NS_METHOD
WebVTTLoadListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
                               const char* aFromSegment, uint32_t aToOffset,
                               uint32_t aCount, uint32_t* aWriteCount)
{
  WebVTTLoadListener* loadListener = static_cast<WebVTTLoadListener*>(aClosure);

  if (WEBVTT_FAILED(webvtt_parse_chunk(loadListener->mParser, aFromSegment,
                                       aCount))) {
    LOG("Unable to parse chunk of WEBVTT text. Aborting.");
    *aWriteCount = 0;
    return NS_ERROR_FAILURE;
  }
  *aWriteCount = aCount;
  return NS_OK;
}

void
WebVTTLoadListener::OnParsedCue(webvtt_cue* aCue)
{
  const char* text = webvtt_string_text(&aCue->body);

  nsRefPtr<TextTrackCue> textTrackCue =
    new TextTrackCue(mElement->OwnerDoc()->GetParentObject(),
                     MS_TO_SECONDS(aCue->from), MS_TO_SECONDS(aCue->until),
                     NS_ConvertUTF8toUTF16(text), mElement,
                     aCue->node_head);

  text = webvtt_string_text(&aCue->id);
  textTrackCue->SetId(NS_ConvertUTF8toUTF16(text));

  textTrackCue->SetSnapToLines(aCue->snap_to_lines);
  textTrackCue->SetSize(aCue->settings.size);
  textTrackCue->SetPosition(aCue->settings.position);
  textTrackCue->SetLine(aCue->settings.line);

  nsAutoString vertical;
  switch (aCue->settings.vertical) {
    case WEBVTT_VERTICAL_LR:
      vertical = NS_LITERAL_STRING("lr");
      break;
    case WEBVTT_VERTICAL_RL:
      vertical = NS_LITERAL_STRING("rl");
      break;
    case WEBVTT_HORIZONTAL:
      // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=865407
      // Will be handled in the processing model.
      break;
  }
  textTrackCue->SetVertical(vertical);

  TextTrackCueAlign align;
  switch (aCue->settings.align) {
    case WEBVTT_ALIGN_START:
      align = TextTrackCueAlign::Start;
      break;
    case WEBVTT_ALIGN_MIDDLE:
      align = TextTrackCueAlign::Middle;
    case WEBVTT_ALIGN_END:
      align = TextTrackCueAlign::End;
    case WEBVTT_ALIGN_LEFT:
      align = TextTrackCueAlign::Left;
      break;
    case WEBVTT_ALIGN_RIGHT:
      align = TextTrackCueAlign::Right;
      break;
    default:
      align = TextTrackCueAlign::Start;
      break;
  }
  textTrackCue->SetAlign(align);

  mElement->mTrack->AddCue(*textTrackCue);
}

int
WebVTTLoadListener::OnReportError(uint32_t aLine, uint32_t aCol,
                                  webvtt_error aError)
{
#ifdef PR_LOGGING
  // Get source webvtt file to display in the log
  DOMString wideFile;
  mElement->GetSrc(wideFile);

  NS_ConvertUTF16toUTF8 file(wideFile);

  const char* error = "parser error";
  if (aError >= 0) {
    error = webvtt_strerror(aError);
  }

  LOG("error: %s(%d:%d) - %s\n", file.get(), aLine, aCol, error);
#endif

  switch(aError) {
    // Non-recoverable errors require us to abort parsing:
    case WEBVTT_MALFORMED_TAG:
      mElement->mReadyState = HTMLTrackElement::ERROR;
      return -1;

    // Errors which should result in dropped cues
    // if the return value is negative:
    case WEBVTT_MALFORMED_TIMESTAMP:
      return -1;

    // By default, we can safely ignore other errors
    // or else parsing the document will be aborted regardless
    // of the return value.
    default:
      return 0;
  }
}

void WEBVTT_CALLBACK
WebVTTLoadListener::OnParsedCueWebVTTCallBack(void* aUserData, webvtt_cue* aCue)
{
  WebVTTLoadListener* self = static_cast<WebVTTLoadListener*>(aUserData);
  self->OnParsedCue(aCue);
}

int WEBVTT_CALLBACK
WebVTTLoadListener::OnReportErrorWebVTTCallBack(void* aUserData, uint32_t aLine,
                                                uint32_t aCol,
                                                webvtt_error aError)
{
  WebVTTLoadListener* self = static_cast<WebVTTLoadListener*>(aUserData);
  return self->OnReportError(aLine, aCol, aError);
}

} // namespace dom
} // namespace mozilla
