/* -*- 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 "txStylesheetCompiler.h"

#include <utility>

#include "mozilla/ArrayUtils.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "nsGkAtoms.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "nsWhitespaceTokenizer.h"
#include "txExprParser.h"
#include "txInstructions.h"
#include "txLog.h"
#include "txPatternParser.h"
#include "txStringUtils.h"
#include "txStylesheet.h"
#include "txStylesheetCompileHandlers.h"
#include "txToplevelItems.h"
#include "txURIUtils.h"
#include "txXSLTFunctions.h"

using namespace mozilla;
using mozilla::dom::ReferrerPolicy;

txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI,
                                           ReferrerPolicy aReferrerPolicy,
                                           txACompileObserver* aObserver)
    : txStylesheetCompilerState(aObserver) {
  mStatus = init(aStylesheetURI, aReferrerPolicy, nullptr, nullptr);
}

txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI,
                                           txStylesheet* aStylesheet,
                                           txListIterator* aInsertPosition,
                                           ReferrerPolicy aReferrerPolicy,
                                           txACompileObserver* aObserver)
    : txStylesheetCompilerState(aObserver) {
  mStatus = init(aStylesheetURI, aReferrerPolicy, aStylesheet, aInsertPosition);
}

void txStylesheetCompiler::setBaseURI(const nsString& aBaseURI) {
  NS_ASSERTION(mObjectStack.size() == 1 && !mObjectStack.peek(),
               "Execution already started");

  if (NS_FAILED(mStatus)) {
    return;
  }

  mElementContext->mBaseURI = aBaseURI;
}

nsresult txStylesheetCompiler::startElement(int32_t aNamespaceID,
                                            nsAtom* aLocalName, nsAtom* aPrefix,
                                            txStylesheetAttr* aAttributes,
                                            int32_t aAttrCount) {
  if (NS_FAILED(mStatus)) {
    // ignore content after failure
    // XXX reevaluate once expat stops on failure
    return NS_OK;
  }

  nsresult rv = flushCharacters();
  NS_ENSURE_SUCCESS(rv, rv);

  // look for new namespace mappings
  bool hasOwnNamespaceMap = false;
  int32_t i;
  for (i = 0; i < aAttrCount; ++i) {
    txStylesheetAttr* attr = aAttributes + i;
    if (attr->mNamespaceID == kNameSpaceID_XMLNS) {
      rv = ensureNewElementContext();
      NS_ENSURE_SUCCESS(rv, rv);

      if (!hasOwnNamespaceMap) {
        mElementContext->mMappings =
            new txNamespaceMap(*mElementContext->mMappings);
        hasOwnNamespaceMap = true;
      }

      if (attr->mLocalName == nsGkAtoms::xmlns) {
        mElementContext->mMappings->mapNamespace(nullptr, attr->mValue);
      } else {
        mElementContext->mMappings->mapNamespace(attr->mLocalName,
                                                 attr->mValue);
      }
    }
  }

  return startElementInternal(aNamespaceID, aLocalName, aPrefix, aAttributes,
                              aAttrCount);
}

nsresult txStylesheetCompiler::startElement(const char16_t* aName,
                                            const char16_t** aAttrs,
                                            int32_t aAttrCount) {
  if (NS_FAILED(mStatus)) {
    // ignore content after failure
    // XXX reevaluate once expat stops on failure
    return NS_OK;
  }

  nsresult rv = flushCharacters();
  NS_ENSURE_SUCCESS(rv, rv);

  UniquePtr<txStylesheetAttr[]> atts;
  if (aAttrCount > 0) {
    atts = MakeUnique<txStylesheetAttr[]>(aAttrCount);
  }

  bool hasOwnNamespaceMap = false;
  int32_t i;
  for (i = 0; i < aAttrCount; ++i) {
    rv = XMLUtils::splitExpatName(
        aAttrs[i * 2], getter_AddRefs(atts[i].mPrefix),
        getter_AddRefs(atts[i].mLocalName), &atts[i].mNamespaceID);
    NS_ENSURE_SUCCESS(rv, rv);
    atts[i].mValue.Append(aAttrs[i * 2 + 1]);

    RefPtr<nsAtom> prefixToBind;
    if (atts[i].mPrefix == nsGkAtoms::xmlns) {
      prefixToBind = atts[i].mLocalName;
    } else if (atts[i].mNamespaceID == kNameSpaceID_XMLNS) {
      prefixToBind = nsGkAtoms::_empty;
    }

    if (prefixToBind) {
      rv = ensureNewElementContext();
      NS_ENSURE_SUCCESS(rv, rv);

      if (!hasOwnNamespaceMap) {
        mElementContext->mMappings =
            new txNamespaceMap(*mElementContext->mMappings);
        hasOwnNamespaceMap = true;
      }

      rv = mElementContext->mMappings->mapNamespace(prefixToBind,
                                                    atts[i].mValue);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  RefPtr<nsAtom> prefix, localname;
  int32_t namespaceID;
  rv = XMLUtils::splitExpatName(aName, getter_AddRefs(prefix),
                                getter_AddRefs(localname), &namespaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  return startElementInternal(namespaceID, localname, prefix, atts.get(),
                              aAttrCount);
}

nsresult txStylesheetCompiler::startElementInternal(
    int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix,
    txStylesheetAttr* aAttributes, int32_t aAttrCount) {
  nsresult rv = NS_OK;
  int32_t i;
  for (i = mInScopeVariables.Length() - 1; i >= 0; --i) {
    ++mInScopeVariables[i].mLevel;
  }

  // Update the elementcontext if we have special attributes
  for (i = 0; i < aAttrCount; ++i) {
    txStylesheetAttr* attr = aAttributes + i;

    // id
    if (mEmbedStatus == eNeedEmbed && attr->mLocalName == nsGkAtoms::id &&
        attr->mNamespaceID == kNameSpaceID_None &&
        attr->mValue.Equals(mTarget)) {
      // We found the right ID, signal to compile the
      // embedded stylesheet.
      mEmbedStatus = eInEmbed;
    }

    // xml:space
    if (attr->mNamespaceID == kNameSpaceID_XML &&
        attr->mLocalName == nsGkAtoms::space) {
      rv = ensureNewElementContext();
      NS_ENSURE_SUCCESS(rv, rv);

      if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::preserve)) {
        mElementContext->mPreserveWhitespace = true;
      } else if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::_default)) {
        mElementContext->mPreserveWhitespace = false;
      } else {
        return NS_ERROR_XSLT_PARSE_FAILURE;
      }
    }

    // extension-element-prefixes
    if ((attr->mNamespaceID == kNameSpaceID_XSLT &&
         attr->mLocalName == nsGkAtoms::extensionElementPrefixes &&
         aNamespaceID != kNameSpaceID_XSLT) ||
        (attr->mNamespaceID == kNameSpaceID_None &&
         attr->mLocalName == nsGkAtoms::extensionElementPrefixes &&
         aNamespaceID == kNameSpaceID_XSLT &&
         (aLocalName == nsGkAtoms::stylesheet ||
          aLocalName == nsGkAtoms::transform))) {
      rv = ensureNewElementContext();
      NS_ENSURE_SUCCESS(rv, rv);

      nsWhitespaceTokenizer tok(attr->mValue);
      while (tok.hasMoreTokens()) {
        int32_t namespaceID =
            mElementContext->mMappings->lookupNamespaceWithDefault(
                tok.nextToken());

        if (namespaceID == kNameSpaceID_Unknown)
          return NS_ERROR_XSLT_PARSE_FAILURE;

        mElementContext->mInstructionNamespaces.AppendElement(namespaceID);
      }

      attr->mLocalName = nullptr;
    }

    // version
    if ((attr->mNamespaceID == kNameSpaceID_XSLT &&
         attr->mLocalName == nsGkAtoms::version &&
         aNamespaceID != kNameSpaceID_XSLT) ||
        (attr->mNamespaceID == kNameSpaceID_None &&
         attr->mLocalName == nsGkAtoms::version &&
         aNamespaceID == kNameSpaceID_XSLT &&
         (aLocalName == nsGkAtoms::stylesheet ||
          aLocalName == nsGkAtoms::transform))) {
      rv = ensureNewElementContext();
      NS_ENSURE_SUCCESS(rv, rv);

      if (attr->mValue.EqualsLiteral("1.0")) {
        mElementContext->mForwardsCompatibleParsing = false;
      } else {
        mElementContext->mForwardsCompatibleParsing = true;
      }
    }
  }

  // Find the right elementhandler and execute it
  bool isInstruction = false;
  int32_t count = mElementContext->mInstructionNamespaces.Length();
  for (i = 0; i < count; ++i) {
    if (mElementContext->mInstructionNamespaces[i] == aNamespaceID) {
      isInstruction = true;
      break;
    }
  }

  const txElementHandler* handler;
  do {
    handler = isInstruction ? mHandlerTable->find(aNamespaceID, aLocalName)
                            : mHandlerTable->mLREHandler;

    rv = (handler->mStartFunction)(aNamespaceID, aLocalName, aPrefix,
                                   aAttributes, aAttrCount, *this);
  } while (rv == NS_XSLT_GET_NEW_HANDLER);

  NS_ENSURE_SUCCESS(rv, rv);

  if (!fcp()) {
    for (i = 0; i < aAttrCount; ++i) {
      txStylesheetAttr& attr = aAttributes[i];
      if (attr.mLocalName && (attr.mNamespaceID == kNameSpaceID_XSLT ||
                              (aNamespaceID == kNameSpaceID_XSLT &&
                               attr.mNamespaceID == kNameSpaceID_None))) {
        // XXX ErrorReport: unknown attribute
        return NS_ERROR_XSLT_PARSE_FAILURE;
      }
    }
  }

  pushPtr(const_cast<txElementHandler*>(handler), eElementHandler);

  mElementContext->mDepth++;

  return NS_OK;
}

nsresult txStylesheetCompiler::endElement() {
  if (NS_FAILED(mStatus)) {
    // ignore content after failure
    // XXX reevaluate once expat stops on failure
    return NS_OK;
  }

  nsresult rv = flushCharacters();
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t i;
  for (i = mInScopeVariables.Length() - 1; i >= 0; --i) {
    txInScopeVariable& var = mInScopeVariables[i];
    if (!--(var.mLevel)) {
      addInstruction(MakeUnique<txRemoveVariable>(var.mName));

      mInScopeVariables.RemoveElementAt(i);
    }
  }

  const txElementHandler* handler = const_cast<const txElementHandler*>(
      static_cast<txElementHandler*>(popPtr(eElementHandler)));
  (handler->mEndFunction)(*this);

  if (!--mElementContext->mDepth) {
    // this will delete the old object
    mElementContext = WrapUnique(static_cast<txElementContext*>(popObject()));
  }

  return NS_OK;
}

nsresult txStylesheetCompiler::characters(const nsAString& aStr) {
  if (NS_FAILED(mStatus)) {
    // ignore content after failure
    // XXX reevaluate once expat stops on failure
    return NS_OK;
  }

  mCharacters.Append(aStr);

  return NS_OK;
}

nsresult txStylesheetCompiler::doneLoading() {
  MOZ_LOG(txLog::xslt, LogLevel::Info,
          ("Compiler::doneLoading: %s\n",
           NS_LossyConvertUTF16toASCII(mStylesheetURI).get()));
  if (NS_FAILED(mStatus)) {
    return mStatus;
  }

  mDoneWithThisStylesheet = true;

  return maybeDoneCompiling();
}

void txStylesheetCompiler::cancel(nsresult aError, const char16_t* aErrorText,
                                  const char16_t* aParam) {
  MOZ_LOG(txLog::xslt, LogLevel::Info,
          ("Compiler::cancel: %s, module: %d, code %d\n",
           NS_LossyConvertUTF16toASCII(mStylesheetURI).get(),
           NS_ERROR_GET_MODULE(aError), NS_ERROR_GET_CODE(aError)));
  if (NS_SUCCEEDED(mStatus)) {
    mStatus = aError;
  }

  if (mObserver) {
    mObserver->onDoneCompiling(this, mStatus, aErrorText, aParam);
    // This will ensure that we don't call onDoneCompiling twice. Also
    // ensures that we don't keep the observer alive longer then necessary.
    mObserver = nullptr;
  }
}

txStylesheet* txStylesheetCompiler::getStylesheet() { return mStylesheet; }

nsresult txStylesheetCompiler::loadURI(const nsAString& aUri,
                                       const nsAString& aReferrerUri,
                                       ReferrerPolicy aReferrerPolicy,
                                       txStylesheetCompiler* aCompiler) {
  MOZ_LOG(txLog::xslt, LogLevel::Info,
          ("Compiler::loadURI forwards %s thru %s\n",
           NS_LossyConvertUTF16toASCII(aUri).get(),
           NS_LossyConvertUTF16toASCII(mStylesheetURI).get()));
  if (mStylesheetURI.Equals(aUri)) {
    return NS_ERROR_XSLT_LOAD_RECURSION;
  }
  return mObserver ? mObserver->loadURI(aUri, aReferrerUri, aReferrerPolicy,
                                        aCompiler)
                   : NS_ERROR_FAILURE;
}

void txStylesheetCompiler::onDoneCompiling(txStylesheetCompiler* aCompiler,
                                           nsresult aResult,
                                           const char16_t* aErrorText,
                                           const char16_t* aParam) {
  if (NS_FAILED(aResult)) {
    cancel(aResult, aErrorText, aParam);
    return;
  }

  mChildCompilerList.RemoveElement(aCompiler);

  maybeDoneCompiling();
}

nsresult txStylesheetCompiler::flushCharacters() {
  // Bail if we don't have any characters. The handler will detect
  // ignoreable whitespace
  if (mCharacters.IsEmpty()) {
    return NS_OK;
  }

  nsresult rv = NS_OK;

  do {
    rv = (mHandlerTable->mTextHandler)(mCharacters, *this);
  } while (rv == NS_XSLT_GET_NEW_HANDLER);

  NS_ENSURE_SUCCESS(rv, rv);

  mCharacters.Truncate();

  return NS_OK;
}

nsresult txStylesheetCompiler::ensureNewElementContext() {
  // Do we already have a new context?
  if (!mElementContext->mDepth) {
    return NS_OK;
  }

  UniquePtr<txElementContext> context(new txElementContext(*mElementContext));
  pushObject(mElementContext.release());
  mElementContext = std::move(context);

  return NS_OK;
}

nsresult txStylesheetCompiler::maybeDoneCompiling() {
  if (!mDoneWithThisStylesheet || !mChildCompilerList.IsEmpty()) {
    return NS_OK;
  }

  if (mIsTopCompiler) {
    nsresult rv = mStylesheet->doneCompiling();
    if (NS_FAILED(rv)) {
      cancel(rv);
      return rv;
    }
  }

  if (mObserver) {
    mObserver->onDoneCompiling(this, mStatus);
    // This will ensure that we don't call onDoneCompiling twice. Also
    // ensures that we don't keep the observer alive longer then necessary.
    mObserver = nullptr;
  }

  return NS_OK;
}

/**
 * txStylesheetCompilerState
 */

