/*
 * 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) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.remote.artery.compress

import scala.annotation.nowarn
import scala.concurrent.Promise

import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Fork
import org.openjdk.jmh.annotations.Level
import org.openjdk.jmh.annotations.OperationsPerInvocation
import org.openjdk.jmh.annotations.Param
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.TearDown
import org.openjdk.jmh.infra.Blackhole

import org.apache.pekko
import pekko.actor.Actor
import pekko.actor.ActorRef
import pekko.actor.ActorSystem
import pekko.actor.ExtendedActorSystem
import pekko.actor.Props
import pekko.pattern.PromiseActorRef
import pekko.remote.artery.SerializationFormatCache
import pekko.serialization.Serialization

/**
 * Actually more like specific benchmarks for the few concrete LRU cache usages
 */
@Fork(1)
@State(Scope.Benchmark)
@nowarn
class SerializationFormatCacheBenchmark {

  // note 1 means only top level no temporary at all
  @Param(Array("1", "2", "5", "10"))
  private var everyNToToplevel = 0
  // a few "normal" top level actors communicating
  @Param(Array("100"))
  private var uniqueTopLevelRefs = 0
  // we want to simulate one per request-response, but create upfront, so very high number
  @Param(Array("100000"))
  private var uniqueTemporaryRefs = 0

  private var system: ActorSystem = _
  // hardocoded capacity of 1024
  // note that this is not quite realistic, with a single cache,
  // in practice there are N caches, one in each outgoing artery lane
  private var cache: SerializationFormatCache = _
  private var temporaryActorRefs: Array[ActorRef] = _
  private var topLevelActorRefs: Array[ActorRef] = _

  object Parent {
    def props(childCount: Int, childProps: Props) = Props(new Parent(childCount, childProps))
  }
  class Parent(childCount: Int, childProps: Props) extends Actor {
    val children = (0 to childCount).map(_ => context.actorOf(childProps))
    def receive = PartialFunction.empty
  }

  @Setup
  def init(): Unit = {
    system = ActorSystem("SerializationFormatCacheBenchmark")
    temporaryActorRefs = Array.tabulate(uniqueTemporaryRefs)(n =>
      new PromiseActorRef(
        system.asInstanceOf[ExtendedActorSystem].provider,
        Promise(),
        "Any",
        // request path is encoded in this string
        s"_user_region_shard${n % 100}_entitypretendid$n"))

    topLevelActorRefs = Array.tabulate(uniqueTopLevelRefs)(n => system.actorOf(Props.empty, s"actor_$n"))
  }

  // new empty cache instance each iteration to have more control over cached contents
  @Setup(Level.Iteration)
  def perRunSetup(): Unit = {
    cache = new SerializationFormatCache
  }

  @TearDown
  def shutdown(): Unit = {
    system.terminate()
  }

  @Benchmark
  @OperationsPerInvocation(2000)
  def useCache(blackhole: Blackhole): Unit = {
    // serialization requires this
    Serialization.withTransportInformation(system.asInstanceOf[ExtendedActorSystem]) { () =>
      var i: Int = 0
      while (i < 2000) {
        val actorRef =
          if (i % everyNToToplevel == 0) topLevelActorRefs(i % uniqueTopLevelRefs)
          else temporaryActorRefs(i % uniqueTemporaryRefs)
        blackhole.consume(cache.getOrCompute(actorRef))
        i += 1
      }
    }
  }

}
