# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Net::SSLeay::SSL_CTX : pointer {
  
  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 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 ref_output_for_set_alpn_select_cb : string;
  
  has verify_callback : Net::SSLeay::Callback::Verify;
  
  has verify_callback_arg : object;
  
  has default_passwd_cb : Net::SSLeay::Callback::PemPassword;
  
  has default_passwd_cb_arg : object;
  
  has alpn_select_cb : Net::SSLeay::Callback::AlpnSelect;
  
  has alpn_select_cb_arg : object;
  
  has tlsext_servername_callback : Net::SSLeay::Callback::TlsextServername;
  
  has tlsext_servername_callback_arg : object;
  
  # 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);
    
    return $self;
  }
  
  protected method init : void ($options : 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 : byte[], $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 add_extra_chain_cert : long ($x509 : Net::SSLeay::X509);
  
  native method set_verify : void ($mode : int, $verify_callback : Net::SSLeay::Callback::Verify = undef, $arg : object = undef);
  
  native method set_default_passwd_cb : void ($cb : Net::SSLeay::Callback::PemPassword, $arg : object = undef);
  
  native method set_alpn_select_cb : void ($cb : Net::SSLeay::Callback::AlpnSelect, $arg : object);
  
  native method set_alpn_select_cb_with_protocols : void ($protocols : string[]);
  
  native method set_tlsext_servername_callback : long ($cb : Net::SSLeay::Callback::TlsextServername, $arg : object = undef);
  
  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;
  }
  
}