txStylesheetCompilerState::txStylesheetCompilerState(
    txACompileObserver* aObserver)
    : mHandlerTable(nullptr),
      mSorter(nullptr),
      mDOE(false),
      mSearchingForFallback(false),
      mDisAllowed(0),
      mObserver(aObserver),
      mEmbedStatus(eNoEmbed),
      mIsTopCompiler(false),
      mDoneWithThisStylesheet(false),
      mNextInstrPtr(nullptr),
      mToplevelIterator(nullptr),
      mReferrerPolicy(ReferrerPolicy::_empty) {
  // Embedded stylesheets have another handler, which is set in
  // txStylesheetCompiler::init if the baseURI has a fragment identifier.
  mHandlerTable = gTxRootHandler;
}

nsresult txStylesheetCompilerState::init(const nsAString& aStylesheetURI,
                                         ReferrerPolicy aReferrerPolicy,
                                         txStylesheet* aStylesheet,
                                         txListIterator* aInsertPosition) {
  NS_ASSERTION(!aStylesheet || aInsertPosition,
               "must provide insertposition if loading subsheet");
  mStylesheetURI = aStylesheetURI;
  mReferrerPolicy = aReferrerPolicy;
  // Check for fragment identifier of an embedded stylesheet.
  int32_t fragment = aStylesheetURI.FindChar('#') + 1;
  if (fragment > 0) {
    int32_t fragmentLength = aStylesheetURI.Length() - fragment;
    if (fragmentLength > 0) {
      // This is really an embedded stylesheet, not just a
      // "url#". We may want to unescape the fragment.
      mTarget = Substring(aStylesheetURI, (uint32_t)fragment, fragmentLength);
      mEmbedStatus = eNeedEmbed;
      mHandlerTable = gTxEmbedHandler;
    }
  }
  nsresult rv = NS_OK;
  if (aStylesheet) {
    mStylesheet = aStylesheet;
    mToplevelIterator = *aInsertPosition;
    mIsTopCompiler = false;
  } else {
    mStylesheet = new txStylesheet;
    rv = mStylesheet->init();
    NS_ENSURE_SUCCESS(rv, rv);

    mToplevelIterator =
        txListIterator(&mStylesheet->mRootFrame->mToplevelItems);
    mToplevelIterator.next();  // go to the end of the list
    mIsTopCompiler = true;
  }

  mElementContext = MakeUnique<txElementContext>(aStylesheetURI);

  // Push the "old" txElementContext
  pushObject(nullptr);

  return NS_OK;
}

