require 'openssl'

class Signer

  # Digest algorithms supported "out of the box"
  DIGEST_ALGORITHMS = {
    # SHA 1
    sha1: {
      name: 'SHA1',
      id: 'http://www.w3.org/2000/09/xmldsig#sha1',
      digester: lambda { OpenSSL::Digest::SHA1.new },
    },
    # SHA 256
    sha256: {
      name: 'SHA256',
      id: 'http://www.w3.org/2001/04/xmlenc#sha256',
      digester: lambda { OpenSSL::Digest::SHA256.new },
    },
    # SHA512
    sha512: {
      name: 'SHA512',
      id: 'http://www.w3.org/2001/04/xmlenc#sha512',
      digester: lambda { OpenSSL::Digest::SHA512.new },
    },
    # GOST R 34-11 94
    gostr3411: {
      name: 'GOST R 34.11-94',
      id: 'http://www.w3.org/2001/04/xmldsig-more#gostr3411',
      digester: lambda { OpenSSL::Digest.new('md_gost94') },
    },
    # GOST R 34-11 2012 256 bit
    gostr34112012_256: {
      name: 'GOST R 34.11-2012 256',
      id: 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256',
      digester: lambda { begin OpenSSL::Digest.new('streebog256') rescue OpenSSL::Digest.new('md_gost12_256') end },
    },
  }.freeze

  # Class that holds +OpenSSL::Digest+ instance with some meta information for digesting in XML.
  class Digester

    # You may pass either a one of +:sha1+, +:sha256+ or +:gostr3411+ symbols
    # or +Hash+ with keys +:id+ with a string, which will denote algorithm in XML Reference tag
    # and +:digester+ with instance of class with interface compatible with +OpenSSL::Digest+ class.
    def initialize(algorithm)
      if algorithm.kind_of? Symbol
        @digest_info = DIGEST_ALGORITHMS[algorithm].dup
        @digest_info[:digester] = @digest_info[:digester].call
        @symbol = algorithm
      else
        @digest_info = algorithm
      end
    end

    attr_reader :symbol

    # Digest
    def digest(message)
      self.digester.digest(message)
    end

    alias call digest

    # Returns +OpenSSL::Digest+ (or derived class) instance
    def digester
      @digest_info[:digester].reset
    end

    # Human-friendly name
    def digest_name
      @digest_info[:name]
    end

    # XML-friendly name (for specifying in XML +DigestMethod+ node +Algorithm+ attribute)
    def digest_id
      @digest_info[:id]
    end
  end
end
