# See Date::Memoize.

require 'date'
require 'date/performance'

class Date #:nodoc:

  # Adds memoization to Date. This can speed things up significantly in cases where a lot 
  # of the same Date objects are created.
  module Memoize

    # Memoized version of Date::strptime.
    def strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
      @__memoized_strptime_dates[ [ str, fmt, sg ] ]
    end

    # Memoized version Date::parse.
    def parse(str='-4712-01-01', comp=false, sg=ITALY)
      @__memoized_parse_dates[ [ str, comp, sg ] ]
    end

    # Memoized version of Date::civil.
    def civil(y=-4712, m=1, d=1, sg=ITALY)
      @__memoized_civil_dates[ [ y, m, d, sg ] ]
    end

    alias_method :new, :civil

  public

    # The methods we'll be replacing on the Date singleton.
    def self.methods_replaced
      [ :new, :civil, :strptime, :parse ]
    end

    # Overridden to move the existing methods out of the way before copying this module's
    # methods.
    def self.extend_object(base)
      singleton = (class<<base;self;end)
      methods_replaced.each do |method|
        singleton.send :alias_method, "#{method}_without_memoization", method
        singleton.send :remove_method, method
      end
      base.send :instance_variable_set, :@__memoized_civil_dates, 
        Hash.new{|h,key| h[key]=Date.new_without_memoization(*key)}
      base.send :instance_variable_set, :@__memoized_strptime_dates, 
        Hash.new{|h,key| h[key]=Date.strptime_without_memoization(*key)}
      base.send :instance_variable_set, :@__memoized_parse_dates, 
        Hash.new{|h,key| h[key]=Date.parse_without_memoization(*key)}
      super
    end

    # Removes memoization methods from singleton of the class provided.
    def self.unextend_object(base)
      singleton = (class<<base;self;end)
      methods_replaced.each do |method|
        singleton.send :alias_method,  method, "#{method}_without_memoization"
        singleton.send :remove_method, "#{method}_without_memoization"
      end
      base.send :remove_instance_variable, :@__memoized_civil_dates
      base.send :remove_instance_variable, :@__memoized_strptime_dates
      base.send :remove_instance_variable, :@__memoized_parse_dates
      base
    end

    # Is Date memoization currently installed and active?
    def self.installed?
      Date.respond_to? :civil_without_memoization
    end

    # Extend the Date class with memoized versions of +new+ and +civil+ but only if 
    # memoization has not yet been installed.
    def self.install!
      Date.extend self unless installed?
    end

    # Remove memoized methods and free up memo cache. This method is idempotent.
    def self.uninstall!
      unextend_object Date if installed?
    end

  end

  Memoize.install!

end