txStylesheetCompilerState::~txStylesheetCompilerState() {
  while (!mObjectStack.isEmpty()) {
    delete popObject();
  }
}

void txStylesheetCompilerState::pushHandlerTable(txHandlerTable* aTable) {
  pushPtr(mHandlerTable, eHandlerTable);
  mHandlerTable = aTable;
}

void txStylesheetCompilerState::popHandlerTable() {
  mHandlerTable = static_cast<txHandlerTable*>(popPtr(eHandlerTable));
}

void txStylesheetCompilerState::pushSorter(txPushNewContext* aSorter) {
  pushPtr(mSorter, ePushNewContext);
  mSorter = aSorter;
}

void txStylesheetCompilerState::popSorter() {
  mSorter = static_cast<txPushNewContext*>(popPtr(ePushNewContext));
}

void txStylesheetCompilerState::pushChooseGotoList() {
  pushObject(mChooseGotoList.release());
  mChooseGotoList = MakeUnique<txList>();
}

void txStylesheetCompilerState::popChooseGotoList() {
  // this will delete the old value
  mChooseGotoList = WrapUnique(static_cast<txList*>(popObject()));
}

void txStylesheetCompilerState::pushObject(txObject* aObject) {
  mObjectStack.push(aObject);
}

txObject* txStylesheetCompilerState::popObject() {
  return static_cast<txObject*>(mObjectStack.pop());
}

