# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Net::SSLeay::SSL_CTX : pointer {
  version_from Net::SSLeay;
  
  use Net::SSLeay::Error;
  
  use Net::SSLeay;
  use Net::SSLeay::SSL_METHOD;
  use Net::SSLeay::X509_STORE;
  use Net::SSLeay::X509_NAME;
  use Net::SSLeay::X509_VERIFY_PARAM;
  use Net::SSLeay::EVP_PKEY;
  use Net::SSLeay::EVP_CIPHER_CTX;
  use List;
  
  use Net::SSLeay::Callback::TlsextServername;
  use Net::SSLeay::Callback::PemPassword;
  use Net::SSLeay::Callback::Msg;
  use Net::SSLeay::Callback::Verify;
  use Net::SSLeay::Callback::AlpnSelect; 
  
  use Net::SSLeay::Util;
  use Net::SSLeay::X509_STORE_CTX;
  use Net::SSLeay::Constant as SSL;
  
  use Sync::Mutex;
  use StringList;
  
  # Class Variables
  our $INSTANCES_H : Hash of Net::SSLeay::SSL_CTX;
  
  our $MUTEX : Sync::Mutex;
  
  INIT {
    $INSTANCES_H = Hash->new;
    
    $MUTEX = Sync::Mutex->new;
  }
  
  # Fields
  has verify_callback : ro Net::SSLeay::Callback::Verify;
  
  has default_passwd_cb : ro Net::SSLeay::Callback::PemPassword;
  
  has alpn_select_cb : ro Net::SSLeay::Callback::AlpnSelect;
  
  has alpn_select_cb_output : string;
  
  has tlsext_servername_callback : ro Net::SSLeay::Callback::TlsextServername;
  
  # Class Methods
  native static method new : Net::SSLeay::SSL_CTX ($method : Net::SSLeay::SSL_METHOD);
  
  private static method new_with_pointer : Net::SSLeay::SSL_CTX ($pointer : Address, $options : object[] = undef) {
    
    my $self = new Net::SSLeay::SSL_CTX;
    
    Fn->set_pointer($self, $pointer);
    
    $self->init($options);
    
    return $self;
  }
  
  protected method init : void ($options : object[] = undef) {
    
    $self->_init_native;
  }
  
  native private method _init_native : void ($objects : object[] = undef);
  
  # Instance Methods
  native method get_mode : long ();
  
  native method set_mode : long ($mode : long);
  
  native method get0_param : Net::SSLeay::X509_VERIFY_PARAM ();
  
  native method load_verify_locations : int ($CAfile : string, $CApath : string);
  
  native method set_default_verify_paths : int ();
  
  native method set_default_verify_paths_windows : void ();
  
  native method use_certificate_file : int ($file : string, $type : int = -1);
  
  native method use_certificate_chain_file : int ($file : string);
  
  native method use_PrivateKey_file : int ($file : string, $type : int = -1);
  
  native method use_PrivateKey : int ($pkey : Net::SSLeay::EVP_PKEY);
  
  native method set_cipher_list : int ($str : string);
  
  native method set_ciphersuites : int ($str : string);
  
  native method get_cert_store : Net::SSLeay::X509_STORE ();
  
  native method set_options : long ($options : long);
  
  native method get_options : long ();
  
  native method clear_options : long ($options : long);
  
  native method set_alpn_protos : int ($protos : string, $protos_len : int = -1);
  
  method set_alpn_protos_with_protocols : int ($protocols : string[]) {
    
    my $protos = Net::SSLeay::Util->convert_to_wire_format($protocols);
    
    my $ret = $self->set_alpn_protos($protos);
    
    return $ret;
  }
  
  native method set1_groups_list : int ($list : string);
  
  native method set_post_handshake_auth : void ($val : int);
  
  native method set_min_proto_version : int ($version : int);
  
  native method set_client_CA_list : void ($list : Net::SSLeay::X509_NAME[]);
  
  native method add_client_CA : int ($cacert : Net::SSLeay::X509);
  
  native method use_certificate : int ($x : Net::SSLeay::X509);
  
  native method add_extra_chain_cert : long ($x509 : Net::SSLeay::X509);
  
  native method set_verify : void ($mode : int, $verify_callback : Net::SSLeay::Callback::Verify = undef);
  
  native method set_default_passwd_cb : void ($cb : Net::SSLeay::Callback::PemPassword);
  
  native method set_alpn_select_cb : void ($cb : Net::SSLeay::Callback::AlpnSelect);
  
  method set_alpn_select_cb_with_protocols : void ($protocols : string[]) {
    
    unless ($protocols) {
      die "The protocols \$protocols must be defined.";
    }
    
    my $cb = [$protocols : string[]] method : int ($ssl : Net::SSLeay, $out_ref : string[], $outlen_ref : byte*, $in : string, $inlen : int) {
      
      my $wire_format = Net::SSLeay::Util->convert_to_wire_format($protocols);
      
      my $status_select_next_proto = Net::SSLeay->select_next_proto($out_ref, $outlen_ref, $in, $inlen, $wire_format, length $wire_format);
      
      my $status = SSL->SSL_TLSEXT_ERR_NOACK;
      if ($status_select_next_proto == SSL->OPENSSL_NPN_NEGOTIATED) {
        $status = SSL->SSL_TLSEXT_ERR_OK;
      }
      
      return $status;
    };
    
    $self->set_alpn_select_cb($cb);
  }
  
  native method set_tlsext_servername_callback : long ($callback : Net::SSLeay::Callback::TlsextServername);
  
  native method DESTROY : void ();
  
  private static method GET_INSTANCE : Net::SSLeay::SSL_CTX ($address : string) {
    
    Fn->defer(method : void () {
      $MUTEX->reader_unlock;
    });
    
    $MUTEX->reader_lock;
    
    my $self = (Net::SSLeay::SSL_CTX)$INSTANCES_H->get($address);
    
    return $self;
  }
  
  private static method INIT_INSTANCE : void ($address : string, $self : Net::SSLeay::SSL_CTX) {
    
    Fn->defer(method : void () {
      $MUTEX->unlock;
    });
    
    $MUTEX->lock;
    
    unless ($INSTANCES_H->exists($address)) {
      $INSTANCES_H->set($address, $self);
      
      $INSTANCES_H->weaken($address);
    }
  }
  
  private static method DELETE_INSTANCE : Net::SSLeay::SSL_CTX ($address : string) {
    
    Fn->defer(method : void () {
      $MUTEX->unlock;
    });
    
    $MUTEX->lock;
    
    my $deleted = (Net::SSLeay::SSL_CTX)$INSTANCES_H->delete($address);
    
    return $deleted;
  }
  
}