module Picky
  # Module that handles object pool behaviour
  # for you.
  #
  module Pool
    class << self
      # Installs itself on a few internal classes.
      #
      # Note: If you need to run two consecutive queries,
      # this can't be used.
      #
      # Note: You need to call Picky::Pool.release_all after each query
      # (or after a few queries).
      #
      def install
        Query::Token.extend self
        Query::Tokens.extend self
        Query::Combination.extend self
        Query::Combinations.extend self
        Query::Allocation.extend self
        Query::Allocations.extend self
      end

      # Initialise/Reset the pool.
      #
      def clear
        @pools = Set.new
      end

      # Add a pooled class to the managed pools.
      #
      def add(klass)
        @pools << klass
      end

      # Releases all obtained objects.
      #
      def release_all
        @pools.each(&:release_all)
      end
    end

    # Reset the pool.
    #
    clear

    def self.extended(klass)
      add klass

      class << klass
        # Initialise/Reset the class pool.
        #
        def clear
          @__free__ = []
          @__used__ = []
        end

        # Obtain creates a new reference if there is no free one
        # and uses an existing one if there is.
        #
        def new *args, &block
          unless (reference = @__free__.shift)
            reference = allocate
          end
          reference.send :initialize, *args, &block
          @__used__ << reference
          reference
        end

        # Releasing an instance adds it to the free pool.
        # (And removes it from the used pool)
        #
        def release(instance)
          @__free__ << instance

          # NOTE: This is relatively fast as there are often only
          # few instances in the used pool.
          #
          @__used__.delete instance
        end

        # After you have called release all, you can't externally
        # use any reference that has formerly been obtained
        # anymore.
        #
        def release_all
          @__used__.each { |used| @__free__ << used } # +0 Array per release_all
          # @__used__ += @__free__ # +1 Array per release_all
          @__used__.clear
        end

        # How many obtainable objects are there?
        #
        def free_size
          @__free__.size
        end

        # How many used objects are there?
        #
        def used_size
          @__used__.size
        end
      end

      # Initialize the pool.
      #
      klass.clear

      # Pooled objects can be released using
      #   object.release
      #
      # Note: Do not use the object in any form after the release.
      #
      klass.send :define_method, :release do
        klass.release self
      end
    end
  end
end
