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

package org.apache.pekko.http.scaladsl.settings

import java.net.InetSocketAddress
import java.util.Random
import java.util.function.Supplier

import org.apache.pekko
import pekko.annotation.ApiMayChange
import pekko.annotation.DoNotInherit
import pekko.http.impl.util._
import pekko.http.impl.settings.ClientConnectionSettingsImpl
import pekko.http.scaladsl.ClientTransport
import pekko.http.scaladsl.model.headers.`User-Agent`
import pekko.io.Inet.SocketOption
import com.typesafe.config.Config

import scala.collection.immutable
import scala.concurrent.duration.{ Duration, FiniteDuration }

/**
 * Public API but not intended for subclassing
 */
@DoNotInherit
abstract class ClientConnectionSettings private[pekko] ()
    extends org.apache.pekko.http.javadsl.settings.ClientConnectionSettings {
  self: ClientConnectionSettingsImpl =>
  def userAgentHeader: Option[`User-Agent`]
  def connectingTimeout: FiniteDuration
  def idleTimeout: Duration
  def requestHeaderSizeHint: Int
  def websocketSettings: WebSocketSettings
  def websocketRandomFactory: () => Random
  def socketOptions: immutable.Seq[SocketOption]
  def parserSettings: ParserSettings
  def logUnencryptedNetworkBytes: Option[Int]
  def streamCancellationDelay: FiniteDuration
  def localAddress: Option[InetSocketAddress]
  def http2Settings: Http2ClientSettings

  /** The underlying transport used to connect to hosts. By default [[ClientTransport.TCP]] is used. */
  @ApiMayChange
  def transport: ClientTransport

  // ---

  // overrides for more specific return type
  def withConnectingTimeout(newValue: FiniteDuration): ClientConnectionSettings =
    self.copy(connectingTimeout = newValue)
  def withIdleTimeout(newValue: Duration): ClientConnectionSettings = self.copy(idleTimeout = newValue)
  def withRequestHeaderSizeHint(newValue: Int): ClientConnectionSettings = self.copy(requestHeaderSizeHint = newValue)
  def withStreamCancellationDelay(newValue: FiniteDuration): ClientConnectionSettings =
    self.copy(streamCancellationDelay = newValue)

  // overloads for idiomatic Scala use
  def withWebsocketSettings(newValue: WebSocketSettings): ClientConnectionSettings =
    self.copy(websocketSettings = newValue)
  def withWebsocketRandomFactory(newValue: () => Random): ClientConnectionSettings =
    withWebsocketSettings(self.websocketSettings.withRandomFactoryFactory(new Supplier[Random] {
      override def get(): Random = newValue()
    }))
  def withUserAgentHeader(newValue: Option[`User-Agent`]): ClientConnectionSettings =
    self.copy(userAgentHeader = newValue)
  def withLogUnencryptedNetworkBytes(newValue: Option[Int]): ClientConnectionSettings =
    self.copy(logUnencryptedNetworkBytes = newValue)
  def withSocketOptions(newValue: immutable.Seq[SocketOption]): ClientConnectionSettings =
    self.copy(socketOptions = newValue)
  def withParserSettings(newValue: ParserSettings): ClientConnectionSettings = self.copy(parserSettings = newValue)
  def withLocalAddress(newValue: Option[InetSocketAddress]): ClientConnectionSettings =
    self.copy(localAddress = newValue)
  def withHttp2Settings(newValue: Http2ClientSettings): ClientConnectionSettings = self.copy(http2Settings = newValue)

  @ApiMayChange
  def withTransport(newTransport: ClientTransport): ClientConnectionSettings = self.copy(transport = newTransport)

  // Scala-only lenses
  def mapHttp2Settings(f: Http2ClientSettings => Http2ClientSettings): ClientConnectionSettings =
    withHttp2Settings(f(self.http2Settings))

  /**
   * Returns a new instance with the given local address set if the given override is `Some(address)`, otherwise
   * return this instance unchanged.
   */
  def withLocalAddressOverride(overrideLocalAddressOption: Option[InetSocketAddress]): ClientConnectionSettings =
    if (overrideLocalAddressOption.isDefined) withLocalAddress(overrideLocalAddressOption)
    else this
}

object ClientConnectionSettings extends SettingsCompanion[ClientConnectionSettings] {
  override def apply(config: Config): ClientConnectionSettings = ClientConnectionSettingsImpl(config)
  override def apply(configOverrides: String): ClientConnectionSettings = ClientConnectionSettingsImpl(configOverrides)

  object LogUnencryptedNetworkBytes {
    def apply(string: String): Option[Int] =
      string.toRootLowerCase match {
        case "off" => None
        case value => Option(value.toInt)
      }
  }
}
