/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2014-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.stream.scaladsl

import org.apache.pekko.stream._
import org.apache.pekko.stream.impl.ContextPropagation
import org.apache.pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }

trait ZipLatestWithApply {

  [2..22#/**
   * Create a new `ZipLatestWith` specialized for 1 inputs.
   *
   * @param zipper zipping-function from the input values to the output value
   */
  def apply[[#A1#], O](zipper: ([#A1#]) => O): ZipLatestWith1[[#A1#], O] =
    new ZipLatestWith1(zipper, true)
  #

  ]

  [2..22#/**
   * Create a new `ZipLatestWith` specialized for 1 inputs.
   *
   * @param zipper zipping-function from the input values to the output value
   */
  def apply[[#A1#], O](zipper: ([#A1#]) => O, eagerComplete: Boolean): ZipLatestWith1[[#A1#], O] =
    new ZipLatestWith1(zipper, eagerComplete)
  #

  ]

}

[2..22#/** `ZipLatestWith` specialized for 1 inputs */
class ZipLatestWith1[[#A1#], O] (val zipper: ([#A1#]) => O, val eagerComplete: Boolean) extends GraphStage[FanInShape1[[#A1#], O]] {
  override def initialAttributes = Attributes.name("ZipLatestWith1")
  override val shape: FanInShape1[[#A1#], O] = new FanInShape1[[#A1#], O]("ZipLatestWith1")
  def out: Outlet[O] = shape.out
  [#val in0: Inlet[A1] = shape.in0#
  ]

  def this(zipper: ([#A1#]) => O) = this(zipper, eagerComplete = true)

  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) { outer =>
      private var upstreamsToWait: Int = 1
      // Without this field the completion signalling would take one extra pull
      private def willShutDown: Boolean = upstreamsToWait == ##0

      private val contextPropagation = ContextPropagation()
      [#private val inlet0 = new ZipLatestInlet(in0)#
      ]
      private var waitingForTuple = false
      private var staleTupleValues = true

      override def preStart(): Unit = {
        [#pull(in0)#
        ]
      }

      setHandler(
        out,
        new OutHandler {
          override def onPull(): Unit = {
            if (hasAllValues) {
                if (staleTupleValues) {
                    waitingForTuple = true
                } else {
                    pushOutput()
                }
            } else {
                waitingForTuple = true
            }
            tryPullAllIfNeeded()
          }
        }
      )

      [#setHandler(in0, inlet0)#
      ]

      private def hasAllValues = [#inlet0.hasValue#&&]

      private def pushOutput(): Unit = {
        contextPropagation.resumeContext()
        push(out, zipper([#inlet0.value#]))
        if (willShutDown) completeStage()
        staleTupleValues = true
      }

      private def tryPullAllIfNeeded(): Unit = {
        [#if (!hasBeenPulled(in0)) {
          tryPull(in0)
        }#
        ]
      }

      private class ZipLatestInlet[T](in: Inlet[T]) extends InHandler {
         var value: T = _
         var hasValue = false

         override def onPush() = {
              hasValue = true
              value = outer.grab(in)
              // Only one context can be propagated.
              if (outer.hasAllValues) contextPropagation.suspendContext()
              outer.staleTupleValues = false
              if (outer.waitingForTuple && outer.hasAllValues) {
                  outer.pushOutput()
                  outer.waitingForTuple = false
                  outer.tryPullAllIfNeeded()
              }
         }

         override def onUpstreamFinish(): Unit = {
           if (!hasValue) completeStage()
           else if (eagerComplete) outer.upstreamsToWait = ##0
           else outer.upstreamsToWait = outer.upstreamsToWait - ##1
           if (willShutDown && staleTupleValues) completeStage()
         }
       }
    }

    override def toString = "ZipLatestWith1"
}
#
]
