require 'openssl'

module Sec
  SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION = 0

  attach_function 'SecCertificateCopyPublicKey', [:pointer, :pointer], :osstatus
  attach_function 'SecCertificateCopyData', [:pointer], :pointer

  attach_variable 'kSecAttrCertificateType', :pointer
  attach_variable 'kSecAttrCertificateEncoding', :pointer
  attach_variable 'kSecAttrSubject', :pointer
  attach_variable 'kSecAttrIssuer', :pointer
  attach_variable 'kSecAttrSerialNumber', :pointer
  attach_variable 'kSecAttrSubjectKeyID', :pointer
  attach_variable 'kSecAttrPublicKeyHash', :pointer
end

module Keychain
  class Certificate < Sec::Base
    register_type 'SecCertificate'

    ATTR_MAP = {CF::Base.typecast(Sec::kSecAttrAccessGroup) => :access_group,
                CF::Base.typecast(Sec::kSecAttrCertificateType) => :certificate_type,
                CF::Base.typecast(Sec::kSecAttrCertificateEncoding) => :certificate_encoding,
                CF::Base.typecast(Sec::kSecAttrLabel) => :label,
                CF::Base.typecast(Sec::kSecAttrSubject) => :subject,
                CF::Base.typecast(Sec::kSecAttrIssuer) => :issuer,
                CF::Base.typecast(Sec::kSecAttrSerialNumber) => :serial_number,
                CF::Base.typecast(Sec::kSecAttrSubjectKeyID) => :subject_key_id,
                CF::Base.typecast(Sec::kSecAttrPublicKeyHash) => :public_key_hash}

    ATTR_MAP[CF::Base.typecast(Sec::kSecAttrAccessible)] = :accessible if defined?(Sec::kSecAttrAccessible)
    ATTR_MAP[CF::Base.typecast(Sec::kSecAttrAccessControl)] = :access_control if defined?(Sec::kSecAttrAccessControl)

    INVERSE_ATTR_MAP = ATTR_MAP.invert
    define_attributes(ATTR_MAP)

    def klass
      Sec::Classes::CERTIFICATE.to_ruby
    end

    def public_key
      key_ref = FFI::MemoryPointer.new(:pointer)
      status = Sec.SecCertificateCopyPublicKey(self, key_ref)
      Sec.check_osstatus(status)

      Key.new(key_ref.read_pointer).release_on_gc
    end

    def x509
      data_ptr = Sec.SecCertificateCopyData(self)
      data = CF::Data.new(data_ptr)

      result = OpenSSL::X509::Certificate.new(data.to_s)
      data.release
      result
    end
  end
end