/*
 * 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.persistence.state

import scala.annotation.nowarn
import scala.reflect.ClassTag

import org.apache.pekko
import pekko.actor.ActorSystem
import pekko.actor.ClassicActorSystemProvider
import pekko.actor.ExtendedActorSystem
import pekko.actor.Extension
import pekko.actor.ExtensionId
import pekko.actor.ExtensionIdProvider
import pekko.annotation.InternalApi
import pekko.persistence.Persistence
import pekko.persistence.PersistencePlugin
import pekko.persistence.PluginProvider
import pekko.persistence.state.scaladsl.DurableStateStore

import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory

/**
 * Persistence extension for queries.
 */
object DurableStateStoreRegistry extends ExtensionId[DurableStateStoreRegistry] with ExtensionIdProvider {

  override def get(system: ActorSystem): DurableStateStoreRegistry = super.get(system)
  override def get(system: ClassicActorSystemProvider): DurableStateStoreRegistry = super.get(system)

  def createExtension(system: ExtendedActorSystem): DurableStateStoreRegistry = new DurableStateStoreRegistry(system)

  def lookup: DurableStateStoreRegistry.type = DurableStateStoreRegistry

  @InternalApi
  private[pekko] val pluginProvider
      : PluginProvider[DurableStateStoreProvider, DurableStateStore[?], javadsl.DurableStateStore[?]] =
    new PluginProvider[DurableStateStoreProvider, scaladsl.DurableStateStore[?], javadsl.DurableStateStore[?]] {
      override def scalaDsl(t: DurableStateStoreProvider): DurableStateStore[?] = t.scaladslDurableStateStore()
      override def javaDsl(t: DurableStateStoreProvider): javadsl.DurableStateStore[?] = t.javadslDurableStateStore()
    }

}

class DurableStateStoreRegistry(system: ExtendedActorSystem)
    extends PersistencePlugin[scaladsl.DurableStateStore[?], javadsl.DurableStateStore[?], DurableStateStoreProvider](
      system)(ClassTag(classOf[DurableStateStoreProvider]), DurableStateStoreRegistry.pluginProvider)
    with Extension {

  private val systemConfig = system.settings.config

  private lazy val defaultPluginId = {
    val configPath = systemConfig.getString("pekko.persistence.state.plugin")
    Persistence.verifyPluginConfigIsDefined(configPath, "Default DurableStateStore")
    Persistence.verifyPluginConfigExists(systemConfig, configPath, "DurableStateStore")
    configPath
  }

  private def pluginIdOrDefault(pluginId: String): String =
    pluginIdOrDefault(pluginId, ConfigFactory.empty)

  private def pluginIdOrDefault(pluginId: String, pluginConfig: Config): String = {
    val configPath = if (isEmpty(pluginId)) defaultPluginId else pluginId
    Persistence.verifyPluginConfigExists(pluginConfig.withFallback(systemConfig), configPath, "DurableStateStore")
    configPath
  }

  private def pluginConfig(pluginId: String): Config = {
    val configPath = pluginIdOrDefault(pluginId)
    systemConfig.getConfig(configPath).withFallback(systemConfig.getConfig("pekko.persistence.state-plugin-fallback"))
  }

  /** Check for default or missing identity. */
  private def isEmpty(text: String) = {
    (text eq null) || text.isEmpty
  }

  /**
   * Scala API: Returns the [[pekko.persistence.state.scaladsl.DurableStateStore]] specified by the given
   * configuration entry.
   */
  final def durableStateStoreFor[T <: scaladsl.DurableStateStore[?]](pluginId: String): T = {
    pluginFor(pluginIdOrDefault(pluginId), pluginConfig(pluginId)).scaladslPlugin.asInstanceOf[T]
  }

  /**
   * Scala API: Returns the [[pekko.persistence.state.scaladsl.DurableStateStore]] specified by the given
   * configuration entry. The provided `runtimeConfig` is used to configure the plugin at runtime, taking
   * precedence over the plugin configuration defined in the actor system configuration.
   *
   * @since 2.0.0
   */
  final def durableStateStoreFor[T <: scaladsl.DurableStateStore[?]](pluginId: String, runtimeConfig: Config): T = {
    pluginFor(pluginIdOrDefault(pluginId, runtimeConfig), runtimeConfig).scaladslPlugin.asInstanceOf[T]
  }

  /**
   * Java API: Returns the [[pekko.persistence.state.javadsl.DurableStateStore]] specified by the given
   * configuration entry.
   */
  final def getDurableStateStoreFor[T <: javadsl.DurableStateStore[?]](
      @nowarn("msg=never used") clazz: Class[T], // FIXME generic Class could be problematic in Java
      pluginId: String): T = {
    pluginFor(pluginIdOrDefault(pluginId), pluginConfig(pluginId)).javadslPlugin.asInstanceOf[T]
  }

  /**
   * Java API: Returns the [[pekko.persistence.state.javadsl.DurableStateStore]] specified by the given
   * configuration entry. The provided `runtimeConfig` is used to configure the plugin at runtime, taking
   * precedence over the plugin configuration defined in the actor system configuration.
   *
   * @since 2.0.0
   */
  final def getDurableStateStoreFor[T <: javadsl.DurableStateStore[?]](
      @nowarn("msg=never used") clazz: Class[T], // FIXME generic Class could be problematic in Java
      pluginId: String,
      runtimeConfig: Config): T = {
    pluginFor(pluginIdOrDefault(pluginId, runtimeConfig), runtimeConfig).javadslPlugin.asInstanceOf[T]
  }

}
