/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.daffodil.dpath

import org.apache.daffodil.exceptions._
import org.apache.daffodil.dsom._
import scala.xml.NamespaceBinding
import org.apache.daffodil.xml._
import org.apache.daffodil.processors._
import org.apache.daffodil.xml.RefQName
import scala.util.{ Success, Failure }
import org.apache.daffodil.dsom.RelativePathPastRootError
import org.apache.daffodil.equality._
import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
import java.lang.{ Long => JLong, Integer => JInt, Boolean => JBoolean, Double => JDouble }
import org.apache.daffodil.util.Numbers
import org.apache.daffodil.api.WarnID
import org.apache.daffodil.infoset.DataValue.DataValuePrimitive
import org.apache.daffodil.udf.UserDefinedFunctionService
import org.apache.daffodil.BasicComponent
import org.apache.daffodil.api.DaffodilTunables
import org.apache.daffodil.oolag.OOLAG.OOLAGHostImpl
import org.apache.daffodil.oolag.OOLAG.OOLAGHost
import org.apache.daffodil.api.UnqualifiedPathStepPolicy

/**
 * Root class of the type hierarchy for the AST nodes used when we
 * compile a DPath Expression.
 *
 * All the work in 'compiling' the DPath expressions happens on methods
 * of these objects.
 *
 * This is the OOLAG pattern again.
 */