void txStylesheetCompilerState::pushPtr(void* aPtr, enumStackType aType) {
#ifdef TX_DEBUG_STACK
  MOZ_LOG(txLog::xslt, LogLevel::Debug,
          ("pushPtr: 0x%x type %u\n", aPtr, aType));
#endif
  mTypeStack.AppendElement(aType);
  mOtherStack.push(aPtr);
}

void* txStylesheetCompilerState::popPtr(enumStackType aType) {
  if (mTypeStack.IsEmpty()) {
    MOZ_CRASH("Attempt to pop when type stack is empty");
  }

  enumStackType type = mTypeStack.PopLastElement();
  void* value = mOtherStack.pop();

#ifdef TX_DEBUG_STACK
  MOZ_LOG(txLog::xslt, LogLevel::Debug,
          ("popPtr: 0x%x type %u requested %u\n", value, type, aType));
#endif

  if (type != aType) {
    MOZ_CRASH("Expected type does not match top element type");
  }

  return value;
}

void txStylesheetCompilerState::addToplevelItem(txToplevelItem* aItem) {
  mToplevelIterator.addBefore(aItem);
}

nsresult txStylesheetCompilerState::openInstructionContainer(
    txInstructionContainer* aContainer) {
  MOZ_ASSERT(!mNextInstrPtr, "can't nest instruction-containers");

  mNextInstrPtr = &aContainer->mFirstInstruction;
  return NS_OK;
}

