///|
pub struct Config {
  role : Role
  identity : CertificateIdentity
  expected_peer_fingerprint : Fingerprint
  wall_time : @transport.WallTime
  cipher_suites : Array[CipherSuite]
  psk : Bytes?
  psk_identity : Bytes?
  srtp_profiles : Array[SrtpProtectionProfile]
  flight_interval : @transport.Duration
  mtu : Int
  replay_window : Int
  verify_peer_fingerprint : Bool
}

///|
pub fn Config::new(
  role~ : Role,
  identity~ : CertificateIdentity,
  expected_peer_fingerprint~ : Fingerprint,
  wall_time~ : @transport.WallTime,
  cipher_suites? : Array[CipherSuite] = [],
  psk? : Bytes,
  psk_identity? : Bytes,
  srtp_profiles? : Array[SrtpProtectionProfile] = [],
  flight_interval? : @transport.Duration,
  mtu? : Int = 1200,
  replay_window? : Int = 64,
  verify_peer_fingerprint? : Bool = true,
) -> Config raise DtlsError {
  if role == Auto {
    raise HandshakeFailed("DTLS setup role must be resolved before start")
  }
  if mtu < 256 || mtu > 65535 {
    raise HandshakeFailed("DTLS MTU must be between 256 and 65535 bytes")
  }
  if replay_window < 1 || replay_window > 64 {
    raise HandshakeFailed("DTLS replay window must be between 1 and 64")
  }
  let cipher_suites = if cipher_suites.is_empty() {
    if psk is Some(_) {
      [PskAes128GcmSha256, PskAes128Ccm, PskAes128Ccm8]
    } else {
      [
        EcdheEcdsaAes128GcmSha256,
        EcdheEcdsaAes256CbcSha,
        EcdheEcdsaChacha20Poly1305Sha256,
        EcdheEcdsaAes128Ccm,
        EcdheEcdsaAes128Ccm8,
      ]
    }
  } else {
    cipher_suites.copy()
  }
  let seen_suites : Map[UInt16, Bool] = Map([])
  for suite in cipher_suites {
    if seen_suites.contains(suite.code()) {
      raise HandshakeFailed("duplicate DTLS cipher suite")
    }
    seen_suites[suite.code()] = true
    if suite.is_psk() != (psk is Some(_)) {
      raise HandshakeFailed(
        "PSK credentials and DTLS cipher-suite type do not match",
      )
    }
  }
  match (psk, psk_identity) {
    (Some(key), Some(identity)) => {
      if key.is_empty() || key.length() > 0xffff {
        raise HandshakeFailed("DTLS PSK must contain 1..65535 bytes")
      }
      if identity.is_empty() || identity.length() > 0xffff {
        raise HandshakeFailed("DTLS PSK identity must contain 1..65535 bytes")
      }
    }
    (Some(_), None) =>
      raise HandshakeFailed("DTLS PSK requires a local identity")
    (None, Some(_)) => raise HandshakeFailed("DTLS PSK identity requires a key")
    (None, None) => ()
  }
  let seen : Map[UInt16, Bool] = Map([])
  for profile in srtp_profiles {
    if seen.contains(profile.code()) {
      raise HandshakeFailed("duplicate SRTP protection profile")
    }
    seen[profile.code()] = true
  }
  let flight_interval = match flight_interval {
    Some(value) => value
    None =>
      @transport.Duration::milliseconds(1000) catch {
        NegativeDuration(value) =>
          raise HandshakeFailed("invalid flight interval \{value}")
        NegativeMonotonicTime(value) =>
          raise HandshakeFailed("invalid monotonic time \{value}")
        TimeOverflow => raise HandshakeFailed("flight interval overflow")
      }
  }
  if flight_interval.as_milliseconds() == 0L {
    raise HandshakeFailed("DTLS flight interval must be positive")
  }
  {
    role,
    identity,
    expected_peer_fingerprint,
    wall_time,
    cipher_suites,
    psk,
    psk_identity,
    srtp_profiles: srtp_profiles.copy(),
    flight_interval,
    mtu,
    replay_window,
    verify_peer_fingerprint,
  }
}

///|
pub fn Config::role(self : Config) -> Role {
  self.role
}

///|
pub fn Config::local_fingerprint(self : Config) -> Fingerprint raise DtlsError {
  self.identity.fingerprint(algorithm=self.expected_peer_fingerprint.algorithm)
}

///|
pub fn Config::srtp_profiles(self : Config) -> Array[SrtpProtectionProfile] {
  self.srtp_profiles.copy()
}

///|
pub fn Config::cipher_suites(self : Config) -> Array[CipherSuite] {
  self.cipher_suites.copy()
}

///|
pub fn Config::psk_identity(self : Config) -> Bytes? {
  self.psk_identity
}

///|
fn Config::maximum_record_expansion(self : Config) -> Int {
  let mut result = 0
  for suite in self.cipher_suites {
    let expansion = suite.maximum_record_expansion()
    if expansion > result {
      result = expansion
    }
  }
  result
}

///|
pub(all) enum AlertLevel {
  WarningAlert
  FatalAlert
} derive(Debug, Eq)

///|
pub(all) enum AlertDescription {
  CloseNotify
  UnexpectedMessage
  BadRecordMac
  HandshakeFailure
  BadCertificate
  IllegalParameter
  DecodeError
  DecryptError
  ProtocolVersionAlert
  InternalError
  UnsupportedExtension
  UnknownAlert(Byte)
} derive(Debug, Eq)

///|
pub(all) enum DtlsEvent {
  StateChanged(State)
  ConnectedWithSrtp(SrtpProtectionProfile?)
  ApplicationDataReceived(Bytes)
  AlertReceived(AlertLevel, AlertDescription)
} derive(Debug, Eq)