/*
 * 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.processors.parsers

import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.schema.annotation.props.EmptyElementParsePolicy
import org.apache.daffodil.processors.ElementRuntimeData
import org.apache.daffodil.processors.ModelGroupRuntimeData

/**
 * Define the kinds of behaviors for terms in a sequence.
 *
 * Positional and Non-Positional are concepts that apply only to the children
 *  of separated sequences. They are not characteristics of the entire sequence,
 *  but of each child term of the sequence.
 *
 *  Positional Behavior - means that separators are required in order to determine
 *  which child of the sequence group is being parsed/unparsed.
 *
 *  Positional-Trailing Behavior - Like positional, but with special treatment for
 *  things at the end of a sequence, where extra separators for absentRep occurrences
 *  may be tolerated when parsing, but are not created when unparsing. A strict behavior
 *  applies only for parsing. In this case, there can be extra separators for absentRep,
 *  but only if they are subsequently followed by a non-absentRep (meaning nilRep, emptyRep
 *  that is distinct from absentRep, or normalRep). For Positional-Trailing behavior to
 *  apply, the sequence child must be potentially trailing.
 *
 *  Non-Positional Behavior - means that separators are not required to determine which
 *  child of the sequence group is being parsed/unparsed. The data must make this
 *  ambiguous with initiators, or with simply the accepted syntax of the data type.
 *
 *  A sequence child which is an array element with dfdl:occursCountKind fixed or expression
 *  this has positional behavior regardless of the separatorSuppressionPolicy of the sequence.
 *  For the occurrences of that element, the behavior is as if the sequence had
 *  separatorSuppressionPolicy 'never'.
 *
 *  A sequence child which is an array element with dfdl:occursCountKind parsed has
 *  non-positional behavior regardless of the separatorSuppressionPolicy of the sequence.
 *  For the occurrences of that element the behavior is as if the sequence had
 *  separatorSuppressionPolicy 'anyEmpty'
 *
 *  A sequence child which is an array element with dfdl:occursCountKind implicit has
 *  positional, positional-trailing, or non-positional behavior based on several other factors.
 */
sealed abstract class SeparatedSequenceChildBehavior
object SeparatedSequenceChildBehavior {
  type Type = SeparatedSequenceChildBehavior
  sealed abstract class PositionalLike extends Type
  sealed abstract class PositionalTrailing extends PositionalLike
  case object Positional extends PositionalLike
  case object PositionalTrailingLax extends PositionalTrailing
  case object PositionalTrailingStrict extends PositionalTrailing
  case object NonPositional extends Type
}

