/*
 * 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.grammar.primitives

import java.util.regex.PatternSyntaxException
import org.apache.daffodil.dsom.ElementBase
import org.apache.daffodil.grammar.Gram
import org.apache.daffodil.grammar.Terminal
import org.apache.daffodil.processors.parsers._
import org.apache.daffodil.processors.unparsers._
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.processors.unparsers.SpecifiedLengthExplicitImplicitUnparser
import org.apache.daffodil.processors.parsers.SpecifiedLengthExplicitParser
import org.apache.daffodil.processors.parsers.SpecifiedLengthImplicitParser
import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.processors.parsers.Parser

abstract class SpecifiedLengthCombinatorBase(val e: ElementBase, eGramArg: => Gram)
  extends Terminal(e, true) {

  lazy val eGram = eGramArg // once only

  final protected lazy val eParser: Parser = {
    val p = eGram.parser
    p
  }

  lazy val eUnparser = {
    val u = eGram.unparser
    u
  }

  def kind: String

  def toBriefXML(depthLimit: Int = -1): String = {
    if (depthLimit == 0) "..." else
      "<SpecifiedLengthCombinator_" + kind + ">" +
        eParser.toBriefXML(depthLimit - 1) +
        "</SpecifiedLengthCombinator_" + kind + ">"
  }

}

class SpecifiedLengthPattern(e: ElementBase, eGram: => Gram)
  extends SpecifiedLengthCombinatorBase(e, eGram) {

  val kind = "Pattern"

  lazy val pattern =
    try {
      e.lengthPattern.r.pattern // imagine a really big expensive pattern to compile.
    } catch {
      case x: PatternSyntaxException =>
        e.SDE(x)
    }

  if (!e.encodingInfo.isScannable && !(e.isSimpleType && e.primType == PrimType.HexBinary)) {
    // lengthKind="pattern" requires scanability. However, xs:hexBinary types
    // are not scannable, but we do allow pattern lengths if the encoding is
    // ISO-8859-1. The ISO-8859-1 check is done elsewhere, so to allow pattern
    // lengths, either this must be scannable or the type must be xs:hexBinary.
    // Anything else is an error.
    e.SDE("Element %s does not meet the requirements of Pattern-Based lengths and Scanability.\nThe element and its children must be representation='text' and share the same encoding.", e.diagnosticDebugName)
  }

  override lazy val parser: Parser = new SpecifiedLengthPatternParser(
    eParser,
    e.elementRuntimeData,
    pattern)

  // When unparsing, the pattern is not used to calculate a length, so just
  // skip that parser and go straight to unparsing the string in the eUnparser
  override lazy val unparser: Unparser = {
    pattern
    // force pattern just so we detect regex syntax errors even though
    // we don't use the pattern when unparsing.
    eUnparser
  }
}

trait SpecifiedLengthExplicitImplicitUnparserMixin {

  def e: ElementBase
  def eUnparser: Unparser

  lazy val unparser: Unparser = {
    val u = eUnparser
    if (u.isEmpty) u
    else
      new SpecifiedLengthExplicitImplicitUnparser(
        u,
        e.elementRuntimeData,
        e.unparseTargetLengthInBitsEv,
        e.maybeUnparseTargetLengthInCharactersEv)
  }
}

class SpecifiedLengthExplicit(e: ElementBase, eGram: => Gram, bitsMultiplier: Int)
  extends SpecifiedLengthCombinatorBase(e, eGram)
  with SpecifiedLengthExplicitImplicitUnparserMixin {

  Assert.usage(bitsMultiplier > 0)

  lazy val kind = "Explicit_" + e.lengthUnits.toString

  lazy val parser: Parser = {
    if (eParser.isEmpty) eParser
    else new SpecifiedLengthExplicitParser(
      eParser,
      e.elementRuntimeData,
      e.lengthEv,
      bitsMultiplier)
  }

}

class SpecifiedLengthImplicit(e: ElementBase, eGram: => Gram, nBits: Long)
  extends SpecifiedLengthCombinatorBase(e, eGram)
  with SpecifiedLengthExplicitImplicitUnparserMixin {

  lazy val kind = "Implicit_" + e.lengthUnits.toString

  lazy val toBits = 1

  lazy val parser: Parser = new SpecifiedLengthImplicitParser(
    eParser,
    e.elementRuntimeData,
    nBits)

}

class SpecifiedLengthPrefixed(e: ElementBase, eGram: => Gram, bitsMultiplier: Int)
  extends SpecifiedLengthCombinatorBase(e, eGram) {

  Assert.usage(bitsMultiplier > 0)

  lazy val kind = "Prefixed_" + e.lengthUnits.toString

  lazy val parser: Parser = new SpecifiedLengthPrefixedParser(
    eParser,
    e.elementRuntimeData,
    e.prefixedLengthBody.parser,
    e.prefixedLengthElementDecl.elementRuntimeData,
    e.lengthUnits,
    e.prefixedLengthAdjustmentInUnits)

  lazy val unparser: Unparser = new SpecifiedLengthPrefixedUnparser(
    eUnparser,
    e.elementRuntimeData,
    e.prefixedLengthBody.unparser,
    e.prefixedLengthElementDecl.elementRuntimeData,
    e.lengthUnits,
    e.prefixedLengthAdjustmentInUnits)
}

class SpecifiedLengthExplicitCharacters(e: ElementBase, eGram: => Gram)
  extends SpecifiedLengthCombinatorBase(e, eGram)
  with SpecifiedLengthExplicitImplicitUnparserMixin {

  val kind = "ExplicitCharacters"

  lazy val parser: Parser = new SpecifiedLengthExplicitCharactersParser(
    eParser,
    e.elementRuntimeData,
    e.lengthEv)
}

class SpecifiedLengthImplicitCharacters(e: ElementBase, eGram: => Gram, nChars: Long)
  extends SpecifiedLengthCombinatorBase(e, eGram)
  with SpecifiedLengthExplicitImplicitUnparserMixin {

  val kind = "ImplicitCharacters"

  lazy val parser: Parser = new SpecifiedLengthImplicitCharactersParser(
    eParser,
    e.elementRuntimeData,
    nChars)
}

class SpecifiedLengthPrefixedCharacters(e: ElementBase, eGram: => Gram)
  extends SpecifiedLengthCombinatorBase(e, eGram) {

  val kind = "ExplicitCharacters"

  lazy val parser: Parser = new SpecifiedLengthPrefixedCharactersParser(
    eParser,
    e.elementRuntimeData,
    e.prefixedLengthBody.parser,
    e.prefixedLengthElementDecl.elementRuntimeData,
    e.prefixedLengthAdjustmentInUnits)

  lazy val unparser: Unparser = Assert.nyi("unparsing with dfdl:lengthKind=\"prefixed\" and dfdl:lengthUnits=\"characters\"")
}