void txStylesheetCompilerState::closeInstructionContainer() {
  NS_ASSERTION(mGotoTargetPointers.IsEmpty(),
               "GotoTargets still exists, did you forget to add txReturn?");
  mNextInstrPtr = 0;
}

txInstruction* txStylesheetCompilerState::addInstruction(
    UniquePtr<txInstruction>&& aInstruction) {
  MOZ_ASSERT(mNextInstrPtr, "adding instruction outside container");

  txInstruction* newInstr = aInstruction.get();

  *mNextInstrPtr = std::move(aInstruction);
  mNextInstrPtr = &newInstr->mNext;

  uint32_t i, count = mGotoTargetPointers.Length();
  for (i = 0; i < count; ++i) {
    *mGotoTargetPointers[i] = newInstr;
  }
  mGotoTargetPointers.Clear();

  return newInstr;
}

nsresult txStylesheetCompilerState::loadIncludedStylesheet(
    const nsAString& aURI) {
  MOZ_LOG(txLog::xslt, LogLevel::Info,
          ("CompilerState::loadIncludedStylesheet: %s\n",
           NS_LossyConvertUTF16toASCII(aURI).get()));
  if (mStylesheetURI.Equals(aURI)) {
    return NS_ERROR_XSLT_LOAD_RECURSION;
  }
  NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED);

  UniquePtr<txToplevelItem> item(new txDummyItem);

  mToplevelIterator.addBefore(item.release());

  // step back to the dummy-item
  mToplevelIterator.previous();

  txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this);

  RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler(
      aURI, mStylesheet, &mToplevelIterator, mReferrerPolicy, observer);

  // step forward before calling the observer in case of syncronous loading
  mToplevelIterator.next();

  mChildCompilerList.AppendElement(compiler);

  nsresult rv =
      mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler);
  if (NS_FAILED(rv)) {
    mChildCompilerList.RemoveElement(compiler);
  }

  return rv;
}