trait SeparatedSequenceChildParseResultHelper
  extends SequenceChildParseResultHelper {
  import SeparatedSequenceChildBehavior._

  def separatedSequenceChildBehavior: SeparatedSequenceChildBehavior
  protected final def sscb = separatedSequenceChildBehavior

  /**
   * Used for postfix separator case.
   *
   * Always false for model groups
   */
  def isSimpleDelimited: Boolean

  def computeFailedSeparatorParseAttemptStatus(
    parser: SequenceChildParser,
    prevBitPosBeforeChild: Long,
    pstate: PState,
    isZL: Boolean,
    requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {
    ParseAttemptStatus.MissingSeparator
  }

  /**
   * Define this as final here so we aren't creating proliferation of
   * traits/classes just for this one little issue.
   */
  final override def finalChecks(parser: SequenceChildParser, pstate: PState, resultOfTry: ParseAttemptStatus,
    priorResultOfTry: ParseAttemptStatus): Unit = {

    if ((sscb eq PositionalTrailingStrict)) {
      val resultToTest = priorResultOfTry
      resultToTest match {
        case ParseAttemptStatus.AbsentRep |
          ParseAttemptStatus.EmptyRep =>
          parser.PE(pstate, "Empty trailing optional elements are not allowed when dfdl:separatorSuppressionPolicy='trailingEmptyStrict'")
        case _ => // ok
      }
    }
  }
}

trait ScalarElementSeparatedSequenceChildParseResultHelper
  extends SeparatedSequenceChildParseResultHelper
  with ScalarElementSequenceChildParseResultHelper {

  def erd: ElementRuntimeData
}

trait PositionalLikeElementSeparatedSequenceChildParseResultMixin
  extends ElementSequenceChildParseResultHelper {

  final override protected def anyTypeElementFailedParseAttemptStatus(
    pstate: PState,
    isZL: Boolean, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {
    requiredOptional match {
      case _: RequiredOptionalStatus.Optional if isZL => {
        //
        // optional and zero-length
        // if we just said AbsentRep, that generally triggers backtracking an element
        //
        // Correction - if you set success here, then failures like when you parse
        // an int, but hit end of data, and get unable to convert empty string to int....
        // those should cause a failure, but those would be masked here.
        // pstate.setSuccess()
        //
        // Missing/Failed is certainly correct for NonPositional(anyEmpty)separated sequences.
        // Also for Positional, and Unseparated.
        //
        // PositionalTrailing is the question. There, a failure that is ZL we want to have
        // just be Absent.
        ParseAttemptStatus.AbsentRep
      }
      case _ if isZL =>
        ParseAttemptStatus.MissingItem
      case _ =>
        ParseAttemptStatus.FailureUnspecified
    }
  }

}

class PositionalScalarElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends ScalarElementSeparatedSequenceChildParseResultHelper
  with ScalarElementSequenceChildParseResultHelper
  with PositionalLikeElementSeparatedSequenceChildParseResultMixin

class NonPositionalScalarElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends ScalarElementSeparatedSequenceChildParseResultHelper
  with NonPositionalLikeElementSequenceChildParseResultMixin

class PositionalTrailingScalarElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends ScalarElementSeparatedSequenceChildParseResultHelper
  with PositionalLikeElementSeparatedSequenceChildParseResultMixin

trait RepElementSeparatedSequenceChildParseResultHelper
  extends SeparatedSequenceChildParseResultHelper
  with RepElementSequenceChildParseResultHelper

class PositionalTrailingRepElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends PositionalRepElementSeparatedSequenceChildParseResultHelper(
    separatedSequenceChildBehavior, erd, isSimpleDelimited, emptyElementParsePolicy,
    isEmptyRepZeroLength, isEmptyRepNonZeroLength)

class PositionalRepElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends RepElementSeparatedSequenceChildParseResultHelper
  with PositionalLikeElementSeparatedSequenceChildParseResultMixin {

}

class NonPositionalRepElementSeparatedSequenceChildParseResultHelper(
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val erd: ElementRuntimeData,
  override val isSimpleDelimited: Boolean,
  override val emptyElementParsePolicy: EmptyElementParsePolicy,
  override val isEmptyRepZeroLength: Boolean,
  override val isEmptyRepNonZeroLength: Boolean)
  extends RepElementSeparatedSequenceChildParseResultHelper
  with NonPositionalLikeElementSequenceChildParseResultMixin

trait GroupSeparatedSequenceChildParseResultHelper
  extends SeparatedSequenceChildParseResultHelper
  with ModelGroupSequenceChildParseResultHelper {

  final override def isSimpleDelimited = false // only relevant to elements
}

trait PositionalLikeGroupSequenceChildParseResultMixin
  extends ModelGroupSequenceChildParseResultHelper {

  /**
   * Called directly sometimes.
   * Used by trickier parser (e.g., postfix separator helper) that
   */
  final protected def modelGroupSuccessParseAttemptStatus(
    parser: SequenceChildParser,
    pstate: PState,
    isZL: Boolean,
    mgrd: ModelGroupRuntimeData,
    requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {
    Assert.invariant(pstate.isSuccess)
    checkModelGroupZL(pstate, isZL)
    if (isZL)
      ParseAttemptStatus.EmptyRep
    else
      ParseAttemptStatus.NormalRep
  }

}
class PositionalGroupSeparatedSequenceChildParseResultHelper(
  override val mgrd: ModelGroupRuntimeData,
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val isModelGroupRepPossiblyZeroLength: Boolean,
  override val isModelGroupRepNonZeroLength: Boolean)
  extends GroupSeparatedSequenceChildParseResultHelper
  with PositionalLikeGroupSequenceChildParseResultMixin {

}
class PositionalTrailingGroupSeparatedSequenceChildParseResultHelper(
  override val mgrd: ModelGroupRuntimeData,
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val isModelGroupRepPossiblyZeroLength: Boolean,
  override val isModelGroupRepNonZeroLength: Boolean)
  extends GroupSeparatedSequenceChildParseResultHelper
  with PositionalLikeGroupSequenceChildParseResultMixin {

  final override def computeFailedSeparatorParseAttemptStatus(
    parser: SequenceChildParser,
    prevBitPosBeforeChild: Long,
    pstate: PState,
    isZL: Boolean,
    requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {

    // When we're parsing a separated sequence, and we're positional trailing
    // and we don't find a separator, the child never even gets attempted to parse
    // we immediately say the group is absent.
    //
    // This allows it to be skipped, but also ends the sequence.
    //
    if (isModelGroupRepPossiblyZeroLength &&
      !isModelGroupRepNonZeroLength) {
      pstate.setSuccess()
      ParseAttemptStatus.AbsentRep
    } else
      ParseAttemptStatus.FailureUnspecified
  }

}

class NonPositionalGroupSeparatedSequenceChildParseResultHelper(
  override val mgrd: ModelGroupRuntimeData,
  override val separatedSequenceChildBehavior: SeparatedSequenceChildBehavior,
  override val isModelGroupRepPossiblyZeroLength: Boolean,
  override val isModelGroupRepNonZeroLength: Boolean)
  extends GroupSeparatedSequenceChildParseResultHelper {

  /**
   * Called directly sometimes.
   * Used by trickier parser (e.g., postfix separator helper) that
   */
  final protected def modelGroupSuccessParseAttemptStatus(
    parser: SequenceChildParser,
    pstate: PState,
    isZL: Boolean,
    mgrd: ModelGroupRuntimeData,
    requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {
    if (pstate.isSuccess) {
      val maybeElem = pstate.infoset.asComplex.maybeMostRecentlyAddedChild()
      Assert.invariant(maybeElem.isDefined)
      val elem = maybeElem.get
      val maybeIsNilled = elem.maybeIsNilled // can't just call isNilled because that throws exceptions on not defined
      if (maybeIsNilled.isDefined && maybeIsNilled.get) {
        ParseAttemptStatus.NilRep
      } else {
        ParseAttemptStatus.NormalRep
      }
    } else {
      if (isZL)
        ParseAttemptStatus.MissingItem // it is an error if ZL in NonPositional
      else
        ParseAttemptStatus.NormalRep
    }
  }

}
