# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Net::SSLeay {
  version "0.044";
  
  use Net::SSLeay::Constant as SSL;
  use Net::SSLeay::SSL_CTX;
  use Net::SSLeay::X509;
  use Net::SSLeay::X509_NAME;
  
  use Net::SSLeay::Error;
  use Net::SSLeay::Error::SSL_ERROR_WANT_READ;
  use Net::SSLeay::Error::SSL_ERROR_WANT_WRITE;
  
  use Net::SSLeay::Callback::Msg;
  
  # Class Variables
  our $INSTANCES_H : Hash of Net::SSLeay;
  
  our $MUTEX : Sync::Mutex;
  
  INIT {
    $INSTANCES_H = Hash->new;
    
    $MUTEX = Sync::Mutex->new;
  }
  
  # Fields
  has operation_error : ro int;
  
  has msg_callback : ro Net::SSLeay::Callback::Msg;
  
  # Class Methods
  native static method new : Net::SSLeay ($ssl_ctx : Net::SSLeay::SSL_CTX);
  
  private static method new_with_pointer : Net::SSLeay ($pointer : Address, $options : object[] = undef) {
    
    my $self = new Net::SSLeay;
    
    Fn->set_pointer($self, $pointer);
    
    $self->init($options);
    
    return $self;
  }
  
  native static method alert_desc_string_long : string ($value : int);
  
  native static method load_client_CA_file : Net::SSLeay::X509_NAME[] ($file : string);
  
  native static method select_next_proto : int ($out_ref : string[], $outlen_ref : byte*, $server : string, $server_len : int, $client : string, $client_len : int);
  
  # Instance Methods
  protected method init : void ($options : object[] = undef) {
    
    $self->_init_native;
  }
  
  native private method _init_native : void ($objects : object[] = undef);
  
  native method version : int ();
  
  native method get_version : string ();
  
  native method get_mode : long ();
  
  native method set_mode : long ($mode : long);
  
  native method clear_mode : long ($mode : long);
  
  native method set_tlsext_host_name : int ($name : string);
  
  native method get_servername : string ($type : int);
  
  native method get_SSL_CTX : Net::SSLeay::SSL_CTX ();
  
  native method set_SSL_CTX : void ($ssl_ctx : Net::SSLeay::SSL_CTX);
  
  native method set_fd : int ($fd : int);
  
  native method connect : int ();
  
  native method accept : int ();
  
  native method read : int ($buf : mutable string, $num : int = -1, $offset : int = 0);
  
  native method write : int ($buf : string, $num : int = -1, $offset : int = 0);
  
  native method shutdown : int ();
  
  native method get_shutdown : int ();
  
  native method get_cipher : string ();
  
  native method get_certificate : Net::SSLeay::X509 ();
  
  native method get_peer_certificate : Net::SSLeay::X509 ();
  
  native method get_peer_cert_chain : Net::SSLeay::X509[] ();
  
  native method get0_alpn_selected : void ($data_ref : string[], $len_ref : int*);
  
  method get0_alpn_selected_return_string : string () {
    
    my $data_ref = new string[1];
    my $len = -1;
    
    $self->get0_alpn_selected($data_ref, \$len);
    
    my $protocol = (string)undef;
    if ($data_ref->[0]) {
      $protocol = $data_ref->[0];
      Fn->shorten((mutable string)$protocol, $len);
    }
    
    return $protocol;
  }
  
  method dump_peer_certificate : string () {
    
    my $cert = $self->get_peer_certificate;
    
    unless ($cert) {
      die "The return value of get_peer_certificate method must be defined.";
    }
    
    my $subject_name = $cert->get_subject_name->oneline;
    
    my $issuer_name = $cert->get_issuer_name->oneline;
    
    my $dump = "Subject Name: $subject_name\nIssuer  Name: $issuer_name\n";
    
    return $dump;
  }
  
  native method set_msg_callback : void ($cb : Net::SSLeay::Callback::Msg);
  
  native method DESTROY : void ();
  
  private static method GET_INSTANCE : Net::SSLeay ($address : string) {
    
    Fn->defer(method : void () {
      $MUTEX->reader_unlock;
    });
    
    $MUTEX->reader_lock;
    
    my $self = (Net::SSLeay)$INSTANCES_H->get($address);
    
    return $self;
  }
  
  private static method INIT_INSTANCE : void ($address : string, $self : Net::SSLeay) {
    
    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 ($address : string) {
    
    Fn->defer(method : void () {
      $MUTEX->unlock;
    });
    
    $MUTEX->lock;
    
    my $deleted = (Net::SSLeay)$INSTANCES_H->delete($address);
    
    return $deleted;
  }
  
}