/* -*- 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 "mozilla/ArrayUtils.h"
#include "mozilla/EnumeratedArray.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MacroArgs.h"
#include "mozilla/MacroForEach.h"

#include "nsAtom.h"
#include "nsGkAtoms.h"
#include "txExecutionState.h"
#include "txExpr.h"
#include "txIXPathContext.h"
#include "txIEXSLTFunctions.h"
#include "txNodeSet.h"
#include "txOutputFormat.h"
#include "txRtfHandler.h"
#include "txXPathTreeWalker.h"
#include "nsImportModule.h"
#include "nsPrintfCString.h"
#include "nsComponentManagerUtils.h"
#include "nsContentCID.h"
#include "nsContentCreatorFunctions.h"
#include "nsIContent.h"
#include "txMozillaXMLOutput.h"
#include "nsTextNode.h"
#include "mozilla/dom/DocumentFragmentBinding.h"
#include "prtime.h"
#include "xpcprivate.h"

using namespace mozilla;
using namespace mozilla::dom;

class txStylesheetCompilerState;

// ------------------------------------------------------------------
// Utility functions
// ------------------------------------------------------------------

static Document* getSourceDocument(txIEvalContext* aContext) {
  txExecutionState* es =
      static_cast<txExecutionState*>(aContext->getPrivateContext());
  if (!es) {
    NS_ERROR("Need txExecutionState!");

    return nullptr;
  }

  const txXPathNode& document = es->getSourceDocument();
  return txXPathNativeNode::getDocument(document);
}

static nsresult convertRtfToNode(txIEvalContext* aContext,
                                 txResultTreeFragment* aRtf) {
  Document* doc = getSourceDocument(aContext);
  if (!doc) {
    return NS_ERROR_UNEXPECTED;
  }

  RefPtr<DocumentFragment> domFragment =
      new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());

  txOutputFormat format;
  txMozillaXMLOutput mozHandler(&format, domFragment, true);

  nsresult rv = aRtf->flushToHandler(&mozHandler);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mozHandler.closePrevious(true);
  NS_ENSURE_SUCCESS(rv, rv);

  // The txResultTreeFragment will own this.
  const txXPathNode* node =
      txXPathNativeNode::createXPathNode(domFragment, true);
  NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);

  aRtf->setNode(node);

  return NS_OK;
}

static nsresult createTextNode(txIEvalContext* aContext, nsString& aValue,
                               txXPathNode** aResult) {
  Document* doc = getSourceDocument(aContext);
  if (!doc) {
    return NS_ERROR_UNEXPECTED;
  }

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

  nsresult rv = text->SetText(aValue, false);
  NS_ENSURE_SUCCESS(rv, rv);

  *aResult = txXPathNativeNode::createXPathNode(text, true);
  NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);

  return NS_OK;
}

static nsresult createAndAddToResult(nsAtom* aName, const nsAString& aValue,
                                     txNodeSet* aResultSet,
                                     DocumentFragment* aResultHolder) {
  Document* doc = aResultHolder->OwnerDoc();
  nsCOMPtr<Element> elem =
      doc->CreateElem(nsDependentAtomString(aName), nullptr, kNameSpaceID_None);
  NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);

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

  nsresult rv = text->SetText(aValue, false);
  NS_ENSURE_SUCCESS(rv, rv);

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

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

  UniquePtr<txXPathNode> xpathNode(
      txXPathNativeNode::createXPathNode(elem, true));
  NS_ENSURE_TRUE(xpathNode, NS_ERROR_OUT_OF_MEMORY);

  aResultSet->append(*xpathNode);

  return NS_OK;
}

// Need to update this array if types are added to the ResultType enum in
// txAExprResult.
static const char* const sTypes[] = {"node-set", "boolean", "number", "string",
                                     "RTF"};

// ------------------------------------------------------------------
// Function implementations
// ------------------------------------------------------------------

enum class txEXSLTType {
  // http://exslt.org/common
  NODE_SET,
  OBJECT_TYPE,

  // http://exslt.org/dates-and-times
  DATE_TIME,

  // http://exslt.org/math
  MAX,
  MIN,
  HIGHEST,
  LOWEST,

  // http://exslt.org/regular-expressions
  MATCH,
  REPLACE,
  TEST,

  // http://exslt.org/sets
  DIFFERENCE_,  // not DIFFERENCE to avoid a conflict with a winuser.h macro
  DISTINCT,
  HAS_SAME_NODE,
  INTERSECTION,
  LEADING,
  TRAILING,

  // http://exslt.org/strings
  CONCAT,
  SPLIT,
  TOKENIZE,

  _LIMIT,
};

struct txEXSLTFunctionDescriptor {
  int8_t mMinParams;
  int8_t mMaxParams;
  Expr::ResultType mReturnType;
  nsStaticAtom* mName;
  bool (*mCreator)(txEXSLTType, FunctionCall**);
  int32_t mNamespaceID;
};

static EnumeratedArray<txEXSLTType, txEXSLTType::_LIMIT,
                       txEXSLTFunctionDescriptor>
    descriptTable;

class txEXSLTFunctionCall : public FunctionCall {
 public:
  explicit txEXSLTFunctionCall(txEXSLTType aType) : mType(aType) {}

  static bool Create(txEXSLTType aType, FunctionCall** aFunction) {
    *aFunction = new txEXSLTFunctionCall(aType);
    return true;
  }

  TX_DECL_FUNCTION

 private:
  txEXSLTType mType;
};

class txEXSLTRegExFunctionCall : public FunctionCall {
 public:
  explicit txEXSLTRegExFunctionCall(txEXSLTType aType) : mType(aType) {}

  static bool Create(txEXSLTType aType, FunctionCall** aFunction) {
    *aFunction = new txEXSLTRegExFunctionCall(aType);
    return true;
  }

  TX_DECL_FUNCTION

 private:
  txEXSLTType mType;
};

nsresult txEXSLTFunctionCall::evaluate(txIEvalContext* aContext,
                                       txAExprResult** aResult) {
  *aResult = nullptr;
  if (!requireParams(descriptTable[mType].mMinParams,
                     descriptTable[mType].mMaxParams, aContext)) {
    return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
  }

  nsresult rv = NS_OK;
  switch (mType) {
    case txEXSLTType::NODE_SET: {
      RefPtr<txAExprResult> exprResult;
      rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
      NS_ENSURE_SUCCESS(rv, rv);

      if (exprResult->getResultType() == txAExprResult::NODESET) {
        exprResult.forget(aResult);
      } else {
        RefPtr<txNodeSet> resultSet;
        rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
        NS_ENSURE_SUCCESS(rv, rv);

        if (exprResult->getResultType() ==
            txAExprResult::RESULT_TREE_FRAGMENT) {
          txResultTreeFragment* rtf =
              static_cast<txResultTreeFragment*>(exprResult.get());

          const txXPathNode* node = rtf->getNode();
          if (!node) {
            rv = convertRtfToNode(aContext, rtf);
            NS_ENSURE_SUCCESS(rv, rv);

            node = rtf->getNode();
          }

          resultSet->append(*node);
        } else {
          nsAutoString value;
          exprResult->stringValue(value);

          UniquePtr<txXPathNode> node;
          rv = createTextNode(aContext, value, getter_Transfers(node));
          NS_ENSURE_SUCCESS(rv, rv);

          resultSet->append(*node);
        }

        NS_ADDREF(*aResult = resultSet);
      }

      return NS_OK;
    }
    case txEXSLTType::OBJECT_TYPE: {
      RefPtr<txAExprResult> exprResult;
      nsresult rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<StringResult> strRes;
      rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
      NS_ENSURE_SUCCESS(rv, rv);

      AppendASCIItoUTF16(MakeStringSpan(sTypes[exprResult->getResultType()]),
                         strRes->mValue);

      NS_ADDREF(*aResult = strRes);

      return NS_OK;
    }
    case txEXSLTType::DIFFERENCE_:
    case txEXSLTType::INTERSECTION: {
      RefPtr<txNodeSet> nodes1;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<txNodeSet> nodes2;
      rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      bool insertOnFound = mType == txEXSLTType::INTERSECTION;

      int32_t searchPos = 0;
      int32_t i, len = nodes1->size();
      for (i = 0; i < len; ++i) {
        const txXPathNode& node = nodes1->get(i);
        int32_t foundPos = nodes2->indexOf(node, searchPos);
        if (foundPos >= 0) {
          searchPos = foundPos + 1;
        }

        if ((foundPos >= 0) == insertOnFound) {
          rv = resultSet->append(node);
          NS_ENSURE_SUCCESS(rv, rv);
        }
      }

      NS_ADDREF(*aResult = resultSet);

      return NS_OK;
    }
    case txEXSLTType::DISTINCT: {
      RefPtr<txNodeSet> nodes;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      nsTHashSet<nsString> hash;

      int32_t i, len = nodes->size();
      for (i = 0; i < len; ++i) {
        nsAutoString str;
        const txXPathNode& node = nodes->get(i);
        txXPathNodeUtils::appendNodeValue(node, str);
        if (hash.EnsureInserted(str)) {
          rv = resultSet->append(node);
          NS_ENSURE_SUCCESS(rv, rv);
        }
      }

      NS_ADDREF(*aResult = resultSet);

      return NS_OK;
    }
    case txEXSLTType::HAS_SAME_NODE: {
      RefPtr<txNodeSet> nodes1;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<txNodeSet> nodes2;
      rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
      NS_ENSURE_SUCCESS(rv, rv);

      bool found = false;
      int32_t i, len = nodes1->size();
      for (i = 0; i < len; ++i) {
        if (nodes2->contains(nodes1->get(i))) {
          found = true;
          break;
        }
      }

      aContext->recycler()->getBoolResult(found, aResult);

      return NS_OK;
    }
    case txEXSLTType::LEADING:
    case txEXSLTType::TRAILING: {
      RefPtr<txNodeSet> nodes1;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<txNodeSet> nodes2;
      rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
      NS_ENSURE_SUCCESS(rv, rv);

      if (nodes2->isEmpty()) {
        *aResult = nodes1;
        NS_ADDREF(*aResult);

        return NS_OK;
      }

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      int32_t end = nodes1->indexOf(nodes2->get(0));
      if (end >= 0) {
        int32_t i = 0;
        if (mType == txEXSLTType::TRAILING) {
          i = end + 1;
          end = nodes1->size();
        }
        for (; i < end; ++i) {
          rv = resultSet->append(nodes1->get(i));
          NS_ENSURE_SUCCESS(rv, rv);
        }
      }

      NS_ADDREF(*aResult = resultSet);

      return NS_OK;
    }
    case txEXSLTType::CONCAT: {
      RefPtr<txNodeSet> nodes;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString str;
      int32_t i, len = nodes->size();
      for (i = 0; i < len; ++i) {
        txXPathNodeUtils::appendNodeValue(nodes->get(i), str);
      }

      return aContext->recycler()->getStringResult(str, aResult);
    }
    case txEXSLTType::SPLIT:
    case txEXSLTType::TOKENIZE: {
      // Evaluate parameters
      nsAutoString string;
      rv = mParams[0]->evaluateToString(aContext, string);
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString pattern;
      if (mParams.Length() == 2) {
        rv = mParams[1]->evaluateToString(aContext, pattern);
        NS_ENSURE_SUCCESS(rv, rv);
      } else if (mType == txEXSLTType::SPLIT) {
        pattern.Assign(' ');
      } else {
        pattern.AssignLiteral("\t\r\n ");
      }

      // Set up holders for the result
      Document* sourceDoc = getSourceDocument(aContext);
      NS_ENSURE_STATE(sourceDoc);

      RefPtr<DocumentFragment> docFrag = new (sourceDoc->NodeInfoManager())
          DocumentFragment(sourceDoc->NodeInfoManager());

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      uint32_t tailIndex;

      // Start splitting
      if (pattern.IsEmpty()) {
        nsString::const_char_iterator start = string.BeginReading();
        nsString::const_char_iterator end = string.EndReading();
        for (; start < end; ++start) {
          rv = createAndAddToResult(nsGkAtoms::token,
                                    Substring(start, start + 1), resultSet,
                                    docFrag);
          NS_ENSURE_SUCCESS(rv, rv);
        }

        tailIndex = string.Length();
      } else if (mType == txEXSLTType::SPLIT) {
        nsAString::const_iterator strStart, strEnd;
        string.BeginReading(strStart);
        string.EndReading(strEnd);
        nsAString::const_iterator start = strStart, end = strEnd;

        while (FindInReadable(pattern, start, end)) {
          if (start != strStart) {
            rv = createAndAddToResult(nsGkAtoms::token,
                                      Substring(strStart, start), resultSet,
                                      docFrag);
            NS_ENSURE_SUCCESS(rv, rv);
          }
          strStart = start = end;
          end = strEnd;
        }

        tailIndex = strStart.get() - string.get();
      } else {
        int32_t found, start = 0;
        while ((found = string.FindCharInSet(pattern, start)) != kNotFound) {
          if (found != start) {
            rv = createAndAddToResult(nsGkAtoms::token,
                                      Substring(string, start, found - start),
                                      resultSet, docFrag);
            NS_ENSURE_SUCCESS(rv, rv);
          }
          start = found + 1;
        }

        tailIndex = start;
      }

      // Add tail if needed
      if (tailIndex != (uint32_t)string.Length()) {
        rv = createAndAddToResult(
            nsGkAtoms::token, Substring(string, tailIndex), resultSet, docFrag);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      NS_ADDREF(*aResult = resultSet);

      return NS_OK;
    }
    case txEXSLTType::MAX:
    case txEXSLTType::MIN: {
      RefPtr<txNodeSet> nodes;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
      NS_ENSURE_SUCCESS(rv, rv);

      if (nodes->isEmpty()) {
        return aContext->recycler()->getNumberResult(UnspecifiedNaN<double>(),
                                                     aResult);
      }

      bool findMax = mType == txEXSLTType::MAX;

      double res = findMax ? mozilla::NegativeInfinity<double>()
                           : mozilla::PositiveInfinity<double>();
      int32_t i, len = nodes->size();
      for (i = 0; i < len; ++i) {
        nsAutoString str;
        txXPathNodeUtils::appendNodeValue(nodes->get(i), str);
        double val = txDouble::toDouble(str);
        if (std::isnan(val)) {
          res = UnspecifiedNaN<double>();
          break;
        }

        if (findMax ? (val > res) : (val < res)) {
          res = val;
        }
      }

      return aContext->recycler()->getNumberResult(res, aResult);
    }
    case txEXSLTType::HIGHEST:
    case txEXSLTType::LOWEST: {
      RefPtr<txNodeSet> nodes;
      rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
      NS_ENSURE_SUCCESS(rv, rv);

      if (nodes->isEmpty()) {
        NS_ADDREF(*aResult = nodes);

        return NS_OK;
      }

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      bool findMax = mType == txEXSLTType::HIGHEST;
      double res = findMax ? mozilla::NegativeInfinity<double>()
                           : mozilla::PositiveInfinity<double>();
      int32_t i, len = nodes->size();
      for (i = 0; i < len; ++i) {
        nsAutoString str;
        const txXPathNode& node = nodes->get(i);
        txXPathNodeUtils::appendNodeValue(node, str);
        double val = txDouble::toDouble(str);
        if (std::isnan(val)) {
          resultSet->clear();
          break;
        }
        if (findMax ? (val > res) : (val < res)) {
          resultSet->clear();
          res = val;
        }

        if (res == val) {
          rv = resultSet->append(node);
          NS_ENSURE_SUCCESS(rv, rv);
        }
      }

      NS_ADDREF(*aResult = resultSet);

      return NS_OK;
    }
    case txEXSLTType::DATE_TIME: {
      // http://exslt.org/date/functions/date-time/

      PRExplodedTime prtime;
      PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prtime);

      int32_t offset =
          (prtime.tm_params.tp_gmt_offset + prtime.tm_params.tp_dst_offset) /
          60;

      bool isneg = offset < 0;
      if (isneg) offset = -offset;

      StringResult* strRes;
      rv = aContext->recycler()->getStringResult(&strRes);
      NS_ENSURE_SUCCESS(rv, rv);

      // format: YYYY-MM-DDTTHH:MM:SS.sss+00:00
      CopyASCIItoUTF16(
          nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32 "T%02" PRId32
                          ":%02" PRId32 ":%02" PRId32 ".%03" PRId32
                          "%c%02" PRId32 ":%02" PRId32,
                          prtime.tm_year, prtime.tm_month + 1, prtime.tm_mday,
                          prtime.tm_hour, prtime.tm_min, prtime.tm_sec,
                          prtime.tm_usec / 10000, isneg ? '-' : '+',
                          offset / 60, offset % 60),
          strRes->mValue);

      *aResult = strRes;

      return NS_OK;
    }
    default: {
      aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
      return NS_ERROR_UNEXPECTED;
    }
  }

  MOZ_ASSERT_UNREACHABLE("Missing return?");
  return NS_ERROR_UNEXPECTED;
}

Expr::ResultType txEXSLTFunctionCall::getReturnType() {
  return descriptTable[mType].mReturnType;
}

bool txEXSLTFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
  if (mType == txEXSLTType::NODE_SET || mType == txEXSLTType::SPLIT ||
      mType == txEXSLTType::TOKENIZE) {
    return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext);
  }
  return argsSensitiveTo(aContext);
}

#ifdef TX_TO_STRING
void txEXSLTFunctionCall::appendName(nsAString& aDest) {
  aDest.Append(descriptTable[mType].mName->GetUTF16String());
}
#endif

nsresult txEXSLTRegExFunctionCall::evaluate(txIEvalContext* aContext,
                                            txAExprResult** aResult) {
  *aResult = nullptr;
  if (!requireParams(descriptTable[mType].mMinParams,
                     descriptTable[mType].mMaxParams, aContext)) {
    return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
  }

  nsAutoString string;
  nsresult rv = mParams[0]->evaluateToString(aContext, string);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString regex;
  rv = mParams[1]->evaluateToString(aContext, regex);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString flags;
  if (mParams.Length() >= 3) {
    rv = mParams[2]->evaluateToString(aContext, flags);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<txIEXSLTFunctions> funcs =
      do_ImportESModule("resource://gre/modules/txEXSLTRegExFunctions.sys.mjs");
  MOZ_ALWAYS_TRUE(funcs);

  switch (mType) {
    case txEXSLTType::MATCH: {
      nsCOMPtr<Document> sourceDoc = getSourceDocument(aContext);
      NS_ENSURE_STATE(sourceDoc);

      RefPtr<DocumentFragment> docFrag;
      rv = funcs->Match(string, regex, flags, sourceDoc,
                        getter_AddRefs(docFrag));
      NS_ENSURE_SUCCESS(rv, rv);
      NS_ENSURE_STATE(docFrag);

      RefPtr<txNodeSet> resultSet;
      rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
      NS_ENSURE_SUCCESS(rv, rv);

      UniquePtr<txXPathNode> node;
      for (nsIContent* result = docFrag->GetFirstChild(); result;
           result = result->GetNextSibling()) {
        node = WrapUnique(txXPathNativeNode::createXPathNode(result, true));
        rv = resultSet->add(*node);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      resultSet.forget(aResult);

      return NS_OK;
    }
    case txEXSLTType::REPLACE: {
      nsAutoString replace;
      rv = mParams[3]->evaluateToString(aContext, replace);
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString result;
      rv = funcs->Replace(string, regex, flags, replace, result);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = aContext->recycler()->getStringResult(result, aResult);
      NS_ENSURE_SUCCESS(rv, rv);

      return NS_OK;
    }
    case txEXSLTType::TEST: {
      bool result;
      rv = funcs->Test(string, regex, flags, &result);
      NS_ENSURE_SUCCESS(rv, rv);

      aContext->recycler()->getBoolResult(result, aResult);

      return NS_OK;
    }
    default: {
      aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
      return NS_ERROR_UNEXPECTED;
    }
  }

  MOZ_ASSERT_UNREACHABLE("Missing return?");
  return NS_ERROR_UNEXPECTED;
}

Expr::ResultType txEXSLTRegExFunctionCall::getReturnType() {
  return descriptTable[mType].mReturnType;
}

bool txEXSLTRegExFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
  if (mType == txEXSLTType::MATCH) {
    return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext);
  }
  return argsSensitiveTo(aContext);
}

#ifdef TX_TO_STRING
void txEXSLTRegExFunctionCall::appendName(nsAString& aDest) {
  aDest.Append(descriptTable[mType].mName->GetUTF16String());
}
#endif

extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID,
                                          txStylesheetCompilerState* aState,
                                          FunctionCall** aResult) {
  for (auto i : MakeEnumeratedRange(txEXSLTType::_LIMIT)) {
    const txEXSLTFunctionDescriptor& desc = descriptTable[i];
    if (aName == desc.mName && aNamespaceID == desc.mNamespaceID) {
      return desc.mCreator(i, aResult) ? NS_OK : NS_ERROR_FAILURE;
    }
  }

  return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
}

extern bool TX_InitEXSLTFunction() {
#define EXSLT_FUNCS(NS, CLASS, ...)                                         \
  {                                                                         \
    int32_t nsid = txNamespaceManager::getNamespaceID(nsLiteralString(NS)); \
    if (nsid == kNameSpaceID_Unknown) {                                     \
      return false;                                                         \
    }                                                                       \
    MOZ_FOR_EACH(EXSLT_FUNC, (nsid, CLASS, ), (__VA_ARGS__))                \
  }

#define EXSLT_FUNC(NS, CLASS, ...)                      \
  descriptTable[txEXSLTType::MOZ_ARG_1 __VA_ARGS__] = { \
      MOZ_ARGS_AFTER_1 __VA_ARGS__, CLASS::Create, NS};

  EXSLT_FUNCS(u"http://exslt.org/common", txEXSLTFunctionCall,
              (NODE_SET, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::nodeSet),
              (OBJECT_TYPE, 1, 1, Expr::STRING_RESULT, nsGkAtoms::objectType))

  EXSLT_FUNCS(u"http://exslt.org/dates-and-times", txEXSLTFunctionCall,
              (DATE_TIME, 0, 0, Expr::STRING_RESULT, nsGkAtoms::dateTime))

  EXSLT_FUNCS(u"http://exslt.org/math", txEXSLTFunctionCall,
              (MAX, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::max),
              (MIN, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::min),
              (HIGHEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::highest),
              (LOWEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::lowest))

  EXSLT_FUNCS(u"http://exslt.org/regular-expressions", txEXSLTRegExFunctionCall,
              (MATCH, 2, 3, Expr::NODESET_RESULT, nsGkAtoms::match),
              (REPLACE, 4, 4, Expr::STRING_RESULT, nsGkAtoms::replace),
              (TEST, 2, 3, Expr::BOOLEAN_RESULT, nsGkAtoms::test))

  EXSLT_FUNCS(
      u"http://exslt.org/sets", txEXSLTFunctionCall,
      (DIFFERENCE_, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference),
      (DISTINCT, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::distinct),
      (HAS_SAME_NODE, 2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::hasSameNode),
      (INTERSECTION, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::intersection),
      (LEADING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::leading),
      (TRAILING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::trailing))

  EXSLT_FUNCS(u"http://exslt.org/strings", txEXSLTFunctionCall,
              (CONCAT, 1, 1, Expr::STRING_RESULT, nsGkAtoms::concat),
              (SPLIT, 1, 2, Expr::STRING_RESULT, nsGkAtoms::split),
              (TOKENIZE, 1, 2, Expr::STRING_RESULT, nsGkAtoms::tokenize))

#undef EXSLT_FUNCS
#undef EXSLT_FUNC
#undef EXSLT_FUNC_HELPER
#undef EXSLT_FUNC_HELPER2

  return true;
}
