/*
 * 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.processors.ElementRuntimeData
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.processors.Failure
import org.apache.daffodil.processors.Success

sealed abstract class SeparatorParseStatus
object SeparatorParseStatus {
  type Type = SeparatorParseStatus
  sealed abstract class SeparatorSuccess extends Type
  case object SeparatorFound extends SeparatorSuccess
  case object SeparatorNotNeeded extends SeparatorSuccess
  case object SeparatorFailed extends Type
}

/**
 * Abstracts away separator position. Digests result of parsing
 * item and associated separator into a summary status.
 */
sealed abstract class SeparatorParseHelper(
  protected val sep: Parser,
  protected val childParser: Parser,
  scParserArg: Separated)
  extends Serializable {

  import ParseAttemptStatus._

  protected val scParser = scParserArg.asInstanceOf[SequenceChildParser with Separated]

  def parseOneWithSeparator(state: PState, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus

  /**
   * Sets PState status to failure when separator is not found.
   */
  final protected def failedSeparator(pstate: PState, kind: String): Unit = {
    val cause = pstate.processorStatus.asInstanceOf[Failure].cause
    scParser.trd match {
      case erd: ElementRuntimeData if (erd.isArray) =>
        sep.PE(pstate, "Failed to populate %s[%s]. Missing %s separator. Cause: %s",
          erd.prefixedName, pstate.mpstate.arrayPos, kind, cause)
      case _ =>
        sep.PE(pstate, "Failed to parse %s separator. Cause: %s.", kind, cause)
    }
  }
}

final class PrefixSeparatorHelper(sep: Parser, childParser: Parser, scParserArg: Separated)
  extends SeparatorParseHelper(sep, childParser, scParserArg)
  with InfixPrefixSeparatorHelperMixin {
  import ParseAttemptStatus._

  override def kind = "prefix"

  override def parseOneWithSeparator(pstate: PState, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus =
    parseOneWithInfixOrPrefixSeparator(true, pstate, requiredOptional)
}

final class InfixSeparatorHelper(sep: Parser, childParser: Parser, scParserArg: Separated)
  extends SeparatorParseHelper(sep, childParser, scParserArg)
  with InfixPrefixSeparatorHelperMixin {
  import ParseAttemptStatus._

  override def kind = "infix"

  override def parseOneWithSeparator(pstate: PState, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {

    val infixSepShouldBePresent =
      pstate.mpstate.groupPos > 1

    parseOneWithInfixOrPrefixSeparator(infixSepShouldBePresent, pstate, requiredOptional)
  }
}

trait InfixPrefixSeparatorHelperMixin { self: SeparatorParseHelper =>
  import ParseAttemptStatus._

  protected def kind: String

  final protected def parseOneWithInfixOrPrefixSeparator(
    shouldParseTheSep: Boolean,
    pstate: PState,
    requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {

    val sepStatus =
      if (shouldParseTheSep) {
        sep.parse1(pstate)
        if (pstate.processorStatus eq Success)
          SeparatorParseStatus.SeparatorFound
        else
          SeparatorParseStatus.SeparatorFailed
      } else
        SeparatorParseStatus.SeparatorNotNeeded

    val prevBitPosBeforeChild = pstate.bitPos0b

    sepStatus match {
      case _: SeparatorParseStatus.SeparatorSuccess => {
        childParser.parse1(pstate)
        val pas =
          scParser.parseResultHelper.computeParseAttemptStatus(
            scParser,
            prevBitPosBeforeChild, pstate,
            requiredOptional)
        pas
      }
      case _ => {
        failedSeparator(pstate, kind)
        scParser.parseResultHelper.computeFailedSeparatorParseAttemptStatus(
          scParser,
          prevBitPosBeforeChild, pstate,
          isZL = true,
          requiredOptional)
      }
    }
  }

}

final class PostfixSeparatorHelper(sep: Parser, child: Parser, scParserArg: Separated,
  isSimpleDelimited: Boolean)
  extends SeparatorParseHelper(sep, child, scParserArg) {
  import ParseAttemptStatus._

  override def parseOneWithSeparator(pstate: PState, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus = {

    val prevBitPosBeforeChild = pstate.bitPos0b
    val beforeChildState = pstate.mark("Postfix Sep parser before child")
    try {
      childParser.parse1(pstate)
      val childSuccessful = pstate.processorStatus eq Success
      val childFailure = !childSuccessful // just makes later logic easier to read
      val bitPosAfterChildAttempt = pstate.bitPos0b
      val prh = scParser.parseResultHelper
      // This seems odd, but there are cases where after a failure
      // the bitPos has not yet been repositioned back to where it started.
      // This does happen for complex types.
      val hasZLChildAttempt = prevBitPosBeforeChild == bitPosAfterChildAttempt

      if (childSuccessful) {
        val dataOnlyRep =
          prh.computeParseAttemptStatus(
            scParser,
            prevBitPosBeforeChild, pstate, requiredOptional)
        sep.parse1(pstate)
        if (pstate.processorStatus eq Success) {
          // we got the postfix sep after successful parse of the data item
          // so whatever the status of the item was, that's the status overall.
          dataOnlyRep
        } else {
          // child successful, but
          // no postfix sep found.
          // capture failure reason, but reset position to before the child,
          // and remove any elements. That way this behaves like a prefix
          // or infix separator, where if you don't get the sep, then you
          // usually (except first one in infix case) don't run the child parser
          val failure = pstate.processorStatus.asInstanceOf[Failure]
          pstate.reset(beforeChildState)
          pstate.setFailed(failure.cause)
          failedSeparator(pstate, "postfix")
          prh.computeFailedSeparatorParseAttemptStatus(scParser, prevBitPosBeforeChild, pstate, hasZLChildAttempt, requiredOptional)
        }
      } else {
        Assert.invariant(childFailure)
        // We might be tempted to just try the postfix sep now,
        // to see if the postfix sep is there immediately.
        // This is, however, problematic.
        //
        // We're depending on this item being an element with lengthKind="delimited"
        // which can't have a zero-length representation, so that if there is zero-data before
        // the postfix separator, we can declare the item to be absentRep.
        //
        // But what if the element is not lengthKind 'delimited'.
        // Or what if it has complex type? But has a non-zero-length structure where this
        // same delimiter is used at a more deeply nested depth?
        //
        // If the type is simple, and lengthKind 'delimited' we can proceed.
        // Otherwise we punt and call it missing.
        //
        if (isSimpleDelimited) {
          val failure = pstate.processorStatus.asInstanceOf[Failure]
          pstate.reset(beforeChildState)
          sep.parse1(pstate)
          if (pstate.isSuccess) {
            val posAfterSep = pstate.bitPos0b
            // failed child, but success on postfix sep.
            // behave here just like a prefix separator that was then followed by a child failure
            pstate.setFailed(failure.cause)
            val pas =
              prh.computeParseAttemptStatus(
                scParser,
                prevBitPosBeforeChild, pstate,
                requiredOptional)
            val res = pas match {
              case AbsentRep => {
                pstate.dataInputStream.setBitPos0b(posAfterSep)
                MissingSeparator
              }
              case _ => pas
            }
            res
          } else {
            // the separator failed on ZL data
            // so no chance on a ZL representation here.
            val isZL = false
            prh.computeFailedSeparatorParseAttemptStatus(scParser, prevBitPosBeforeChild, pstate, isZL, requiredOptional)
          }
        } else {
          // not simple delimited. We really have no way of knowing
          // if trying to parse just the sep now makes any sense at all.
          // (ex: the child could be failing because it is fixed length, with asserts that check the value
          // that fail.)
          val isZL = false
          prh.computeFailedParseAttemptStatus(scParser, prevBitPosBeforeChild, pstate, isZL, requiredOptional)
        }
      }
    } finally {
      if ((beforeChildState ne null) && pstate.isInUse(beforeChildState))
        pstate.discard(beforeChildState)
    }
  }
}