nsresult txStylesheetCompilerState::loadImportedStylesheet(
    const nsAString& aURI, txStylesheet::ImportFrame* aFrame) {
  MOZ_LOG(txLog::xslt, LogLevel::Info,
          ("CompilerState::loadImportedStylesheet: %s\n",
           NS_LossyConvertUTF16toASCII(aURI).get()));
  if (mStylesheetURI.Equals(aURI)) {
    return NS_ERROR_XSLT_LOAD_RECURSION;
  }
  NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED);

  txListIterator iter(&aFrame->mToplevelItems);
  iter.next();  // go to the end of the list

  txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this);

  RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler(
      aURI, mStylesheet, &iter, mReferrerPolicy, observer);

  mChildCompilerList.AppendElement(compiler);

  nsresult rv =
      mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler);
  if (NS_FAILED(rv)) {
    mChildCompilerList.RemoveElement(compiler);
  }

  return rv;
}

void txStylesheetCompilerState::addGotoTarget(txInstruction** aTargetPointer) {
  mGotoTargetPointers.AppendElement(aTargetPointer);
}

void txStylesheetCompilerState::addVariable(const txExpandedName& aName) {
  mInScopeVariables.AppendElement(aName);
}

nsresult txStylesheetCompilerState::resolveNamespacePrefix(nsAtom* aPrefix,
                                                           int32_t& aID) {
  NS_ASSERTION(aPrefix && aPrefix != nsGkAtoms::_empty,
               "caller should handle default namespace ''");
  aID = mElementContext->mMappings->lookupNamespace(aPrefix);
  return (aID != kNameSpaceID_Unknown) ? NS_OK : NS_ERROR_FAILURE;
}

/**
 * Error Function to be used for unknown extension functions.
 *
 */
class txErrorFunctionCall : public FunctionCall {
 public:
  explicit txErrorFunctionCall(nsAtom* aName) : mName(aName) {}

  TX_DECL_FUNCTION

 private:
  RefPtr<nsAtom> mName;
};

nsresult txErrorFunctionCall::evaluate(txIEvalContext* aContext,
                                       txAExprResult** aResult) {
  *aResult = nullptr;

  return NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION;
}

Expr::ResultType txErrorFunctionCall::getReturnType() {
  // It doesn't really matter what we return here, but it might
  // be a good idea to try to keep this as unoptimizable as possible
  return ANY_RESULT;
}

bool txErrorFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
  // It doesn't really matter what we return here, but it might
  // be a good idea to try to keep this as unoptimizable as possible
  return true;
}

#ifdef TX_TO_STRING
void txErrorFunctionCall::appendName(nsAString& aDest) {
  aDest.Append(mName->GetUTF16String());
}
#endif

