begin
  require "bcrypt"
rescue LoadError
end

module Authlogic
  module CryptoProviders
    # = Bcrypt
    #
    # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
    # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
    # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
    # of the Sha algorithms. I did some benchmarking to save you some time with your decision:
    #
    #   require "bcrypt"
    #   require "digest"
    #   require "benchmark"
    #
    #   Benchmark.bm(18) do |x|
    #     x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
    #     x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
    #     x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
    #     x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
    #   end
    #
    #                           user     system      total        real
    #   BCrypt (cost = 10): 10.780000   0.060000  10.840000 ( 11.100289)
    #   BCrypt (cost = 2):  0.180000   0.000000   0.180000 (  0.181914)
    #   Sha512:             0.000000   0.000000   0.000000 (  0.000829)
    #   Sha1:               0.000000   0.000000   0.000000 (  0.000395)
    #
    # You can play around with the cost to get that perfect balance between performance and security.
    #
    # Decided BCrypt is for you? Just insall the bcrypt gem:
    #
    #   gem install bcrypt-ruby
    #
    # Tell acts_as_authentic to use it:
    #
    #   acts_as_authentic :crypto_provider => Authlogic::CryptoProviders::BCrypt
    #
    # You are good to go!
    class BCrypt
      class << self
        # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
        # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
        def cost
          @cost ||= 10
        end
        attr_writer :cost
        
        # Creates a BCrypt hash for the password passed.
        def encrypt(*tokens)
          ::BCrypt::Password.create(join_tokens(tokens), :cost => cost)
        end
        
        # Does the hash match the tokens? Uses the same tokens that were used to encrypt.
        def matches?(hash, *tokens)
          hash = new_from_hash(hash)
          return false if hash.blank?
          hash == join_tokens(tokens)
        end
        
        # This method is used as a flag to tell Authlogic to "resave" the password upon a successful login, using the new cost
        def cost_matches?(hash)
          hash = new_from_hash(hash)
          if hash.blank?
            false
          else
            hash.cost == cost
          end
        end
        
        private
          def join_tokens(tokens)
            tokens.flatten.join
          end
          
          def new_from_hash(hash)
            begin
              ::BCrypt::Password.new(hash)
            rescue ::BCrypt::Errors::InvalidHash
              return nil
            end
          end
      end
    end
  end
end