abstract class Expression extends OOLAGHostImpl()
  with BasicComponent {

  /**
   * Use several calls instead of one, because then OOLAG will try them
   * all separately, and perhaps if you're lucky report more errors.
   */
  requiredEvaluationsAlways(parent)
  requiredEvaluationsAlways(targetType)
  requiredEvaluationsAlways(inherentType)
  requiredEvaluationsAlways(isTypeCorrect)
  requiredEvaluationsAlways(compiledDPath_)

  override lazy val tunable: DaffodilTunables = parent.tunable
  override lazy val unqualifiedPathStepPolicy: UnqualifiedPathStepPolicy = parent.unqualifiedPathStepPolicy
  /**
   * Override where we traverse/access elements.
   */
  def leafContentLengthReferencedElements = ReferencedElementInfos.None
  def leafValueLengthReferencedElements = ReferencedElementInfos.None

  def contentReferencedElementInfos: Set[DPathElementCompileInfo] = {
    val clds = children
    val cldsreis: Set[DPathElementCompileInfo] =
      clds.foldLeft(ReferencedElementInfos.None) {
        (s, item) =>
          {
            val ireis = item.contentReferencedElementInfos
            s.union(ireis)
          }
      }
    val res = cldsreis ++
      leafContentLengthReferencedElements
    res
  }

  def valueReferencedElementInfos: Set[DPathElementCompileInfo] = {
    val clds = children
    val cldsreis: Set[DPathElementCompileInfo] =
      clds.foldLeft(ReferencedElementInfos.None) {
        (s, item) =>
          {
            val ireis = item.valueReferencedElementInfos
            s.union(ireis)
          }
      }
    val res = cldsreis ++
      leafValueLengthReferencedElements
    res
  }

  // This split allows overrides of this lazy val to still reuse this
  // (super.isTypeCorrect doesn't work inside an overridden lazy val.)
  lazy val isTypeCorrect: Boolean = checkTypeCorrectness
  //
  // override for other checking beyond what is needed to do conversions
  //
  protected lazy val checkTypeCorrectness =
    children.forall { child =>
      child.isTypeCorrect
    }

  def text: String

  def hasReferenceTo(elem: DPathElementCompileInfo): Boolean = {
    children.exists(_.hasReferenceTo(elem))
  }

  /**
   * TODO: get more precise line and column information for
   * pointing at sub-regions of large DPath expressions
   *
   * We're parsing them, so we should have access to specific locations
   * within the expression.
   */
  lazy val schemaFileLocation = compileInfo.schemaFileLocation

  lazy val conversions = {
    val inh = inherentType
    val tt = targetType
    val res = Conversion.conversionOps(inh, tt, this)
    res
  }

  private def compiledDPath_ = LV('compiledDPath) { compiledDPath }
  def compiledDPath: CompiledDPath

  final lazy val parentOpt = Option(parent)

  final lazy val parent: Expression = {
    if (!this.hasOOLAGRootSetup)
      Assert.invariantFailed("not setup")
    this match {
      case w: WholeExpression => null
      case _ => this.oolagContext.asInstanceOf[Expression]
    }
  }

  final lazy val wholeExpression: Expression = {
    parent match {
      case w: WholeExpression => w
      case null => this
      case _ => parent.wholeExpression
    }
  }

  final lazy val wholeExpressionText = wholeExpression.text

  lazy val compileInfo: DPathCompileInfo = // override in WholeExpression
    parent.compileInfo

  final lazy val enclosingElementCompileInfos: Seq[DPathElementCompileInfo] =
    compileInfo.enclosingElementCompileInfos

  final lazy val rootElement: DPathElementCompileInfo = {
    val ecis = compileInfo.elementCompileInfos
    if (ecis.length == 0) {
      Assert.invariantFailed("Element doesn't have compile info")
    }
    ecis.head.rootElement
  }

  lazy val namespaces: NamespaceBinding = parent.namespaces

  def children: Seq[Expression]

  def setContextsForChildren(context: OOLAGHost = this): Unit = {
    children.foreach {
      c =>
        c.setOOLAGContext(context);
        c.setContextsForChildren(c);
    }
  }

  /**
   * The target type is defined for simple types. It gives the type that
   * the expression must return. It is the type that the context is expecting
   * or in attribute-grammar terms, the inherited type.
   * <p>
   * For inputValueCalc, this is the type of the element carrying that property.
   * <p>
   * For setVariable and newVariableInstance, it is the type of the variable.
   * <p>
   * For dfdl:length and dfdl:occursCount it is UnsignedInt
   * <p>
   * For the test properties of assert/discriminator it is Boolean
   * <p>
   * For the following properties, when their value is an expression
   * the xsdTargetType is NonEmptyString
   * byteOrder,
   * encoding,
   * outputNewLine,
   * escapeCharacter,
   * escapeEscapeCharacter,
   * initiator,
   * terminator,
   * separator,
   * textStandardDecimalSeparator,
   * textStandardGroupingSeparator,
   * textStandardExponentRep,
   * binaryFloatRep,
   * textBooleanFalseRep,
   * textBooleanTrueRep
   */
  lazy val targetType: NodeInfo.Kind = {
    // this will only be called for expressions that have a parent
    Assert.usage(parentOpt.isDefined)
    val p = parentOpt.get
    val res = p.targetTypeForSubexpression(this)
    res
  }

  def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind

  /**
   * The inherent type is the bottom-up type. That is, the type that
   * is determined by examining the expression, or in attribute-grammar speak,
   * the synthetic type.
   *
   * For "foo" it is string, for
   * 5.0 it is double, and for 5 it is integer. For an identifier such
   * as b in /a/b, it is the simple type of the element declaration for b.
   *
   * The inherent type can be a complex type, as in the foo in /foo/bar.
   * That is why this is not the same system of type-kind values as the
   * xsdTargetType
   */
  def inherentType: NodeInfo.Kind

  def resolveRef(qnameString: String) = {
    QName.resolveRef(qnameString, namespaces, tunable.unqualifiedPathStepPolicy).recover {
      case _: Throwable =>
        SDE("The prefix of '%s' has no corresponding namespace definition.", qnameString)
    }.get
  }
}

abstract class ExpressionLists(val lst: List[Expression])
  extends Expression {
  override lazy val children = lst
}

trait BinaryExpMixin { self: ExpressionLists =>
  Assert.invariant(children.length == 2)
  def left = children(0)
  def right = children(1)
  def op: String
  def text = left.text + " " + op + " " + right.text
}

trait BooleanExpression extends BinaryExpMixin {
  self: ExpressionLists =>

  override lazy val compiledDPath = {
    val leftDPath = left.compiledDPath
    val rightDPath = right.compiledDPath
    val c = conversions
    val res = new CompiledDPath(BooleanOp(op, leftDPath, rightDPath) +: c)
    res
  }

  override def targetTypeForSubexpression(subexp: Expression): NodeInfo.Kind = NodeInfo.Boolean
  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Boolean

}

case class ComparisonExpression(op: String, adds: List[Expression])
  extends ExpressionLists(adds) with BooleanExpression {

  lazy val compareOp: CompareOpBase = {
    import NodeInfo.PrimType._
    import NodeInfo.ArrayIndex
    (op, convergedArgType) match {
      case ("<", _) => subsetError("Unsupported operation '%s'. Use 'lt' instead.", op)
      case (">", _) => subsetError("Unsupported operation '%s'. Use 'gt' instead.", op)
      case ("<=", _) => subsetError("Unsupported operation '%s'. Use 'le' instead.", op)
      case (">=", _) => subsetError("Unsupported operation '%s'. Use 'ge' instead.", op)
      case ("=", _) => subsetError("Unsupported operation '%s'. Use 'eq' instead.", op)
      case ("!=", _) => subsetError("Unsupported operation '%s'. Use 'ne' instead.", op)

      case ("eq", HexBinary) => EQ_CompareByteArray
      case ("ne", HexBinary) => NE_CompareByteArray
      case ("eq", _) => EQ_Compare
      case ("ne", _) => NE_Compare

      case ("lt", Boolean) => LT_Boolean
      case ("gt", Boolean) => GT_Boolean
      case ("le", Boolean) => LE_Boolean
      case ("ge", Boolean) => GE_Boolean

      case ("lt", Date) => LT_Date
      case ("gt", Date) => GT_Date
      case ("le", Date) => LE_Date
      case ("ge", Date) => GE_Date

      case ("lt", Time) => LT_Time
      case ("gt", Time) => GT_Time
      case ("le", Time) => LE_Time
      case ("ge", Time) => GE_Time

      case ("lt", DateTime) => LT_DateTime
      case ("gt", DateTime) => GT_DateTime
      case ("le", DateTime) => LE_DateTime
      case ("ge", DateTime) => GE_DateTime

      case ("lt", String) => LT_String
      case ("gt", String) => GT_String
      case ("le", String) => LE_String
      case ("ge", String) => GE_String

      case ("lt", Decimal) => LT_Decimal
      case ("gt", Decimal) => GT_Decimal
      case ("le", Decimal) => LE_Decimal
      case ("ge", Decimal) => GE_Decimal

      case ("lt", Integer) => LT_Integer
      case ("gt", Integer) => GT_Integer
      case ("le", Integer) => LE_Integer
      case ("ge", Integer) => GE_Integer

      case ("lt", NonNegativeInteger) => LT_NonNegativeInteger
      case ("gt", NonNegativeInteger) => GT_NonNegativeInteger
      case ("le", NonNegativeInteger) => LE_NonNegativeInteger
      case ("ge", NonNegativeInteger) => GE_NonNegativeInteger

      case ("lt", UnsignedLong) => LT_UnsignedLong
      case ("gt", UnsignedLong) => GT_UnsignedLong
      case ("le", UnsignedLong) => LE_UnsignedLong
      case ("ge", UnsignedLong) => GE_UnsignedLong

      case ("lt", Long) => LT_Long
      case ("gt", Long) => GT_Long
      case ("le", Long) => LE_Long
      case ("ge", Long) => GE_Long

      case ("lt", UnsignedInt) => LT_UnsignedInt
      case ("gt", UnsignedInt) => GT_UnsignedInt
      case ("le", UnsignedInt) => LE_UnsignedInt
      case ("ge", UnsignedInt) => GE_UnsignedInt

      case ("lt", ArrayIndex) => LT_UnsignedInt
      case ("gt", ArrayIndex) => GT_UnsignedInt
      case ("le", ArrayIndex) => LE_UnsignedInt
      case ("ge", ArrayIndex) => GE_UnsignedInt

      case ("lt", Int) => LT_Int
      case ("gt", Int) => GT_Int
      case ("le", Int) => LE_Int
      case ("ge", Int) => GE_Int

      case ("lt", UnsignedShort) => LT_UnsignedShort
      case ("gt", UnsignedShort) => GT_UnsignedShort
      case ("le", UnsignedShort) => LE_UnsignedShort
      case ("ge", UnsignedShort) => GE_UnsignedShort

      case ("lt", Short) => LT_Short
      case ("gt", Short) => GT_Short
      case ("le", Short) => LE_Short
      case ("ge", Short) => GE_Short

      case ("lt", UnsignedByte) => LT_UnsignedByte
      case ("gt", UnsignedByte) => GT_UnsignedByte
      case ("le", UnsignedByte) => LE_UnsignedByte
      case ("ge", UnsignedByte) => GE_UnsignedByte

      case ("lt", Byte) => LT_Byte
      case ("gt", Byte) => GT_Byte
      case ("le", Byte) => LE_Byte
      case ("ge", Byte) => GE_Byte

      case ("lt", Float) => LT_Float
      case ("gt", Float) => GT_Float
      case ("le", Float) => LE_Float
      case ("ge", Float) => GE_Float

      case ("lt", Double) => LT_Double
      case ("gt", Double) => GT_Double
      case ("le", Double) => LE_Double
      case ("ge", Double) => GE_Double

      case _ => subsetError("Unsupported operation '%s' on type %s.", op, convergedArgType)
    }
  }

  override lazy val compiledDPath = {
    val leftDPath = left.compiledDPath
    val rightDPath = right.compiledDPath
    val c = conversions
    val res = new CompiledDPath(CompareOperator(compareOp, leftDPath, rightDPath) +: c)
    res
  }

  override def targetTypeForSubexpression(child: Expression): NodeInfo.Kind = convergedArgType

  lazy val convergedArgType = (left.inherentType, right.inherentType) match {
    // String => Numeric conversions are not allowed for comparison Ops.
    //
    // See http://www.w3.org/TR/xpath20/#mapping
    //
    case (left: NodeInfo.String.Kind, right: NodeInfo.String.Kind) =>
      NodeInfo.String
    case (left: NodeInfo.Numeric.Kind, right: NodeInfo.Numeric.Kind) =>
      NodeInfoUtils.generalizeArgTypesForComparisonOp(op, left, right)
    case (left: NodeInfo.Date.Kind, right: NodeInfo.Date.Kind) =>
      NodeInfo.Date
    case (left: NodeInfo.Time.Kind, right: NodeInfo.Time.Kind) =>
      NodeInfo.Time
    case (left: NodeInfo.DateTime.Kind, right: NodeInfo.DateTime.Kind) =>
      NodeInfo.DateTime
    case (left: NodeInfo.Boolean.Kind, right: NodeInfo.Boolean.Kind) =>
      NodeInfo.Boolean
    case (left: NodeInfo.HexBinary.Kind, right: NodeInfo.HexBinary.Kind) =>
      NodeInfo.HexBinary
    case (l, r) =>
      SDE("Cannot compare %s with %s for operator '%s'", l, r, op)
  }

}

trait NumericExpression extends BinaryExpMixin {
  self: ExpressionLists =>

  lazy val numericOp: NumericOp = {
    import NodeInfo._
    (op, convergedArgType) match {
      case ("+", Decimal) => PlusDecimal
      case ("-", Decimal) => MinusDecimal
      case ("*", Decimal) => TimesDecimal
      case ("div", Decimal) => DivDecimal
      case ("idiv", Decimal) => IDivDecimal
      case ("mod", Decimal) => ModDecimal

      case ("+", Integer) => PlusInteger
      case ("-", Integer) => MinusInteger
      case ("*", Integer) => TimesInteger
      case ("div", Integer) => DivInteger
      case ("idiv", Integer) => IDivInteger
      case ("mod", Integer) => ModInteger

      case ("+", NonNegativeInteger) => PlusNonNegativeInteger
      case ("-", NonNegativeInteger) => MinusNonNegativeInteger
      case ("*", NonNegativeInteger) => TimesNonNegativeInteger
      case ("div", NonNegativeInteger) => DivNonNegativeInteger
      case ("idiv", NonNegativeInteger) => IDivNonNegativeInteger
      case ("mod", NonNegativeInteger) => ModNonNegativeInteger

      case ("+", UnsignedLong) => PlusUnsignedLong
      case ("-", UnsignedLong) => MinusUnsignedLong
      case ("*", UnsignedLong) => TimesUnsignedLong
      case ("div", UnsignedLong) => DivUnsignedLong
      case ("idiv", UnsignedLong) => IDivUnsignedLong
      case ("mod", UnsignedLong) => ModUnsignedLong

      case ("+", Long) => PlusLong
      case ("-", Long) => MinusLong
      case ("*", Long) => TimesLong
      case ("div", Long) => DivLong
      case ("idiv", Long) => IDivLong
      case ("mod", Long) => ModLong

      case ("+", UnsignedInt) => PlusUnsignedInt
      case ("-", UnsignedInt) => MinusUnsignedInt
      case ("*", UnsignedInt) => TimesUnsignedInt
      case ("div", UnsignedInt) => DivUnsignedInt
      case ("idiv", UnsignedInt) => IDivUnsignedInt
      case ("mod", UnsignedInt) => ModUnsignedInt

      case ("+", ArrayIndex) => PlusUnsignedInt
      case ("-", ArrayIndex) => MinusUnsignedInt
      case ("*", ArrayIndex) => TimesUnsignedInt
      case ("div", ArrayIndex) => DivUnsignedInt
      case ("idiv", ArrayIndex) => IDivUnsignedInt
      case ("mod", ArrayIndex) => ModUnsignedInt

      case ("+", Int) => PlusInt
      case ("-", Int) => MinusInt
      case ("*", Int) => TimesInt
      case ("div", Int) => DivInt
      case ("idiv", Int) => IDivInt
      case ("mod", Int) => ModInt

      case ("+", UnsignedShort) => PlusUnsignedShort
      case ("-", UnsignedShort) => MinusUnsignedShort
      case ("*", UnsignedShort) => TimesUnsignedShort
      case ("div", UnsignedShort) => DivUnsignedShort
      case ("idiv", UnsignedShort) => IDivUnsignedShort
      case ("mod", UnsignedShort) => ModUnsignedShort

      case ("+", Short) => PlusShort
      case ("-", Short) => MinusShort
      case ("*", Short) => TimesShort
      case ("div", Short) => DivShort
      case ("idiv", Short) => IDivShort
      case ("mod", Short) => ModShort

      case ("+", UnsignedByte) => PlusUnsignedByte
      case ("-", UnsignedByte) => MinusUnsignedByte
      case ("*", UnsignedByte) => TimesUnsignedByte
      case ("div", UnsignedByte) => DivUnsignedByte
      case ("idiv", UnsignedByte) => IDivUnsignedByte
      case ("mod", UnsignedByte) => ModUnsignedByte

      case ("+", Byte) => PlusByte
      case ("-", Byte) => MinusByte
      case ("*", Byte) => TimesByte
      case ("div", Byte) => DivByte
      case ("idiv", Byte) => IDivByte
      case ("mod", Byte) => ModByte

      case ("+", Float) => PlusFloat
      case ("-", Float) => MinusFloat
      case ("*", Float) => TimesFloat
      case ("div", Float) => DivFloat
      case ("idiv", Float) => IDivFloat
      case ("mod", Float) => ModFloat

      case ("+", Double) => PlusDouble
      case ("-", Double) => MinusDouble
      case ("*", Double) => TimesDouble
      case ("div", Double) => DivDouble
      case ("idiv", Double) => IDivDouble
      case ("mod", Double) => ModDouble
      case _ => subsetError("Unsupported operation '%s' on type %s.", op, convergedArgType)
    }
  }

  override lazy val compiledDPath = {
    val leftDPath = left.compiledDPath
    val rightDPath = right.compiledDPath
    val c = conversions
    new CompiledDPath(NumericOperator(numericOp, leftDPath, rightDPath) +: c)
  }

  /**
   * The DPath operator, such as, "+", or "idiv"
   */
  def op: String

  override def inherentType: NodeInfo.Kind = convergedResult

  override def targetTypeForSubexpression(child: Expression): NodeInfo.Kind = convergedArgType

  lazy val (convergedArgType, convergedResult) = (left.inherentType, right.inherentType) match {
    case (left: NodeInfo.Numeric.Kind, right: NodeInfo.Numeric.Kind) =>
      NodeInfoUtils.generalizeArgAndResultTypesForNumericOp(op, left, right)
    case (left: NodeInfo.Numeric.Kind, r) =>
      SDE("Right operand for operator '%s' must have numeric type. Type was: %s.", op, r)
    case (l, r: NodeInfo.Numeric.Kind) =>
      SDE("Left operand for operator '%s' must have numeric type. Type was: %s.", op, l)
    case (l, r) =>
      SDE("Operands for operator '%s' must have numeric type. Types were: %s and %s.", op, l, r)
  }

}

/**
 * A whole expression
 */
case class WholeExpression(
  nodeInfoKind: NodeInfo.Kind,
  ifor: Expression,
  nsBindingForPrefixResolution: NamespaceBinding,
  ci: DPathCompileInfo,
  host: BasicComponent)
  extends Expression {

  final override lazy val tunable: DaffodilTunables = host.tunable
  final override lazy val unqualifiedPathStepPolicy: UnqualifiedPathStepPolicy = host.unqualifiedPathStepPolicy

  def init(): Unit = {
    this.setOOLAGContext(host) // we are the root of expression, but we propagate diagnostics further.
    this.setContextsForChildren()
  }

  override lazy val namespaces = nsBindingForPrefixResolution

  override def text = ifor.text

  override lazy val targetType = nodeInfoKind

  override def targetTypeForSubexpression(subExpr: Expression): NodeInfo.Kind = {
    //
    // Note: the subExpr might not be exactly our ifor child expression
    // because if ifor was a function call, then the resulting function
    // object will have been created and given this as its parent pointer.
    //
    Assert.invariant(subExpr == ifor || ifor.isInstanceOf[FunctionCallExpression])

    // The result of this function will be used to coerce the result of the
    // expression to the target type. However, we do not always want to allow
    // type coercion even when it might be possible. For example, if the
    // targetType is xs:string and the expression is { 5 }, we do not want to
    // coerce that to the string "5", but instead want to throw an SDE
    // signifying that we expected a string but the expression result was a
    // numeric. But we sometimes do want coercion for usability purposes. For
    // example, if the result of an expression is a int but the type is long,
    // then we should still allow that coercion. Below we allow coercion
    // between decimal-like types where precision would not be lost, and all
    // integer-like types (which check for precision loss when evaluated).

    val allowCoercion = (inherentType, targetType) match {
      case (_, _) if inherentType == targetType => true
      case (_, _) if (inherentType.isSubtypeOf(NodeInfo.String) && targetType.isSubtypeOf(NodeInfo.String)) => true
      case (_, _) if (inherentType.isSubtypeOf(NodeInfo.Integer) && targetType.isSubtypeOf(NodeInfo.Integer)) => true
      case (_, NodeInfo.Float) if (inherentType.isSubtypeOf(NodeInfo.Integer)) => true
      case (_, NodeInfo.Double) if (inherentType.isSubtypeOf(NodeInfo.Integer)) => true
      case (_, NodeInfo.Decimal) if (inherentType.isSubtypeOf(NodeInfo.Integer)) => true
      case (NodeInfo.Float, NodeInfo.Double) => true
      case (NodeInfo.Float, NodeInfo.Decimal) => true
      case (NodeInfo.Double, NodeInfo.Decimal) => true
      case (NodeInfo.Nothing, _) => true
      case (_, NodeInfo.AnyType) => true
      case _ => false
    }

    if (!allowCoercion) {
      if (tunable.allowExpressionResultCoercion) {
        SDW(
          WarnID.DeprecatedExpressionResultCoercion,
          "In expression %s, result type (%s) should be manually cast to the expected type (%s) with the appropriate constructor." +
            "Performing deprecated automatic conversion.",
          wholeExpressionText,
          inherentType,
          targetType)
      } else {
        SDE(
          "In expression %s, result type (%s) must be manually cast to the expected type (%s) with the approrpriate constructor.",
          wholeExpressionText,
          inherentType,
          targetType)
      }
    }

    targetType
  }

  override lazy val inherentType = ifor.inherentType

  override lazy val compileInfo = ci

  override lazy val compiledDPath = ifor.compiledDPath

  override lazy val children = List(ifor)

}

case class IfExpression(ifthenelse: List[Expression])
  extends ExpressionLists(ifthenelse) {

  override lazy val compiledDPath = new CompiledDPath(IF(
    predicate.compiledDPath,
    thenPart.compiledDPath, elsePart.compiledDPath))

  override def text = "if (" + predicate.text + ") then " + thenPart.text + " else " + elsePart.text
  lazy val List(predicate, thenPart, elsePart) = ifthenelse
  val op = "if"

  override def targetTypeForSubexpression(subexp: Expression) = {
    Assert.invariant(children.contains(subexp))
    if (subexp == predicate)
      NodeInfo.Boolean
    else
      this.targetType
  }

  override lazy val inherentType = {
    (thenPart.inherentType, elsePart.inherentType) match {
      case (left: NodeInfo.Numeric.Kind, right: NodeInfo.Numeric.Kind) =>
        NodeInfoUtils.generalizeArgAndResultTypesForNumericOp(op, left, right)._2
      case (left, right) if left == right => left
      case (left, right) if right == NodeInfo.Nothing => left
      case (left, right) if left == NodeInfo.Nothing => right
      case (left, right) =>
        SDE("If-expression branches must have similar types, but were %s and %s ", left, right)
    }
  }
}

case class OrExpression(ands: List[Expression])
  extends ExpressionLists(ands) with BooleanExpression {
  val op = "or"
}
case class AndExpression(comps: List[Expression])
  extends ExpressionLists(comps) with BooleanExpression {
  val op = "and"
}

case class AdditiveExpression(op: String, mults: List[Expression])
  extends ExpressionLists(mults) with NumericExpression {

}

case class MultiplicativeExpression(op: String, unarys: List[Expression])
  extends ExpressionLists(unarys) with NumericExpression

case class UnaryExpression(op: String, exp: Expression)
  extends ExpressionLists(List(exp)) {

  override def text = "( " + op + " (" + exp.text + "))"

  override def inherentType: NodeInfo.Kind = exp.inherentType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind =
    if (op == "+") targetType
    else {
      exp.inherentType // negate the inherent type, then we'll convert to whatever the target wants.
    }

  override lazy val compiledDPath =
    if (op == "+") {
      // no conversions because we passed on target type to subexpression
      exp.compiledDPath
    } else {
      new CompiledDPath(NegateOp(exp.compiledDPath) +: conversions)
    }
}

abstract class PathExpression()
  extends Expression {
  def steps: Seq[StepExpression]

  override def text = steps.map { _.text }.mkString("/")

  override lazy val inherentType: NodeInfo.Kind = steps.last.inherentType

  override def targetTypeForSubexpression(subexp: Expression) = {
    parent.targetTypeForSubexpression(this)
  }

  /**
   * Path to a single array element without a [N] predicate qualifying it.
   *
   * That is, the kind of path expression used to access information
   * about an array such as its current count.
   */
  lazy val isPathToOneWholeArray: Boolean = {
    if (steps == Nil) false // root is never an array
    else steps.last.isArray &&
      !steps.last.pred.isDefined && // last cannot have a [N] pred
      steps.dropRight(1).forall {
        // for all the prior steps
        // if they mention an array element, there
        // must be a [N] predicate.
        step =>
          if (step.isInstanceOf[Up]) true
          else if (step.isArray) step.pred.isDefined
          else true
      }
  }
}

case class RootPathExpression(relPath: Option[RelativePathExpression])
  extends PathExpression() {

  override def text = "/" + super.text

  lazy val steps = relPath.map { _.steps }.getOrElse(Nil)

  override lazy val inherentType = {
    if (!(steps == Nil)) steps.last.inherentType
    else {
      rootElement.optPrimType match {
        case Some(pt) => pt
        case None => {
          // this is the more common case. Only in unit tests will the
          // root element be simple type.
          NodeInfo.Complex
        }
      }
    }
  }

  override def targetTypeForSubexpression(subexp: Expression) = {
    Assert.usage(relPath.isDefined)
    Assert.invariant(subexp == relPath.get)
    super.targetTypeForSubexpression(this)
  }

  override lazy val children = relPath.toSeq

  override lazy val compiledDPath = {
    val rel = relPath.map { rp => rp.compiledDPath.ops.toList }.getOrElse(Nil)
    val cdp = new CompiledDPath(ToRoot +: rel)
    cdp
  }
}

case class RelativePathExpression(steps1: List[StepExpression], isEvaluatedAbove: Boolean)
  extends PathExpression() {

  lazy val isAbsolutePath = {
    parent != null && parent.isInstanceOf[RootPathExpression]
  }

  /**
   * We must adjust the steps for the isEvaluatedAbove case.
   * That's when something like dfdl:occursCount is written as { ../c }.
   * In that case, it's written on an element decl as if it were accessing a
   * peer, but in fact the expression is evaluated before any instances of
   * the array are even allocated, so we must remove an ".." up move from
   * every relative path contained inside the expression, except when
   * part of an absolute path.
   */
  private lazy val adjustedSteps = {
    if (parent.isInstanceOf[RootPathExpression]) steps2
    else {
      // not an absolute path.
      if (isEvaluatedAbove) {
        // in this case, the relative path must begin with ".." to be
        // meaningful.
        Assert.invariant(steps2(0).isInstanceOf[Up])
        steps2.tail // trim off the UP move at the start.
      } else steps2
    }
  }

  override lazy val compiledDPath: CompiledDPath = {
    val cps = adjustedSteps.map {
      _.compiledDPath
    }
    val ops = cps.map {
      _.ops.toList
    }
    val res = new CompiledDPath(ops.flatten)
    res
  }

  override lazy val steps = {
    steps2
  }

  // remove any spurious "." in relative paths so "./.././.." becomes "../.."
  // corner case "././././." should behave like "."
  val steps2 = {
    val noSelfSteps = steps1.filter { case Self(None) => false; case _ => true }
    val res =
      if (noSelfSteps.length == 0) List(Self(None)) // we need one "."
      else noSelfSteps
    res
  }

  override lazy val children: List[Expression] = steps

}

sealed abstract class StepExpression(val step: String, val pred: Option[PredicateExpression])
  extends Expression {

  def relPathErr() = {
    val err = new RelativePathPastRootError(
      this.schemaFileLocation,
      "Relative path '%s' past root element.", this.wholeExpressionText)
    toss(err)
  }
  requiredEvaluationsAlways(priorStep)
  requiredEvaluationsAlways(compileInfo)
  requiredEvaluationsAlways(stepElements)

  def verifyQNames(cis: Seq[DPathElementCompileInfo]): Unit = {
    cis.foreach { ci =>
      if (!ci.namedQName.matches(stepQName))
        ci.noMatchError(stepQName)
    }
  }
  // Combination of lazy val and a protected def is an idiom
  // that enables a lazy calculation to call super.
  final lazy val stepElements = {
    val res = stepElementDefs
    res
  }

  protected def stepElementDefs: Seq[DPathElementCompileInfo]

  // Note: all instances are distinct regardless of contents.
  override def equals(x: Any) = x match {
    case ar: AnyRef => this _eq_ ar
    case _ => false
  }

  override def hashCode() = super.hashCode()

  final override def hasReferenceTo(elem: DPathElementCompileInfo): Boolean = {
    stepElements.contains(elem)
  }

  lazy val stepQName = {
    val e = QName.resolveStep(step, namespaces, tunable.unqualifiedPathStepPolicy)
    e match {
      case Failure(th) => SDE("Step %s prefix has no corresponding namespace.", step)
      case Success(v) => v
    }
  }

  override lazy val children = pred.toList

  lazy val priorStep: Option[StepExpression] = {
    val res =
      if (isFirstStep)
        None
      else {
        val pos = positionInStepsSequence - 1
        val step = relPathParent.steps(pos)
        Some(step)
      }
    res
  }

  lazy val relPathParent = {
    parentOpt match {
      case Some(rel: RelativePathExpression) => rel
      case Some(x) => Assert.invariantFailed("StepExpression must have RelativePathExpression parent.")
      case None => Assert.invariantFailed("StepExpression must have parent.")
    }
  }

  lazy val isAbsolutePath = relPathParent.isAbsolutePath

  lazy val positionInStepsSequence = {
    val steps = relPathParent.steps
    steps.indexOf(this)
  }

  lazy val isFirstStep = {
    val res = (positionInStepsSequence == 0)
    res
  }

  lazy val isLastStep = {
    val res = positionInStepsSequence == (relPathParent.steps.length - 1)
    res
  }

  final lazy val isArray: Boolean = {
    val (arrays, scalars) = stepElements.partition { _.isArray }
    (arrays.length, scalars.length) match {
      case (a, s) if (a > 0 && s > 0) =>
        arrays.head.SDE("Path step is ambiguous. It can be to an array or a non-array element.\n" +
          "One of the non-arrays is %s", scalars.head.schemaFileLocation.toString)
      case (a, s) if (a == 0) => false
      case _ => true
    }
  }

  /**
   * Used when there is no match as part of error diagnostic message.
   * It searches the local xml scope for a prefix binding for this
   * namespace. Returns Nil if none, a list of strings for all the available
   * prefixes for this namespace (because there can be more than one)
   */
  def suggestedPossiblePrefixes(ns: NS): Seq[String] = {
    val res = NS.allPrefixes(ns, compileInfo.namespaces)
    res
  }

  override lazy val targetType: NodeInfo.Kind = {
    if (isLastStep) parent.targetTypeForSubexpression(this)
    else NodeInfo.Complex
  }

  override def targetTypeForSubexpression(subexp: Expression) = {
    Assert.invariant(pred.isDefined)
    Assert.invariant(pred.get == subexp)
    NodeInfo.ArrayIndex
  }

}

sealed abstract class DownStepExpression(s: String, predArg: Option[PredicateExpression])
  extends StepExpression(s, predArg) {

  /**
   * Since an expression can be in a reused group, type, or element, the downward path
   * step may actually represent a step downward to distinct elements that happen to
   * just have the same name/namespace, but they could be different types.
   *
   * If that is the case, for the expression to be meaningful, the path step must be
   * ultimately result in an element of type suitable for the next operation that
   * occurs on the element.
   *
   * To insure we have referential transparency, that requires that each distinct type
   * result in its own compilation of the expression. In a complex expression involving
   * multiple such paths.... gets complicated.
   *
   * For now, we just insist all the types are equal, and issue a SDE if they are not.
   */
  override lazy val inherentType: NodeInfo.Kind = {
    val allTypes = stepElements.map { _.typeNode }.distinct
    Assert.invariant(allTypes.length > 0)
    if (allTypes.length > 1) {
      val (relevantExpr, details) = polymorphicExpressionErrorDetails(this)
      subsetError(
        "Expression %s has different types at different points of usage.%s",
        relevantExpr.text, details)
    } else {
      allTypes.head
    }
  }

  final def polymorphicExpressionErrorDetails(context: Expression): (Expression, String) = {
    val (relevantExpr: Expression, detailMsg: String) =
      context match {
        case stepE: StepExpression => {
          val typeNodeGroups = stepE.stepElements.groupBy { _.typeNode }
          Assert.invariant(typeNodeGroups.size > 0)
          val rpp = stepE.relPathParent
          val wholePath =
            if (rpp.isAbsolutePath)
              rpp.parent.asInstanceOf[RootPathExpression]
            else
              rpp
          if (typeNodeGroups.size > 1) {
            //
            // types are not consistent for all step elements
            //
            val detailStrings: Seq[String] = {
              typeNodeGroups.flatMap {
                case (tn, cis) =>
                  val tname = tn.globalQName.toQNameString
                  val perUsePointStrings = cis.map { ci =>
                    val sfl = ci.schemaFileLocation.locationDescription
                    val qn = ci.namedQName.toQNameString
                    val msg = "element %s in expression %s with %s type at %s".format(
                      qn, wholePath.wholeExpressionText, tname, sfl)
                    msg
                  }
                  perUsePointStrings
              }
            }.toSeq
            val detailPart: Seq[String] =
              if (detailStrings.length > 4)
                detailStrings.take(4) :+ "..."
              else
                detailStrings
            val detailMessage: String =
              "\nThe inconsistent type usages are listed here:\n%s".format(
                detailPart.mkString("\n"))
            (wholePath, detailMessage)
          } else {
            (wholePath, "")
          }
        }
        case _ => (context, "")
      }
    (relevantExpr, detailMsg)
  }
}


// TODO: Is ".[i]" ever a valid expression in DFDL?
// Perhaps. Doesn't work currently though. See DAFFODIL-2182

sealed abstract class SelfStepExpression(s: String, predArg: Option[PredicateExpression])
  extends DownStepExpression(s, predArg) {

  override lazy val compiledDPath = new CompiledDPath(SelfMove +: conversions)
  override def text = "."

  protected def stepElementDefs: Seq[DPathElementCompileInfo] = {
    if (this.isFirstStep) {
      val ecs = compileInfo.elementCompileInfos
      if (ecs.isEmpty) {
        Assert.invariantFailed("Self expression step '.' but there is no enclosing element")
      }
      ecs
    } else
      priorStep.get match {
        case priorUp: UpStepExpression => SDE("Path '../.' is not allowed.")
        case priorDown: DownStepExpression => priorDown.stepElements
        case x => Assert.invariantFailed("Not recognized: " + x)
      }
  }
}

case class Self(predArg: Option[PredicateExpression])
  extends SelfStepExpression(null, predArg)

/**
 * Different from Self in that it verifies the qName (s) is
 * the name of the context.
 */
case class Self2(s: String, predArg: Option[PredicateExpression])
  extends SelfStepExpression(s, predArg) {

  requiredEvaluationsAlways(stepQName)

  override def stepElementDefs: Seq[DPathElementCompileInfo] = {
    val cis = super.stepElementDefs
    verifyQNames(cis)
    cis
  }
}

sealed abstract class UpStepExpression(s: String, predArg: Option[PredicateExpression])
  extends StepExpression(s, predArg) {

  override def text = ".."

  final override lazy val compiledDPath = {
    val areAllArrays = isLastStep && stepElements.forall { _.isArray } && targetType == NodeInfo.Array
    if (areAllArrays) {
      new CompiledDPath(UpMoveArray)
    } else {
      new CompiledDPath(UpMove)
    }
  }

  protected def stepElementDefs: Seq[DPathElementCompileInfo] = {
    val res =
      if (isFirstStep) {
        Assert.invariant(!isAbsolutePath)
        //
        // This looks like 2 hops up, but it is really 1 hop up.
        // first position ourselves on nearest enclosing element, or self if
        // we are an element.
        // Then take the enclosing elements to get ".." upward move.
        //
        val p = compileInfo.elementCompileInfos.flatMap { _.enclosingElementCompileInfos }
        if (p.isEmpty) relPathErr()
        p
      } else {
        val ps = priorStep.get
        ps.stepElements.flatMap { _.enclosingElementCompileInfos }
      }
    res
  }

  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Complex
}

case class Up(predArg: Option[PredicateExpression])
  extends UpStepExpression(null, predArg)

/**
 * Different from Up in that it verifies the qName (s) is the
 * name of the parent node.
 */
case class Up2(s: String, predArg: Option[PredicateExpression])
  extends UpStepExpression(s, predArg) {

  requiredEvaluationsAlways(stepQName)

  override def text = ".."

  override protected def stepElementDefs: Seq[DPathElementCompileInfo] = {
    val cis = super.stepElementDefs
    verifyQNames(cis)
    cis
  }

}

case class NamedStep(s: String, predArg: Option[PredicateExpression])
  extends DownStepExpression(s, predArg) {

  requiredEvaluationsAlways(stepQName)

  override lazy val compiledDPath = {
    val d = downwardStep
    val conv = conversions
    val c = (if (isLastStep) conv else Nil)
    val res = new CompiledDPath(d +: c)
    res
  }

  lazy val dpathElementCompileInfos = stepElements

  lazy val downwardStep = {
    val nqn = dpathElementCompileInfos.head.namedQName
    if (isArray) {
      if (pred.isDefined) {
        Assert.invariant(pred.get.targetType == NodeInfo.ArrayIndex)
        val indexRecipe = pred.get.compiledDPath
        new DownArrayOccurrence(nqn, indexRecipe)
      } else if (targetType == NodeInfo.Exists) {
        new DownArrayExists(nqn)
      } else {
        schemaDefinitionUnless(targetType == NodeInfo.Array, "Query-style paths not supported. Must have '[...]' after array-element's name. Offending path step: '%s'.", step)
        new DownArray(nqn)
      }
    } else {
      //
      // Note: DFDL spec allows a[exp] if a is not an array, but it's a processing
      // error if exp doesn't evaluate to 1.
      // TODO: Implement this.
      if (pred.isDefined) subsetError("Indexing is only allowed on arrays. Offending path step: '%s%s'.", step, pred.get.text)
      // the downward element step must be the same for all the possible elements, so
      // we can pick the first one arbitrarily.
      new DownElement(nqn)
    }
  }

  override def text = step

  /*
   * The ERDs of the elements that correspond to this path step
   * (or SDE trying to find them.)
   *
   * There are multiple step elements because an upward relative path can be say
   * from an element inside a global group, upward to multiple group
   * references where the relative path then steps downward to
   * different elements having the same name.
   *
   * E.g., ../../b must type check for all possible b that this path
   * can refer to. If that path appears inside a global group definition,
   * then every place that group is used must have a corresponding b
   * child element that satisfies this path, and which type-checks.
   *
   * So long as they all type check, the we can generate the
   * downward step we need in a way that works universally for all
   * uses of the path.
   */
  override protected def stepElementDefs: Seq[DPathElementCompileInfo] = {
    val res: Seq[DPathElementCompileInfo] =
      if (isFirstStep) {
        if (isAbsolutePath) {
          // has to be the root element, but we have to make sure the name matches.
          val re = rootElement
          re.findRoot(stepQName, this)
          Seq(re)
        } else {
          // Since we're first we start from the element, or nearest enclosing
          // for this path expression.

          // That can already be multiple such, because this expression could be
          // say on the initiator property of an inner sequence inside a global sequence
          // group def. That global def could be used inside the complex type of
          // 3 different elements. So we would have 3 elementCompileInfos for this
          // step.
          //
          // Also since we're the first step, and this is a named step, this has
          // to be downward to a child. Assertion check here is just in case
          // somebody refactors this code elsewhere, as it is a very strong
          // assumption for this algorithm.
          Assert.invariant(this.isInstanceOf[NamedStep])
          //
          // All enclosing elements for this path step must be able to
          // find a named child that matches.
          //
          val nc = compileInfo.elementCompileInfos.map {
            _.findNamedChild(stepQName, this) // will SDE on not found.
          }
          nc
        }
      } else {
        // This is not first step so we are extension of prior step
        val ps = priorStep.get
        //
        // Now our current step is one that we can start
        // look for our downward named step.
        //
        // This downward step must make sense name and type-wise
        val possibles = ps.stepElements.flatMap { d =>
          val possibleChildren = d.elementChildrenCompileInfo
          val matches = d.findNamedChildren(stepQName, possibleChildren)
          if (matches.isEmpty)
            d.noMatchError(stepQName)
          if (matches.length > 1) {
            // expression is ambiguous. A path like ../foo, but for one of the possible ".." there's multiple foo.
            d.queryMatchWarning(stepQName, matches, this)
          }
          matches
        }
        if (possibles.isEmpty) relPathErr()
        possibles
      }
    res
  }
}

/**
 * The thing in square brackets is indexing in DFDL, but XPath
 * calls it a "predicate".
 */
case class PredicateExpression(ifOr: Expression)
  extends Expression {

  override def text = "[" + ifOr.text + "]"

  override lazy val inherentType = NodeInfo.ArrayIndex
  override def targetTypeForSubexpression(subexp: Expression) = {
    Assert.invariant(ifOr == subexp)
    NodeInfo.ArrayIndex
  }

  override lazy val compiledDPath = ifOr.compiledDPath

  override lazy val children: List[Expression] = List(ifOr)
}

abstract class PrimaryExpression(expressions: List[Expression])
  extends ExpressionLists(expressions) {
}

abstract class LiteralExpressionBase(value: Any)
  extends PrimaryExpression(Nil) {

  override def text = {
    value match {
      case s: String => s
      case _ => value.toString
    }
  }

  override def toString = text

  /**
   * Convert to regular types from the pessimistic BigInt
   * and BigDecimal that come in from the parser.
   */
  lazy val litValue: DataValuePrimitive = value match {
    case s: String => s
    case i: Int => i
    case i: JBigInt => {
      if (Numbers.isValidInt(i)) i.intValue()
      else if (Numbers.isValidLong(i)) i.longValue()
      else i
    }
    case bd: JBigDecimal => {
      // since we got a JBigDecimal, we know it at least wasn't parsed
      // into a JBigInteger, so it has a fraction component, even if it is ".0"
      // We want a double if it fits in one exactly. Otherwise keep a decimal.
      //
      // So consider 0.2. That's going to become a double float since it can be
      // converted to/from without loss of information.
      //
      // But 0.20000000000 is going to stay a BigDecimal.
      //
      if (Numbers.isDecimalDouble(bd)) bd.doubleValue()
      else bd
    }
    case f: Float => f.toDouble
    case d: Double => d
    case b: Boolean => b // there are no literal booleans, but fn:true() and fn:false() turns into one.
    case _ => Assert.invariantFailed("value not one of the expected types")
  }

  override lazy val compiledDPath: CompiledDPath = {
    litValue
    new CompiledDPath(Literal(litValue) +: conversions)
  }

  override lazy val inherentType = {
    litValue.getAnyRef match {
      case s: String => NodeInfo.String
      case i: JBigInt => NodeInfo.Integer
      case d: JBigDecimal => NodeInfo.Decimal
      case df: JDouble => NodeInfo.Double
      case l: JLong => NodeInfo.Long
      case i: JInt => NodeInfo.Int
      case b: JBoolean => NodeInfo.Boolean
      case _ => Assert.invariantFailed("value not one of the expected types " + litValue.getClass())
    }
  }

  override def targetTypeForSubexpression(subexp: Expression) =
    Assert.usageError("literal expressions have no subexpressions")
}

case class LiteralExpression(v: Any) extends LiteralExpressionBase(v)

/**
 * This is a literal as used to replace the fn:true() and fn:false() functions.
 *
 * It has to behave like a function call with respect to target type.
 */
case class LiteralBooleanExpression(v: Any) extends LiteralExpressionBase(v) {
  override lazy val targetType = {
    val res = parent.targetType
    res
  }
}

case class VariableRef(val qnameString: String)
  extends PrimaryExpression(Nil) {

  override lazy val compiledDPath = {
    val vrefOp = VRef(vrd, compileInfo)
    new CompiledDPath(vrefOp +: conversions)
  }
  override def text = "$" + qnameString

  lazy val theQName: GlobalQName = {
    val refQ = resolveRef(qnameString)
    val global = QName.createGlobal(refQ.local, refQ.namespace, namespaces)
    global
  }

  lazy val vrd = compileInfo.variableMap.getVariableRuntimeData(theQName).getOrElse(
    SDE("Undefined variable: %s", text))

  lazy val varType = {
    val vt = vrd.primType
    vt
  }

  override lazy val inherentType: NodeInfo.Kind = varType
  override def targetTypeForSubexpression(subexp: Expression) =
    Assert.usageError("variable reference expressions have no subexpressions")
}

/*
 * Functions and operators that exist in tests as of 2014-08-05
 *
 * fn:dateTime
 * xs:dateTime
 * xs:time
 * xs:date
 * xs:hexBinary
 */

case class FunctionCallExpression(functionQNameString: String, expressions: List[Expression])
  extends PrimaryExpression(expressions) {

  override def text = functionObject.text

  final override def leafContentLengthReferencedElements = functionObject.leafContentLengthReferencedElements
  final override def leafValueLengthReferencedElements = functionObject.leafValueLengthReferencedElements

  lazy val functionQName: RefQName = resolveRef(functionQNameString)

  override lazy val compiledDPath = functionObject.compiledDPath

  def inherentType: NodeInfo.Kind = functionObject.inherentType

  def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = {
    // This is called by the arguments to the function to get their target types.
    // However, this is not the right parent. They are arguments to the function object
    // once the FunctionCallExpression creates that function object, so
    // we delegate this back to the function object
    functionObject.targetTypeForSubexpression(childExpr)
  }

  lazy val functionObject: Expression = {
    val DFDL = XMLUtils.DFDL_NAMESPACE
    val DFDLX = XMLUtils.DFDLX_NAMESPACE
    val FUNC = XMLUtils.XPATH_FUNCTION_NAMESPACE
    val MATH = XMLUtils.XPATH_MATH_NAMESPACE
    val XSD = XMLUtils.XSD_NAMESPACE
    val DAF_NCSA = XMLUtils.EXT_NS_NCSA
    val DAF_APACHE = XMLUtils.EXT_NS_APACHE
    val funcObj = (functionQName, expressions) match {

      case (RefQName(_, "trace", DFDLX | DAF_NCSA | DAF_APACHE), args) => {
        if (functionQName.namespace != DFDLX) {
          SDW(
            WarnID.DeprecatedFunctionDAFError,
            "Expression %s is deprecated. Use dfdlx:trace instead",
            functionQNameString)
        }
        DFDLXTraceExpr(functionQNameString, functionQName, args)
      }

      case (RefQName(_, "error", DFDLX | DAF_NCSA | DAF_APACHE), args) => {
        SDW(
          WarnID.DeprecatedFunctionDAFError,
          "Expression %s is deprecated. Use fn:error instead",
          functionQNameString)
        DAFErrorExpr(functionQNameString, functionQName, args)
      }

      case (RefQName(_, "error", FUNC), args) => {
        FNErrorExpr(functionQNameString, functionQName, args)
      }

      case (RefQName(_, "not", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean, NodeInfo.AnyAtomic, FNNot(_, _))

      case (RefQName(_, "empty", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean, NodeInfo.Exists, FNEmpty(_, _))

      case (RefQName(_, "contains", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean,
          NodeInfo.String, NodeInfo.String, FNContains(_))

      case (RefQName(_, "starts-with", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean,
          NodeInfo.String, NodeInfo.String, FNStartsWith(_))

      case (RefQName(_, "ends-with", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean,
          NodeInfo.String, NodeInfo.String, FNEndsWith(_))

      case (RefQName(_, "local-name", FUNC), args) if args.length == 0 =>
        FNZeroArgExpr(functionQNameString, functionQName,
          NodeInfo.String, NodeInfo.Exists, FNLocalName0(_, _))

      case (RefQName(_, "local-name", FUNC), args) if args.length == 1 =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.Exists, FNLocalName1(_, _))

      case (RefQName(_, "namespace-uri", FUNC), args) if args.length == 0 =>
        FNZeroArgExpr(functionQNameString, functionQName,
          NodeInfo.String, NodeInfo.Exists, FNNamespaceUri0(_, _))

      case (RefQName(_, "namespace-uri", FUNC), args) if args.length == 1 =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.Exists, FNNamespaceUri1(_, _))

      case (RefQName(_, "string", XSD), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.AnyAtomic, XSString(_, _))

      case (RefQName(_, "dateTime", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.DateTime, NodeInfo.Date, NodeInfo.Time, FNDateTime(_))

      case (RefQName(_, "dateTime", XSD), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.DateTime, NodeInfo.AnyAtomic, XSDateTime(_, _))

      case (RefQName(_, "date", XSD), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Date, NodeInfo.AnyAtomic, XSDate(_, _))

      case (RefQName(_, "time", XSD), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Time, NodeInfo.AnyAtomic, XSTime(_, _))

      case (RefQName(_, "hexBinary", XSD), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.HexBinary, NodeInfo.AnyAtomic, XSHexBinary(_, _))

      case (RefQName(_, "hexBinary", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.HexBinary, NodeInfo.AnyAtomic, DFDLHexBinary(_, _))

      case (RefQName(_, "byte", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Byte, NodeInfo.AnyAtomic, DFDLByte(_, _))

      case (RefQName(_, "unsignedByte", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.UnsignedByte, NodeInfo.AnyAtomic, DFDLUnsignedByte(_, _))

      case (RefQName(_, "short", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Short, NodeInfo.AnyAtomic, DFDLShort(_, _))

      case (RefQName(_, "unsignedShort", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.UnsignedShort, NodeInfo.AnyAtomic, DFDLUnsignedShort(_, _))

      case (RefQName(_, "int", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Int, NodeInfo.AnyAtomic, DFDLInt(_, _))

      case (RefQName(_, "unsignedInt", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.UnsignedInt, NodeInfo.AnyAtomic, DFDLUnsignedInt(_, _))

      case (RefQName(_, "long", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Long, NodeInfo.AnyAtomic, DFDLLong(_, _))

      case (RefQName(_, "unsignedLong", DFDL), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.UnsignedLong, NodeInfo.AnyAtomic, DFDLUnsignedLong(_, _))

      case (RefQName(_, "nilled", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean, NodeInfo.AnyType, FNNilled(_, _))

      case (RefQName(_, "exists", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean, NodeInfo.Exists, FNExists(_, _))

      case (RefQName(_, "ceiling", FUNC), args) =>
        FNOneArgMathExpr(functionQNameString, functionQName, args,
          FNCeiling(_, _))

      case (RefQName(_, "floor", FUNC), args) =>
        FNOneArgMathExpr(functionQNameString, functionQName, args,
          FNFloor(_, _))

      case (RefQName(_, "round", FUNC), args) =>
        FNOneArgMathExpr(functionQNameString, functionQName, args,
          FNRound(_, _))

      case (RefQName(_, "abs", FUNC), args) =>
        FNOneArgMathExpr(functionQNameString, functionQName, args,
          FNAbs(_, _))

      case (RefQName(_, "concat", FUNC), args) =>
        FNArgListExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, FNConcat(_))

      // Note: there is no string-join function in DFDL
      // keep this as we may put this in as a daffodil extension
      //      case (RefQName(_, "string-join", FUNC), args) =>
      //        FNTwoArgsExpr(functionQNameString, functionQName, args,
      //          NodeInfo.Array, NodeInfo.String, NodeInfo.String, FNStringJoin(_))

      case (RefQName(_, "substring", FUNC), args) if args.length == 2 =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, NodeInfo.Double, FNSubstring2(_))
      case (RefQName(_, "substring", FUNC), args) if args.length == 3 =>
        FNThreeArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, NodeInfo.Double, NodeInfo.Double, FNSubstring3(_))

      case (RefQName(_, "substring-before", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, NodeInfo.String, FNSubstringBefore(_))

      case (RefQName(_, "substring-after", FUNC), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, NodeInfo.String, FNSubstringAfter(_))

      case (RefQName(_, "true", FUNC), Nil) => LiteralBooleanExpression(true)
      case (RefQName(_, "false", FUNC), Nil) => LiteralBooleanExpression(false)

      case (RefQName(_, "count", FUNC), args) =>
        FNCountExpr(functionQNameString, functionQName, args)

      case (RefQName(_, "exactly-one", FUNC), args) =>
        FNExactlyOneExpr(functionQNameString, functionQName, args)

      case (RefQName(_, "year-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.DateTime, FNYearFromDateTime(_, _))
      case (RefQName(_, "month-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.DateTime, FNMonthFromDateTime(_, _))
      case (RefQName(_, "day-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.DateTime, FNDayFromDateTime(_, _))
      case (RefQName(_, "hours-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.DateTime, FNHoursFromDateTime(_, _))
      case (RefQName(_, "minutes-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.DateTime, FNMinutesFromDateTime(_, _))
      case (RefQName(_, "seconds-from-dateTime", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Decimal, NodeInfo.DateTime, FNSecondsFromDateTime(_, _))
      case (RefQName(_, "year-from-date", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.Date, FNYearFromDate(_, _))
      case (RefQName(_, "month-from-date", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.Date, FNMonthFromDate(_, _))
      case (RefQName(_, "day-from-date", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.Date, FNDayFromDate(_, _))
      case (RefQName(_, "hours-from-time", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.Time, FNHoursFromTime(_, _))
      case (RefQName(_, "minutes-from-time", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Integer, NodeInfo.Time, FNMinutesFromTime(_, _))
      case (RefQName(_, "seconds-from-time", FUNC), args) => FNOneArgExpr(functionQNameString, functionQName, args, NodeInfo.Decimal, NodeInfo.Time, FNSecondsFromTime(_, _))

      case (RefQName(_, "occursIndex", DFDL), args) => {
        DFDLOccursIndexExpr(functionQNameString, functionQName, args)
      }
      case (RefQName(_, "checkConstraints", DFDL), args) => {
        DFDLCheckConstraintsExpr(functionQNameString, functionQName, args)
      }
      case (RefQName(_, "decodeDFDLEntities", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, DFDLDecodeDFDLEntities(_, _))
      }
      case (RefQName(_, "encodeDFDLEntities", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, DFDLEncodeDFDLEntities(_, _))
      }
      case (RefQName(_, "containsDFDLEntities", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.Boolean, NodeInfo.String, DFDLContainsDFDLEntities(_, _))
      }

      case (RefQName(_, "testBit", DFDL), args) => {
        DFDLTestBitExpr(functionQNameString, functionQName, args)
      }
      case (RefQName(_, "setBits", DFDL), args) => {
        DFDLSetBitsExpr(functionQNameString, functionQName, args)
      }

      //Begin DFDLX functions

      //Begin TypeValueCalc related functions
      case (RefQName(_, "inputTypeCalc", DFDLX), args) => {
        val typeCalc = lookupTypeCalculator(args(0), true, false)
        FNTwoArgsExprInferedArgType(functionQNameString, functionQName, args,
          typeCalc.dstType, NodeInfo.String, typeCalc.srcType, DFDLXInputTypeCalc(_, typeCalc))
      }

      case (RefQName(_, "outputTypeCalc", DFDLX), args) => {
        val typeCalc = lookupTypeCalculator(args(0), false, true)
        FNTwoArgsExprInferedArgType(functionQNameString, functionQName, args,
          typeCalc.srcType, NodeInfo.String, typeCalc.dstType, DFDLXOutputTypeCalc(_, typeCalc))
      }

      case (RefQName(_, "outputTypeCalcNextSibling", DFDLX), args) => {
        val erd = compileInfo.lexicalContextRuntimeData match {
          case erd: ElementRuntimeData => erd
          case _ => SDE("dfdlx:outputTypeCalcNextSibling can only be defined on an element")
        }
        val resolver = erd.partialNextElementResolver
        val followingERDs = resolver.currentPossibleNextElements.dropWhile { _ == erd }

        // It is required that the next sibling be a peer "adjacent or downward", but not up-and-out
        // from the location of the term where this resides.
        //
        //we keep the ERD to be able to produce better error messages
        val dstTypes: Seq[(NodeInfo.Kind, ElementRuntimeData)] = followingERDs.map(erd => {
          val strd = erd.optSimpleTypeRuntimeData match {
            case Some(x) => x
            case None => SDE("dfdlx:outputTypeCalcNextSibling: potential next sibling %s does not have a simple type def. This could be because it has a primitive type, or a complex type.", erd.namedQName)
          }
          val calc = strd.typeCalculator match {
            case Some(x) => x
            case None => SDE("dfdlx:outputTypeCalcNextSibling(): potential next sibling %s does not define a type calculator", erd.namedQName)
          }
          if (!calc.supportsUnparse) {
            SDE("dfdlx:outputTypeCalcNextSibling(): potential next sibling %s does not have an outputTypeCalc", erd.namedQName)
          }
          (calc.srcType, erd)
        })
        val dstType = dstTypes match {
          case Seq() => SDE("dfdlx:outputTypeCalcNextSibling() called where no suitable next sibling exists")
          case Seq(x) => x._1
          case x +: xs => {
            val (ans, headQName) = x
            /*
             * Verify that all next siblings have the same dstType
             * In theory, we could loosen the requirement to having the same primitive type,
             * but in the interest of being conservative, we will stick to the stricter interperatation for now
             */
            xs.map(other => {
              val (otherAns, otherQName) = other
              if (otherAns != ans) {
                SDE(
                  "dfdlx:outputTypeCalcNextSibling() requires that all the possible next siblings have the same " +
                    "repType. However, the potential next siblings %s and %s have repTypes %s and %s respectively",
                  headQName, otherQName, ans, otherAns)
              }
            })
            ans
          }
        }

        // We need to mark all possible following compileInfos as possibly used
        // in an expression since the DFDLXOutputTypeCalcNextSibling expression
        // might need them. We also need to mark this compileInfo as used in an
        // expression since the evaluate method of DFDLXTypeCalcNextSibilng
        // uses this element to find the next sibling
        val dpeci = erd.dpathCompileInfo.asInstanceOf[DPathElementCompileInfo]
        followingERDs.foreach { erd =>
          dpeci.indicateReferencedByExpression(Seq(erd.dpathElementCompileInfo))
        }
        dpeci.indicateReferencedByExpression(Seq(dpeci))

        FNZeroArgExpr(functionQNameString, functionQName,
          dstType, NodeInfo.AnyAtomic, DFDLXOutputTypeCalcNextSibling(_, _))
      }

      case (RefQName(_, "repTypeValue", DFDLX), args) => {
        val strd = compileInfo.lexicalContextRuntimeData match {
          case strd: SimpleTypeRuntimeData => strd
          case _ => {
            SDE("dfdlx:repTypeValue() can only be defined on a simple type")
          }
        }
        val repPrimType = strd.optRepPrimType match {
          case Some(x) => x
          case None => SDE("dfdlx:repTypeValue() used on type that does not have a repType.")
        }
        FNZeroArgExpr(functionQNameString, functionQName,
          repPrimType, NodeInfo.AnyAtomic, DFDLXRepTypeValue(_, _))
      }

      case (RefQName(_, "logicalTypeValue", DFDLX), args) => {
        val strd = compileInfo.lexicalContextRuntimeData match {
          case strd: SimpleTypeRuntimeData => strd
          case _ => {
            SDE("dfdlx:logicalTypeValue() can only be defined on a simple type")
          }
        }
        val logicalType = strd.primType
        FNZeroArgExpr(functionQNameString, functionQName,
          logicalType, NodeInfo.AnyAtomic, DFDLXLogicalTypeValue(_, _))
      }

      //End typeValueCalc related functions

      case (RefQName(_, "lookAhead", DFDLX), args) =>
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.NonNegativeInteger, NodeInfo.UnsignedInt, NodeInfo.UnsignedInt, DFDLXLookAhead(_))

      //End DFDLX functions

      case (RefQName(_, "round-half-to-even", FUNC), args) if args.length == 1 => {
        FNOneArgMathExpr(functionQNameString, functionQName, args,
          FNRoundHalfToEven1(_, _))
      }

      case (RefQName(_, "round-half-to-even", FUNC), args) => {
        FNTwoArgsMathExpr(functionQNameString, functionQName, args,
          NodeInfo.Integer, FNRoundHalfToEven2(_))
      }

      case (RefQName(_, "string-length", FUNC), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.ArrayIndex, NodeInfo.String, FNStringLength(_, _))
      }

      case (RefQName(_, "contentLength", DFDL), args) =>
        ContentLengthExpr(functionQNameString, functionQName, args,
          NodeInfo.Long, NodeInfo.Exists, NodeInfo.String, DFDLContentLength(_))
      // The above specifies Exists, because we want to know a node exists, but
      // it has to be a node, not a simple value. E.g., dfdl:contentLength("foobar", "bits") makes no sense.
      // The first argument has to be a path to a node, otherwise we don't have format properties and so
      // can't determine a content length.
      //
      // One might argue that dfdl:contentLength("foobar", "characters") is meaningful and should be 6.
      // But that's fairly pointless.

      case (RefQName(_, "valueLength", DFDL), args) => {
        ValueLengthExpr(functionQNameString, functionQName, args,
          NodeInfo.Long, NodeInfo.Exists, NodeInfo.String, DFDLValueLength(_))
      }

      case (RefQName(_, "lower-case", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, FNLowerCase(_, _))

      case (RefQName(_, "upper-case", FUNC), args) =>
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.String, FNUpperCase(_, _))

      case (RefQName(_, "timeZoneFromDateTime", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.DateTime, DFDLTimeZoneFromDFDLCalendar(_, _))
      }
      case (RefQName(_, "timeZoneFromDate", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.Date, DFDLTimeZoneFromDFDLCalendar(_, _))
      }
      case (RefQName(_, "timeZoneFromTime", DFDL), args) => {
        FNOneArgExpr(functionQNameString, functionQName, args,
          NodeInfo.String, NodeInfo.Time, DFDLTimeZoneFromDFDLCalendar(_, _))
      }
      case (RefQName(_, "pow", MATH), args) => {
        FNTwoArgsExpr(functionQNameString, functionQName, args,
          NodeInfo.Double, NodeInfo.Double, NodeInfo.Numeric, MATHPow(_))
      }

      // conversion functions
      case (RefQName(_, "integer", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Integer)

      case (RefQName(_, "decimal", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Decimal)

      case (RefQName(_, "boolean", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Boolean)

      case (RefQName(_, "float", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Float)

      case (RefQName(_, "double", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Double)

      case (RefQName(_, "long", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Long)

      case (RefQName(_, "unsignedLong", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.UnsignedLong)

      case (RefQName(_, "int", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Int)

      case (RefQName(_, "unsignedInt", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.UnsignedInt)

      case (RefQName(_, "short", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Short)

      case (RefQName(_, "unsignedShort", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.UnsignedShort)

      case (RefQName(_, "nonNegativeInteger", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.NonNegativeInteger)

      case (RefQName(_, "byte", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.Byte)

      case (RefQName(_, "unsignedByte", XSD), args) =>
        XSConverterExpr(functionQNameString, functionQName, args, NodeInfo.UnsignedByte)

      case (_: RefQName, args) => {
        val namespace = functionQName.namespace.toString()
        val fName = functionQName.local

        val udfCallingInfo = UserDefinedFunctionService.lookupUserDefinedFunctionCallingInfo(namespace, fName)

        if (udfCallingInfo.isEmpty) {
          SDE("Unsupported function: %s", functionQName)
        } else {
          val UserDefinedFunctionService.UserDefinedFunctionCallingInfo(udf, ei) = udfCallingInfo.get
          val UserDefinedFunctionService.EvaluateMethodInfo(evaluateMethod, evaluateParamTypes, evaluateReturnType) =
            ei

          UserDefinedFunctionCallExpr(functionQNameString, functionQName, args, evaluateParamTypes,
            evaluateReturnType, UserDefinedFunctionCall(_, _, udf, evaluateMethod))
        }
      }
    }
    funcObj.setOOLAGContext(this)
    funcObj
  }

  def lookupTypeCalculator(typeCalcExpr: Expression, requiresParse: Boolean, requiresUnparse: Boolean): TypeCalculator = {
    /*
     * Every function that currently uses this defines the type calculator
     * as their first arguement.
     * We still take the calculators name as an arguement to avoid relying on this
     * fact.
     */
    Assert.invariant(typeCalcExpr == expressions(0))
    /*
     * This lookup needs to occur before the main type-checker runs, because it is
     * used to determine the type of this expression.
     * As a result, we must do some type checking manually
     * Also, we insist that typeCalcExpr is a constant, which is not even
     * a concept that the type checker has
     */
    if (typeCalcExpr.inherentType != NodeInfo.String) {
      SDE("The type calculator name argument must be a constant string")
    }
    val qname = typeCalcExpr match {
      case typeCalcName: LiteralExpression => typeCalcName.v.asInstanceOf[String]
      case _ => SDE("The type calculator name argument must be a constant string")
    }
    val refType = QName.resolveRef(
      qname, compileInfo.namespaces,
      tunable.unqualifiedPathStepPolicy).get.toGlobalQName
    val typeCalculator = compileInfo.typeCalcMap.get(refType) match {
      case None => SDE("Simple type %s does not exist or does not have a repType", refType)
      case Some(x) => x
    }
    if (requiresParse && !typeCalculator.supportsParse) {
      SDE("The type calculator defined by %s does not define an inputValueCalc.", qname)
    }
    if (requiresUnparse && !typeCalculator.supportsUnparse) {
      SDE("The type calculator defined by %s does not define an outputValueCalc.", qname)
    }
    typeCalculator
  }

}

abstract class FunctionCallBase(
  functionQNameString: String,
  functionQName: RefQName,
  expressions: List[Expression]) extends ExpressionLists(expressions) {
  override def text = functionQNameString + "(" + expressions.map { _.text }.mkString(", ") + ")"

  override lazy val targetType = {
    val res = parent.targetType
    res
  }

  final def checkArgCount(n: Int): Unit = {
    if (expressions.length != n) argCountErr(n)
  }
  final def argCountErr(n: Int) = {
    SDE("The %s function requires %s argument(s).", functionQName.toPrettyString, n)
  }

  final def argCountTooFewErr(n: Int) = {
    //
    // Digression: below illustrates what is a hard problem in internationalization
    // of software, which is called pluralization.
    //
    // See that "(s)" fudge here where we mean - add plural 's' if the number is not one.
    // Well there's no one consistent way to do that.

    // Even consider zero. In English zero is plural. In French it is singular.

    // In many languages there are different endings for 0, x1, x2, x3, etc.
    // Much the way ordinal numbers work in English where we have 1st (st ending)
    // 2nd (nd ending for 2nd 22nd, 32nd, 572nd - but not 12 - special case 12th has th ending).
    // We seldom use ordinals in English, but in many languages CARDINAL numbers work
    // this way, and must match the quantity and there's 3 or 4 different endings
    // with exceptions for 11, 12, 13, etc.
    //
    SDE("The %s function requires at least %s argument(s).", functionQName.toPrettyString, n)
  }

  final def argCountTooManyErr(n: Int) = {
    SDE("The %s function requires no more than %s argument(s).", functionQName.toPrettyString, n)
  }

}

/**
 * Tells the sub-expression that we want an array out of it.
 */
abstract class FunctionCallArrayBase(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  def funcName: String

  lazy val arrPath = args(0) match {
    case pe: PathExpression => pe
    case _ => subsetError("The %s function must contain a path.", funcName)
  }

  override lazy val isTypeCorrect = {
    checkArgCount(1)
    arrPath.isPathToOneWholeArray
  }

  override def targetTypeForSubexpression(subExp: Expression): NodeInfo.Kind = NodeInfo.Array

}
/**
 * Tells the sub-expression that we want an array out of it.
 */
case class FNCountExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallArrayBase(nameAsParsed, fnQName, args) {

  val funcName: String = "fn:count"

  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Long

  override lazy val compiledDPath = {
    checkArgCount(1)
    val arg0Recipe = args(0).compiledDPath
    val arg0Type = args(0).inherentType
    val c = conversions
    val res = new CompiledDPath(FNCount(arg0Recipe, arg0Type) +: c)
    res
  }
}

case class FNExactlyOneExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallArrayBase(nameAsParsed, fnQName, args) {

  val funcName: String = "fn:exactly-one"

  override lazy val inherentType: NodeInfo.Kind = NodeInfo.AnyType

  override lazy val compiledDPath = {
    checkArgCount(1)
    subsetError("fn:exactly-one is not supported.")
  }
}

case class FNRoundHalfToEvenExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  val (valueExpr, precisionExpr) = args match {
    case List(ve) => (ve, LiteralExpression(0))
    case List(ve, pe) => (ve, pe)
    case _ => SDE(
      "The %n function requires 1 or 2 arguments. But %s were found.",
      fnQName.toPrettyString, args.length)
  }

  override lazy val children = List(valueExpr, precisionExpr)

  override lazy val inherentType = valueExpr.inherentType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = {
    if (childExpr == valueExpr) valueExpr.inherentType
    else if (childExpr == precisionExpr) NodeInfo.Int
    else Assert.invariantFailed("subexpression isn't one of the expected.")
  }

  override lazy val compiledDPath =
    new CompiledDPath(
      FNRoundHalfToEven(
        valueExpr.compiledDPath,
        precisionExpr.compiledDPath) +: conversions)
}

/**
 * Preserves the inherent type of the argument to the function as the type of
 * the result, that is if the argument type is a numeric type, and if
 * the argument type is a subtype thereof.
 */
case class FNOneArgMathExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], constructor: (CompiledDPath, NodeInfo.Kind) => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = {
    schemaDefinitionUnless(
      argInherentType.isSubtypeOf(NodeInfo.Numeric),
      "Argument must be of numeric type but was %s.", argInherentType)
    argInherentType
  }

  lazy val argInherentType = {
    schemaDefinitionUnless(args.length == 1, "Function %s takes 1 argument.",
      fnQName.toPrettyString)
    args(0).inherentType
  }

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = inherentType

  override lazy val compiledDPath = {
    val arg0Recipe = args(0).compiledDPath
    val arg0Type = args(0).inherentType
    val c = conversions
    val res = new CompiledDPath(constructor(arg0Recipe, arg0Type) +: c)
    res
  }
}

/**
 * Preserves the inherent type of the argument to the function as the type of
 * the result, that is if the argument type is a numeric type, and if the
 * argument type is a subtype thereof. The inherent type comes from the first
 * argument
 */
case class FNTwoArgsMathExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], arg2Type: NodeInfo.Kind, constructor: List[CompiledDPath] => RecipeOp)
  extends FNTwoArgsExprBase(nameAsParsed, fnQName, args, NodeInfo.Numeric, NodeInfo.Numeric, arg2Type, constructor) {

  override lazy val inherentType = {
    schemaDefinitionUnless(
      argInherentType.isSubtypeOf(NodeInfo.Numeric),
      "First argument must be of numeric type but was %s.", argInherentType)
    argInherentType
  }

  lazy val argInherentType = {
    schemaDefinitionUnless(args.length == 2, "Function %s takes 2 arguments.",
      fnQName.toPrettyString)
    args(0).inherentType
  }

  override def targetTypeForSubexpression(subexp: Expression): NodeInfo.Kind = {
    if (subexp == arg1)
      inherentType
    else if (subexp == arg2)
      arg2Type
    else
      Assert.invariantFailed("subexpression %s is not an argument.".format(subexp))
  }
}

case class FNZeroArgExpr(nameAsParsed: String, fnQName: RefQName,
  resultType: NodeInfo.Kind, argType: NodeInfo.Kind, constructor: (CompiledDPath, NodeInfo.Kind) => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, Nil) {

  override lazy val inherentType = resultType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = argType

  override lazy val compiledDPath = {
    checkArgCount(0)
    val c = conversions
    val res = new CompiledDPath(constructor(null, null) +: c)
    res
  }
}

case class FNOneArgExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, argType: NodeInfo.Kind, constructor: (CompiledDPath, NodeInfo.Kind) => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = argType

  override lazy val compiledDPath = {
    checkArgCount(1)
    val arg0Recipe = args(0).compiledDPath
    val arg0Type = args(0).inherentType
    val c = conversions
    val res = new CompiledDPath(constructor(arg0Recipe, arg0Type) +: c)
    res
  }
}

case class FNOneArgExprConversionDisallowed(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, argType: NodeInfo.Kind, constructor: (CompiledDPath, NodeInfo.Kind) => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = argType

  override lazy val conversions = Nil

  override lazy val compiledDPath = {
    checkArgCount(1)
    val arg0Recipe = args(0).compiledDPath
    val arg0Type = args(0).inherentType
    val c = Nil //conversions
    val res = new CompiledDPath(constructor(arg0Recipe, arg0Type) +: c)
    res
  }
}

abstract class FNTwoArgsExprBase(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  lazy val List(arg1, arg2) = { checkArgCount(2); args }

  override def targetTypeForSubexpression(subexp: Expression): NodeInfo.Kind = {
    if (subexp == arg1)
      arg1Type
    else if (subexp == arg2)
      arg2Type
    else
      Assert.invariantFailed("subexpression %s is not an argument.".format(subexp))
  }

  override lazy val compiledDPath = {
    val arg1Recipe = arg1.compiledDPath
    val arg2Recipe = arg2.compiledDPath
    val recipe = constructor(List(arg1Recipe, arg2Recipe))
    val res = new CompiledDPath(recipe +: conversions)
    res
  }
}

case class FNTwoArgsExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends FNTwoArgsExprBase(nameAsParsed, fnQName, args, resultType, arg1Type, arg2Type, constructor)

/*
 * Used when the underlying constructor does not inherantly know its arguement types will be.
 * Note that we do not actually "infer" the types here, as we can get them from the args parameter
 * as easily as the caller, and the caller might be aware of more constraints than we are.
 * (Eg. in the DFDLXTypeInputCalc functions, only the second parameter is infered, the first
 * must be a String).
 *
 * Note that, for arguements that should be inferred, the caller should use arg.inherentType,
 * not arg.targetType, as the latter will look to this class to make its determination.
 */
case class FNTwoArgsExprInferedArgType(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[(CompiledDPath, NodeInfo.Kind)] => RecipeOp)
  extends {
    private val constructor_ : List[CompiledDPath] => RecipeOp = (subExprs: List[CompiledDPath]) => {
      Assert.invariant(subExprs.length == args.length)
      val types = args.map(_.targetType)
      val typedSubExprs = subExprs.zip(types)
      constructor(typedSubExprs)
    }
  } with FNTwoArgsExprBase(nameAsParsed, fnQName, args, resultType, arg1Type, arg2Type, constructor_)

sealed abstract class LengthExprBase(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends FNTwoArgsExprBase(nameAsParsed, fnQName, args, resultType, arg1Type, arg2Type, constructor) {

  protected final def leafReferencedElements = {
    val arg = args(0).asInstanceOf[PathExpression]
    val steps = arg.steps
    val lst = steps.last
    val res = lst.stepElements.toSet
    res
  }

}

case class ContentLengthExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends LengthExprBase(nameAsParsed, fnQName, args, resultType, arg1Type, arg2Type, constructor) {

  override lazy val leafContentLengthReferencedElements = leafReferencedElements
}

case class ValueLengthExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends LengthExprBase(nameAsParsed, fnQName, args, resultType, arg1Type, arg2Type, constructor) {

  override lazy val leafValueLengthReferencedElements = leafReferencedElements
}

case class FNThreeArgsExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind,
  arg1Type: NodeInfo.Kind, arg2Type: NodeInfo.Kind, arg3Type: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  lazy val List(arg1, arg2, arg3) = { checkArgCount(3); args }

  override def targetTypeForSubexpression(subexp: Expression): NodeInfo.Kind = {
    if (subexp == arg1) arg1Type
    else if (subexp == arg2) arg2Type
    else {
      Assert.invariant(subexp == arg3)
      arg3Type
    }
  }

  override lazy val compiledDPath = {
    val arg1Path = arg1.compiledDPath
    val arg2Path = arg2.compiledDPath
    val arg3Path = arg3.compiledDPath
    val c = conversions
    val res = new CompiledDPath(constructor(
      List(arg1Path, arg2Path, arg3Path)) +: c)
    res
  }
}

case class XSConverterExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression],
  resultType: NodeInfo.Kind)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  /*
   * By using the result type as the target for the expression, conversions will
   * do the work of converting into that type. We don't need to put down any
   * additional recipe operator to convert anything.
   */
  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = resultType // NodeInfo.AnyType

  // TODO: this should work... why do we need to call an additional converter. The
  // args(0).compiledDPath should already have taken into account converting into
  // their target types which are the same as this conversion's output result type.

  override lazy val compiledDPath = {
    checkArgCount(1)
    val arg0Recipe = args(0).compiledDPath
    val c = conversions
    val res = new CompiledDPath(arg0Recipe.ops.toList ++ c)
    res
  }
}

case class FNArgListExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression], resultType: NodeInfo.Kind, argType: NodeInfo.Kind,
  constructor: List[CompiledDPath] => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = argType

  override lazy val compiledDPath = {
    if (args.length < 1) argCountTooFewErr(1)
    new CompiledDPath(constructor(args.map { _.compiledDPath }) +: conversions)
  }
}

case class FNErrorExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Nothing

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = NodeInfo.String

  override lazy val compiledDPath = {
    if (args.length > 3) argCountTooManyErr(3)
    Assert.invariant(conversions == Nil)
    new CompiledDPath(FNError(args.map { _.compiledDPath }))
  }
}

case class DFDLOccursIndexExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {
  // Note that even though there are no arguments, this cannot be a case object
  // since it gets wired into expressions, and so the parent pointer must
  // be set. So it's not a singleton.

  override lazy val children = Nil
  override def text = "dfdl:occursIndex"

  override lazy val compiledDPath = {
    schemaDefinitionUnless(args.length == 0, "Function %s takes no arguments.", fnQName.toPrettyString)
    new CompiledDPath(DFDLOccursIndex +: conversions)
  }

  override lazy val inherentType = NodeInfo.ArrayIndex
  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind =
    Assert.usageError("No subexpressions")
}

case class DFDLTestBitExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression]) extends FunctionCallBase(nameAsParsed, fnQName, args) {

  lazy val List(data, bitPos) = { checkArgCount(2); args }

  override lazy val inherentType = NodeInfo.Boolean

  override def targetTypeForSubexpression(subexpr: Expression): NodeInfo.Kind = {
    subexpr match {
      case `data` => NodeInfo.UnsignedByte
      case `bitPos` => NodeInfo.UnsignedByte
      case _ => Assert.invariantFailed("wasn't one of the subexpressions.")
    }
  }

  override lazy val compiledDPath =
    new CompiledDPath(DFDLTestBit(data.compiledDPath, bitPos.compiledDPath) +: conversions)

}

case class DFDLSetBitsExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression]) extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = NodeInfo.UnsignedByte
  override def targetTypeForSubexpression(subexpr: Expression): NodeInfo.Kind = NodeInfo.UnsignedByte

  override lazy val compiledDPath = {
    checkArgCount(8)
    new CompiledDPath(DFDLSetBits(args.map { _.compiledDPath }) +: conversions)
  }
}

case class DFDLCheckConstraintsExpr(nameAsParsed: String, fnQName: RefQName,
  args: List[Expression]) extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val children = args

  override lazy val compiledDPath = {
    checkArgCount(1)
    val argDPath = args(0).compiledDPath
    val c = conversions
    val res = new CompiledDPath(DFDLCheckConstraints(argDPath) +: c)
    res
  }
  override def targetTypeForSubexpression(subexpr: Expression): NodeInfo.Kind = NodeInfo.AnyAtomic

  override lazy val inherentType = NodeInfo.Boolean

}

/**
 * Really this just delegates anything bottom-up to the contained expression
 * and anything top-down to the parent
 */
case class ParenthesizedExpression(expression: Expression)
  extends PrimaryExpression(List(expression)) {

  override def inherentType: NodeInfo.Kind = expression.inherentType
  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = targetType
  override def text: String = "( " + expression.text + " )"
  override lazy val compiledDPath = expression.compiledDPath // no conversions because we passed on targetType to subexp
}

case class DFDLXTraceExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  requiredEvaluationsAlways(realArg)
  requiredEvaluationsAlways(msgText)

  lazy val (realArg, msgText) = args match {
    case List(arg0, LiteralExpression(txt: String)) => (arg0, txt)
    case List(arg0, other) =>
      SDE(
        "The second argument to %n must be a string literal, but was %s.",
        nameAsParsed, other.text)
    case _ => {
      argCountErr(2)
    }
  }

  override lazy val inherentType: NodeInfo.Kind = realArg.inherentType
  override def targetTypeForSubexpression(subExp: Expression): NodeInfo.Kind = targetType

  override lazy val compiledDPath = {
    new CompiledDPath(DFDLXTrace(realArg.compiledDPath, msgText) +: conversions)
  }
}

case class DAFErrorExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Nothing
  override def targetTypeForSubexpression(subExp: Expression): NodeInfo.Kind =
    Assert.invariantFailed("no subexpressions")

  override lazy val compiledDPath = {
    checkArgCount(0)
    Assert.invariant(conversions == Nil)
    new CompiledDPath(DAFError)
  }
}

case class UserDefinedFunctionCallExpr(
  nameAsParsed: String,
  fnQName: RefQName,
  args: List[Expression],
  argTypes: List[NodeInfo.Kind],
  resultType: NodeInfo.Kind,
  constructor: (String, List[CompiledDPath]) => RecipeOp)
  extends FunctionCallBase(nameAsParsed, fnQName, args) {

  override lazy val inherentType = resultType

  lazy val argToArgType = {
    /*
     * Note that this checkArgCount is necessary as zip will mask any length
     * inconsistencies. Putting the check in $compiledDPath ought to, but doesn't
     * result in an SDE being thrown.
     */
    checkArgCount(argTypes.length)
    (args zip argTypes).toMap
  }

  override def targetTypeForSubexpression(childExpr: Expression): NodeInfo.Kind = {
    argToArgType.get(childExpr) match {
      case Some(tt) => tt
      case None => Assert.invariantFailed("subexpression isn't of the expected type.")
    }
  }

  override lazy val compiledDPath = {
    checkArgCount(argTypes.length)
    val recipes = args.map { _.compiledDPath }
    val res = new CompiledDPath(constructor(nameAsParsed, recipes) +: conversions)
    res
  }
}
