require 'openssl'
require 'r509/exceptions'
require 'r509/io_helpers'

module R509
    # class for handling SPKAC/SPKI requests (typically generated by the <keygen> tag
    class Spki
        include R509::IOHelpers

        attr_reader :subject, :spki, :san_names
        # @option opts [String,OpenSSL::Netscape::SPKI] :spki the spki you want to parse
        # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
        # @example [['CN','langui.sh'],['ST','Illinois'],['L','Chicago'],['C','US'],['emailAddress','ca@langui.sh']]
        # you can also pass OIDs (see tests)
        # @option opts [Array] :san_names array of SAN names
        def initialize(opts={})
            if not opts.kind_of?(Hash)
                raise ArgumentError, 'Must provide a hash of options'
            end
            if opts.has_key?(:spki) and not opts.has_key?(:subject)
                raise ArgumentError, "Must provide both spki and subject"
            end
            if opts.has_key?(:san_names) and not opts[:san_names].kind_of?(Array)
                raise ArgumentError, "if san_names are provided they must be in an Array"
            end
            @spki = OpenSSL::Netscape::SPKI.new(opts[:spki].sub("SPKAC=",""))
            @subject = R509::Subject.new(opts[:subject])
            @san_names = opts[:san_names] || []
        end

        # @return [OpenSSL::PKey::RSA] public key
        def public_key
            @spki.public_key
        end

        # Converts the SPKI into the PEM format
        #
        # @return [String] the SPKI converted into PEM format.
        def to_pem
            @spki.to_pem
        end

        alias :to_s :to_pem

        # Converts the SPKI into the DER format
        #
        # @return [String] the SPKI converted into DER format.
        def to_der
            @spki.to_der
        end

        # Writes the SPKI into the PEM format
        #
        # @param [String, #write] filename_or_io Either a string of the path for
        #  the file that you'd like to write, or an IO-like object.
        def write_pem(filename_or_io)
            write_data(filename_or_io, @spki.to_pem)
        end

        # Writes the SPKI into the DER format
        #
        # @param [String, #write] filename_or_io Either a string of the path for
        #  the file that you'd like to write, or an IO-like object.
        def write_der(filename_or_io)
            write_data(filename_or_io, @spki.to_der)
        end

        # Returns whether the public key is RSA
        #
        # @return [Boolean] true if the public key is RSA, false otherwise
        def rsa?
            @spki.public_key.kind_of?(OpenSSL::PKey::RSA)
        end

        # Returns whether the public key is DSA
        #
        # @return [Boolean] true if the public key is DSA, false otherwise
        def dsa?
            @spki.public_key.kind_of?(OpenSSL::PKey::DSA)
        end

        # Returns the bit strength of the key used to create the SPKI
        # @return [Integer] the integer bit strength.
        def bit_strength
            if self.rsa?
                return @spki.public_key.n.num_bits
            elsif self.dsa?
                return @spki.public_key.p.num_bits
            end
        end

        # Returns key algorithm (RSA/DSA)
        #
        # @return [String] value of the key algorithm. RSA or DSA
        def key_algorithm
            if @spki.public_key.kind_of? OpenSSL::PKey::RSA then
                'RSA'
            elsif @spki.public_key.kind_of? OpenSSL::PKey::DSA then
                'DSA'
            end
        end

        # Returns a hash structure you can pass to the Ca
        # You will want to call this method if you intend to alter the values
        # and then pass them to the Ca class.
        #
        # @return [Hash] :subject and :san_names you can pass to Ca
        def to_hash
            { :subject => @subject.dup , :san_names => @san_names.dup }
        end
    end
end