/* -*- Mode: C++; tab-width: 4; 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 "txMozillaTextOutput.h"
#include "nsContentCID.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsIDocumentTransformer.h"
#include "nsCharsetSource.h"
#include "txURIUtils.h"
#include "nsContentCreatorFunctions.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
#include "mozilla/Encoding.h"
#include "nsTextNode.h"
#include "nsNameSpaceManager.h"
#include "mozilla/dom/DocumentFragment.h"

using namespace mozilla;
using namespace mozilla::dom;

txMozillaTextOutput::txMozillaTextOutput(Document* aSourceDocument,
                                         nsITransformObserver* aObserver)
    : mSourceDocument(aSourceDocument),
      mObserver(do_GetWeakReference(aObserver)),
      mCreatedDocument(false) {
  MOZ_COUNT_CTOR(txMozillaTextOutput);
}

txMozillaTextOutput::txMozillaTextOutput(DocumentFragment* aDest)
    : mTextParent(aDest),
      mDocument(mTextParent->OwnerDoc()),
      mCreatedDocument(false) {
  MOZ_COUNT_CTOR(txMozillaTextOutput);
  mTextParent = aDest;
  mDocument = mTextParent->OwnerDoc();
}

txMozillaTextOutput::~txMozillaTextOutput() {
  MOZ_COUNT_DTOR(txMozillaTextOutput);
}

nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
                                        nsAtom* aLowercaseLocalName,
                                        int32_t aNsID, const nsString& aValue) {
  return NS_OK;
}

nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, const nsAString& aName,
                                        const int32_t aNsID,
                                        const nsString& aValue) {
  return NS_OK;
}

nsresult txMozillaTextOutput::characters(const nsAString& aData, bool aDOE) {
  mText.Append(aData);

  return NS_OK;
}

nsresult txMozillaTextOutput::comment(const nsString& aData) { return NS_OK; }

nsresult txMozillaTextOutput::endDocument(nsresult aResult) {
  NS_ENSURE_TRUE(mDocument && mTextParent, NS_ERROR_FAILURE);

  RefPtr<nsTextNode> text = new (mDocument->NodeInfoManager())
      nsTextNode(mDocument->NodeInfoManager());

  ErrorResult rv;
  text->SetText(mText, false);
  mTextParent->AppendChildTo(text, true, rv);
  if (rv.Failed()) {
    return rv.StealNSResult();
  }

  // This should really be handled by Document::EndLoad
  if (mCreatedDocument) {
    MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
               "Bad readyState");
  } else {
    MOZ_ASSERT(
        mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE,
        "Bad readyState");
  }
  mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);

  if (NS_SUCCEEDED(aResult)) {
    nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver);
    if (observer) {
      observer->OnTransformDone(mSourceDocument, aResult, mDocument);
    }
  }

  return NS_OK;
}

nsresult txMozillaTextOutput::endElement() { return NS_OK; }

nsresult txMozillaTextOutput::processingInstruction(const nsString& aTarget,
                                                    const nsString& aData) {
  return NS_OK;
}

nsresult txMozillaTextOutput::startDocument() { return NS_OK; }

nsresult txMozillaTextOutput::createResultDocument(bool aLoadedAsData) {
  /*
   * Create an XHTML document to hold the text.
   *
   * <html>
   *   <head />
   *   <body>
   *     <pre id="transformiixResult"> * The text comes here * </pre>
   *   <body>
   * </html>
   *
   * Except if we are transforming into a non-displayed document we create
   * the following DOM
   *
   * <transformiix:result> * The text comes here * </transformiix:result>
   */

  // Create the document
  nsresult rv = NS_NewXMLDocument(getter_AddRefs(mDocument), aLoadedAsData);
  NS_ENSURE_SUCCESS(rv, rv);
  mCreatedDocument = true;
  // This should really be handled by Document::BeginLoad
  MOZ_ASSERT(
      mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
      "Bad readyState");
  mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING);
  bool hasHadScriptObject = false;
  nsIScriptGlobalObject* sgo =
      mSourceDocument->GetScriptHandlingObject(hasHadScriptObject);
  NS_ENSURE_STATE(sgo || !hasHadScriptObject);

  NS_ASSERTION(mDocument, "Need document");

  // Reset and set up document
  URIUtils::ResetWithSource(mDocument, mSourceDocument);
  // Only do this after resetting the document to ensure we have the
  // correct principal.
  mDocument->SetScriptHandlingObject(sgo);

  // Set the charset
  if (!mOutputFormat.mEncoding.IsEmpty()) {
    const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding);
    if (encoding) {
      mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
      mDocument->SetDocumentCharacterSet(WrapNotNull(encoding));
    }
  }

  // Notify the contentsink that the document is created
  nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver);
  if (observer) {
    rv = observer->OnDocumentCreated(mSourceDocument, mDocument);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Create the content

  // When transforming into a non-displayed document (i.e. when there is no
  // observer) we only create a transformiix:result root element.
  if (!observer) {
    int32_t namespaceID;
    rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
        nsLiteralString(kTXNameSpaceURI), namespaceID);
    NS_ENSURE_SUCCESS(rv, rv);

    mTextParent =
        mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
                              nsGkAtoms::transformiix, namespaceID);

    ErrorResult error;
    mDocument->AppendChildTo(mTextParent, true, error);
    if (error.Failed()) {
      return error.StealNSResult();
    }
  } else {
    RefPtr<Element> html, head, body;
    rv = createXHTMLElement(nsGkAtoms::html, getter_AddRefs(html));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = createXHTMLElement(nsGkAtoms::head, getter_AddRefs(head));
    NS_ENSURE_SUCCESS(rv, rv);

    ErrorResult error;
    html->AppendChildTo(head, false, error);
    if (error.Failed()) {
      return error.StealNSResult();
    }

    rv = createXHTMLElement(nsGkAtoms::body, getter_AddRefs(body));
    NS_ENSURE_SUCCESS(rv, rv);

    html->AppendChildTo(body, false, error);
    if (error.Failed()) {
      return error.StealNSResult();
    }

    {
      RefPtr<Element> textParent;
      rv = createXHTMLElement(nsGkAtoms::pre, getter_AddRefs(textParent));
      NS_ENSURE_SUCCESS(rv, rv);
      mTextParent = std::move(textParent);
    }

    rv = mTextParent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::id,
                                           u"transformiixResult"_ns, false);
    NS_ENSURE_SUCCESS(rv, rv);

    body->AppendChildTo(mTextParent, false, error);
    if (error.Failed()) {
      return error.StealNSResult();
    }

    mDocument->AppendChildTo(html, true, error);
    if (error.Failed()) {
      return error.StealNSResult();
    }
  }

  return NS_OK;
}

nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
                                           nsAtom* aLowercaseLocalName,
                                           int32_t aNsID) {
  return NS_OK;
}

nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix,
                                           const nsAString& aName,
                                           const int32_t aNsID) {
  return NS_OK;
}

void txMozillaTextOutput::getOutputDocument(Document** aDocument) {
  NS_IF_ADDREF(*aDocument = mDocument);
}

nsresult txMozillaTextOutput::createXHTMLElement(nsAtom* aName,
                                                 Element** aResult) {
  nsCOMPtr<Element> element = mDocument->CreateHTMLElement(aName);
  element.forget(aResult);
  return NS_OK;
}
