/*
 * 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.unparsers

import org.apache.daffodil.processors.{ SequenceRuntimeData, TermRuntimeData }
import org.apache.daffodil.processors.ElementRuntimeData
import org.apache.daffodil.equality._
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind

trait Unseparated { self: SequenceChildUnparser =>

  val childProcessors = Vector(childUnparser)
}

class ScalarOrderedUnseparatedSequenceChildUnparser(
  childUnparser: Unparser,
  srd: SequenceRuntimeData,
  trd: TermRuntimeData)
  extends SequenceChildUnparser(childUnparser, srd, trd)
  with Unseparated {

  override protected def unparse(state: UState) = childUnparser.unparse1(state)
}

class RepOrderedUnseparatedSequenceChildUnparser(
  childUnparser: Unparser,
  srd: SequenceRuntimeData,
  erd: ElementRuntimeData)
  extends RepeatingChildUnparser(childUnparser, srd, erd)
  with Unseparated {

  private val ock = erd.maybeOccursCountKind.get

  override def checkArrayPosAgainstMaxOccurs(state: UState): Boolean = {
    if (ock eq OccursCountKind.Implicit)
      state.arrayPos <= maxRepeats(state)
    else
      true
  }
}

class OrderedUnseparatedSequenceUnparser(rd: SequenceRuntimeData, childUnparsers: Seq[SequenceChildUnparser])
  extends OrderedSequenceUnparserBase(rd, childUnparsers.toVector) {

  /**
   * Unparses one iteration of an array/optional element
   */
  protected def unparseOne(
    unparser: SequenceChildUnparser,
    trd: TermRuntimeData,
    state: UState): Unit = {

    unparser.unparse1(state)
  }

  /**
   * Unparses an entire sequence, including both scalar and array/optional children.
   */
  protected def unparse(state: UState): Unit = {

    state.groupIndexStack.push(1L) // one-based indexing

    var index = 0
    var doUnparser = false
    val limit = childUnparsers.length
    while (index < limit) {
      val childUnparser = childUnparsers(index)
      val trd = childUnparser.trd
      state.pushTRD(trd) // because we inspect before we invoke the unparser

      //
      // Unparsing an ordered sequence depends on the incoming
      // stream of infoset events matching up with the order that
      // they are expected as the unparser recurses through the
      // child term unparsers.
      //
      childUnparser match {
        case unparser: RepeatingChildUnparser => {
          state.arrayIndexStack.push(1L)
          val erd = unparser.erd
          var numOccurrences = 0
          val maxReps = unparser.maxRepeats(state)
          val minReps = unparser.minRepeats(state)

          //
          // The number of occurrances we unparse is always exactly driven
          // by the number of infoset events for the repeating/optional element.
          //
          // For RepUnparser - array/optional case - in all cases we should get a
          // startArray event. If we don't then
          // the element must be entirely optional, so we get no events for it
          // at all.
          //
          if (state.inspect) {
            val ev = state.inspectAccessor
            val isArr = ev.isArray
            if (ev.isStart && (isArr || ev.erd.isOptional)) {
              val eventNQN = ev.namedQName
              if (ev.erd eq erd) {
                //
                // StartArray for this unparser's array element
                //
                unparser.startArrayOrOptional(state)
                while ({
                  doUnparser = unparser.shouldDoUnparser(unparser, state)
                  doUnparser
                }) {
                  if (isArr) if (state.dataProc.isDefined) state.dataProc.get.beforeRepetition(state, this)

                  unparseOne(unparser, erd, state)
                  numOccurrences += 1
                  state.moveOverOneArrayIndexOnly()
                  state.moveOverOneGroupIndexOnly() // array elements are always represented.

                  if (isArr) if (state.dataProc.isDefined) state.dataProc.get.afterRepetition(state, this)
                }

                unparser.checkFinalOccursCountBetweenMinAndMaxOccurs(state, unparser, numOccurrences, maxReps, state.arrayPos - 1)
                unparser.endArrayOrOptional(erd, state)
              } else {
                //
                // start array but not for the expected array,
                // rather for some other array. Not this one. So we
                // don't unparse anything here, and we'll go on to the next
                // sequence child, which hopefully will be a matching array.
                //
                // minReps has to be 0, meaning it is allowed to have zero
                // occurrences (not necessarily valid, but allowed),
                // because we got zero instances of this array
                //
                Assert.invariant(minReps == 0L)
              }
            } else if (ev.isStart) {
              Assert.invariant(!isArr && !ev.erd.isOptional)
              //
              // start of scalar.
              // That has to be for a different element later in the sequence
              // since this one has a RepUnparser (i.e., is NOT scalar)
              val eventNQN = ev.namedQName
              Assert.invariant(eventNQN != erd.namedQName)
            } else {
              Assert.invariant(ev.isEnd && ev.erd.isComplexType)
              unparser.checkFinalOccursCountBetweenMinAndMaxOccurs(state, unparser, numOccurrences, maxReps, 0)
            }
          } else {
            // no event (state.inspect returned false)
            Assert.invariantFailed("No event for unparing.")
          }

          state.arrayIndexStack.pop()
        }
        //
        case scalarUnparser => {
          unparseOne(scalarUnparser, trd, state)
          // only move over in group if the scalar "thing" is an element
          // that is represented.
          scalarUnparser.trd match {
            case erd: ElementRuntimeData if (!erd.isRepresented) => // ok, skip group advance
            case _ => state.moveOverOneGroupIndexOnly()
          }
        }
      }

      state.popTRD(trd)
      index += 1
      //
      // Note: the invariant is that unparsers move over 1 within their group themselves
      // we do not do the moving over here as we are the caller of the unparser.
      //
    }

    state.groupIndexStack.pop()
    //
    // this is establishing the invariant that unparsers (in this case the sequence unparser)
    // moves over within its containing group. The caller of an unparser does not do this move.
    //
    // state.moveOverOneGroupIndexOnly() // move past this sequence itself, next group child it ITS parent.
  }

}
