require 'r509/cert/extensions/base'
require 'r509/cert/extensions/validation_mixin'

module R509
  class Cert
    module Extensions
      # RFC 5280 Description (see: http://www.ietf.org/rfc/rfc5280.txt)
      #
      # The CRL distribution points extension identifies how CRL information
      # is obtained.  The extension SHOULD be non-critical, but this profile
      # RECOMMENDS support for this extension by CAs and applications.
      # Further discussion of CRL management is contained in Section 5.
      #
      # You can use this extension to parse an existing extension for easy access
      # to the contents or create a new one.
      class CRLDistributionPoints < OpenSSL::X509::Extension
        include R509::Cert::Extensions::ValidationMixin
        include R509::Cert::Extensions::GeneralNamesMixin

        # friendly name for CDP OID
        OID = "crlDistributionPoints"
        Extensions.register_class(self)

        # This method takes a hash or an existing Extension object to parse.
        #
        # @option arg :value [Array,R509::ASN1::GeneralNames] Array of hashes (see examples) or GeneralNames object
        # @option arg :critical [Boolean] (false)
        # @example
        #   R509::Cert::Extensions::CRLDistributionPoints.new(
        #     :value => [
        #       { :type => "URI", :value => "http://crl.domain.com/test.crl" }
        #   )
        # @example
        #   name = R509::ASN1::GeneralName.new(:type => "URI", :value => "http://crl.domain.com/test.crl")
        #   R509::Cert::Extensions::CRLDistributionPoints.new(
        #     :value => [name]
        #   )
        def initialize(arg)
          unless R509::Cert::Extensions.is_extension?(arg)
            arg = build_extension(arg)
          end

          super(arg)
          parse_extension
        end

        # @return [Hash]
        def to_h
          {
            :critical => self.critical?,
            :value => R509::Cert::Extensions.names_to_h(@general_names.names)
          }
        end

        # @return [YAML]
        def to_yaml
          self.to_h.to_yaml
        end

        private

        def parse_extension
          @general_names = R509::ASN1::GeneralNames.new
          data = R509::ASN1.get_extension_payload(self)
          data.entries.each do |distribution_point|
            #   DistributionPoint ::= SEQUENCE {
            #        distributionPoint       [0]     DistributionPointName OPTIONAL,
            #        reasons                 [1]     ReasonFlags OPTIONAL,
            #        cRLIssuer               [2]     GeneralNames OPTIONAL }
            #   DistributionPointName ::= CHOICE {
            #        fullName                [0]     GeneralNames,
            #        nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
            # We're only going to handle DistributionPointName [0] for now
            # so grab entries[0] and then get the fullName with value[0]
            # and the value of that ASN1Data with value[0] again
            @general_names.add_item(distribution_point.entries[0].value[0].value[0])
          end
        end

        def build_extension(arg)
          validate_crl_distribution_points(arg)
          validate_location('crl_distribution_points', arg[:value])
          serialize = R509::ASN1::GeneralNames.new(arg[:value]).serialize_names
          ef = OpenSSL::X509::ExtensionFactory.new
          ef.config = OpenSSL::Config.parse(serialize[:conf])
          critical = R509::Cert::Extensions.calculate_critical(arg[:critical], false)
          ef.create_extension("crlDistributionPoints", serialize[:extension_string], critical)
        end

        def validate_crl_distribution_points(arg)
          raise ArgumentError, "You must pass a hash with a :value key" unless arg.is_a?(Hash)
        end
      end
    end
  end
end
