//
// Copyright (c) 2021, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   20 Aug 2021  Matthew Giannini  Creation
//

using concurrent
using crypto

**
** Configuration options for TCP and UDP sockets. All socket types accept
** a socket configuration which will be used to configure the socket when
** it is created.
**
** A system-wide default socket configuration can be obtained with
** `SocketConfig.cur`. You can change the system default by using
** `SocketConfig.setCur`.
**
** See `TcpSocket.make`, `TcpListener.make`, `UdpSocket.make`, `MulticastSocket.make`
**
const class SocketConfig
{

//////////////////////////////////////////////////////////////////////////
// Cur
//////////////////////////////////////////////////////////////////////////

  ** Get the current, default socket configuration
  static SocketConfig cur() { curRef.val }

  ** Set a new default socket configuration. This configuration will
  ** only apply to new sockets created after this is called. This
  ** method may only be called **once** to change the default socket configuration.
  static Void setCur(SocketConfig cfg)
  {
    if (errRef.val != null) throw Err("Default socket configuration already set", errRef.val)
    curRef.val = cfg
    errRef.val = Err("Default socket configuration changed")
  }

  private static const AtomicRef curRef := AtomicRef(SocketConfig())
  private static const AtomicRef errRef := AtomicRef()

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  ** Create and configure the socket options.
  new make(|This|? f := null)
  {
    f?.call(this)
  }

  @NoDoc protected new makeCopy(SocketConfig? orig, |This| f)
  {
    if (orig != null)
    {
      this.keystore          = orig.keystore
      this.truststore        = orig.truststore
      this.tlsParams         = orig.tlsParams

      this.inBufferSize      = orig.inBufferSize
      this.keepAlive         = orig.keepAlive
      this.receiveBufferSize = orig.receiveBufferSize
      this.sendBufferSize    = orig.sendBufferSize
      this.reuseAddr         = orig.reuseAddr
      this.linger            = orig.linger
      this.connectTimeout    = orig.connectTimeout
      this.receiveTimeout    = orig.receiveTimeout
      this.acceptTimeout     = orig.acceptTimeout
      this.noDelay           = orig.noDelay
      this.trafficClass      = orig.trafficClass

      this.broadcast         = orig.broadcast
    }
    f(this)
  }

  ** Create a copy of this configuration and then apply any overrides from the it-block.
  virtual This copy(|This| f) { makeCopy(this, f) }

  ** Convenience to create a copy of this socket configuration and set the connect
  ** and receive timeouts to the given duration. Setting to 'null' indicates
  ** infinite timeouts.
  This setTimeouts(Duration? connectTimeout, Duration? receiveTimeout := connectTimeout)
  {
    copy { it.connectTimeout = connectTimeout; it.receiveTimeout = receiveTimeout }
  }

  private native Void force_peer()

//////////////////////////////////////////////////////////////////////////
// Tls Config
//////////////////////////////////////////////////////////////////////////

  ** The `crypto::KeyStore` to use when creating secure sockets. If null, the runtime
  ** default will be used.
  const KeyStore? keystore := null

  ** The `crypto::KeyStore` to use for obtaining trusted certificates when creating
  ** secure sockets. If null, the runtime default will be used.
  const KeyStore? truststore := null

  ** TCP sockets that are upgraded to TLS will be configured with these parameters.
  ** The following parameters are supported:
  ** - 'appProtocols': ('Str[]') prioritized array of application-layer protocol
  ** names that can be negotiated over the TLS protocol
  **
  ** **Experimental - this functionality is subject to change**
  @NoDoc const Str:Obj? tlsParams := [:]

//////////////////////////////////////////////////////////////////////////
// Socket Config
//////////////////////////////////////////////////////////////////////////


  ** The size in bytes for the sys::InStream buffer. A value of 0 or
  ** null disables input stream buffing.
  const Int? inBufferSize := 4096

  ** The size in bytes for the sys::OutStream buffer. A value of 0 or
  ** null disables output stream buffing.
  const Int? outBufferSize := 4096

  ** 'SO_KEEPALIVE' option
  const Bool keepAlive := false

  ** 'SO_RCVBUF' option for the size in bytes of the IP stack buffers.
  const Int receiveBufferSize := 65_536

  ** 'SO_SNDBUF' option for the size in bytes of the IP stack buffers.
  const Int sendBufferSize := 65_536

  ** 'SO_REUSEADDR' is used to control the time wait state of a closed socket.
  const Bool reuseAddr := false

  ** 'SO_LINGER' controls the linger time or set to null to disable linger.
  const Duration? linger := null

  ** Controls the default timeout used by `TcpSocket.connect`.
  ** A null value indicates a system default timeout (usually wait forever).
  const Duration? connectTimeout  := 60sec

  ** 'SO_TIMEOUT' controls the amount of time a socket
  ** will block on a read call before throwing an IOErr timeout exception.
  ** 'null' is used to indicate an infinite timeout.
  const Duration? receiveTimeout := 60sec

  ** Controls how long a `TcpListener.accept` will block before throwing an
  ** IOErr timeout exception. 'null' is used to indicate infinite timeout.
  const Duration? acceptTimeout := null

  ** 'TCP_NODELAY' socket option specifies that send not be delayed
  ** to merge packets (Nagle's algorthm).
  const Bool noDelay := true

  ** The type-of-class byte in the IP packet header.
  **
  ** For IPv4 this value is detailed in RFC 1349 as the following bitset:
  **  - IPTOS_LOWCOST     (0x02)
  **  - IPTOS_RELIABILITY (0x04)
  **  - IPTOS_THROUGHPUT  (0x08)
  **  - IPTOS_LOWDELAY    (0x10)
  **
  ** For IPv6 this is the value placed into the sin6_flowinfo header field.
  const Int trafficClass := 0

  ** 'SO_BROADCAST' socket option
  const Bool broadcast := false

}