static nsresult TX_ConstructXSLTFunction(nsAtom* aName,
                                         txStylesheetCompilerState* aState,
                                         FunctionCall** aFunction) {
  if (aName == nsGkAtoms::document) {
    *aFunction = new DocumentFunctionCall(aState->mElementContext->mBaseURI);
  } else if (aName == nsGkAtoms::key) {
    if (!aState->allowed(txIParseContext::KEY_FUNCTION)) {
      return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED;
    }
    *aFunction = new txKeyFunctionCall(aState->mElementContext->mMappings);
  } else if (aName == nsGkAtoms::formatNumber) {
    *aFunction = new txFormatNumberFunctionCall(
        aState->mStylesheet, aState->mElementContext->mMappings);
  } else if (aName == nsGkAtoms::current) {
    *aFunction = new CurrentFunctionCall();
  } else if (aName == nsGkAtoms::unparsedEntityUri) {
    return NS_ERROR_NOT_IMPLEMENTED;
  } else if (aName == nsGkAtoms::generateId) {
    *aFunction = new GenerateIdFunctionCall();
  } else if (aName == nsGkAtoms::systemProperty) {
    *aFunction = new txXSLTEnvironmentFunctionCall(
        txXSLTEnvironmentFunctionCall::SYSTEM_PROPERTY,
        aState->mElementContext->mMappings);
  } else if (aName == nsGkAtoms::elementAvailable) {
    *aFunction = new txXSLTEnvironmentFunctionCall(
        txXSLTEnvironmentFunctionCall::ELEMENT_AVAILABLE,
        aState->mElementContext->mMappings);
  } else if (aName == nsGkAtoms::functionAvailable) {
    *aFunction = new txXSLTEnvironmentFunctionCall(
        txXSLTEnvironmentFunctionCall::FUNCTION_AVAILABLE,
        aState->mElementContext->mMappings);
  } else {
    return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
  }

  MOZ_ASSERT(*aFunction);
  return NS_OK;
}

extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID,
                                          txStylesheetCompilerState* aState,
                                          FunctionCall** aResult);

static nsresult findFunction(nsAtom* aName, int32_t aNamespaceID,
                             txStylesheetCompilerState* aState,
                             FunctionCall** aResult) {
  if (aNamespaceID == kNameSpaceID_None) {
    return TX_ConstructXSLTFunction(aName, aState, aResult);
  }

  return TX_ConstructEXSLTFunction(aName, aNamespaceID, aState, aResult);
}

extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID) {
  RefPtr<txStylesheetCompiler> compiler =
      new txStylesheetCompiler(u""_ns, ReferrerPolicy::_empty, nullptr);
  NS_ENSURE_TRUE(compiler, false);

  UniquePtr<FunctionCall> fnCall;

  return NS_SUCCEEDED(
      findFunction(aName, aNameSpaceID, compiler, getter_Transfers(fnCall)));
}

nsresult txStylesheetCompilerState::resolveFunctionCall(
    nsAtom* aName, int32_t aID, FunctionCall** aFunction) {
  *aFunction = nullptr;

  nsresult rv = findFunction(aName, aID, this, aFunction);
  if (rv == NS_ERROR_XPATH_UNKNOWN_FUNCTION &&
      (aID != kNameSpaceID_None || fcp())) {
    *aFunction = new txErrorFunctionCall(aName);
    rv = NS_OK;
  }

  return rv;
}

bool txStylesheetCompilerState::caseInsensitiveNameTests() { return false; }

void txStylesheetCompilerState::SetErrorOffset(uint32_t aOffset) {
  // XXX implement me
}

txElementContext::txElementContext(const nsAString& aBaseURI)
    : mPreserveWhitespace(false),
      mForwardsCompatibleParsing(true),
      mBaseURI(aBaseURI),
      mMappings(new txNamespaceMap),
      mDepth(0) {
  mInstructionNamespaces.AppendElement(kNameSpaceID_XSLT);
}

txElementContext::txElementContext(const txElementContext& aOther)
    : mPreserveWhitespace(aOther.mPreserveWhitespace),
      mForwardsCompatibleParsing(aOther.mForwardsCompatibleParsing),
      mBaseURI(aOther.mBaseURI),
      mMappings(aOther.mMappings),
      mDepth(0) {
  mInstructionNamespaces = aOther.mInstructionNamespaces.Clone();
}
