require 'generator'

module Caring
  module Utilities
    module UrlUtils
      # Makes a string safe for urls
      # Options:
      #   :replacements - a Hash of a replacement string to a regex that should match for replacement
      #   :char - when :replacements is not provided, this is the string that will be used to replace unsafe characters. defaults to '-'
      #   :collapse - set to false if multiple, consecutive unsafe characters should not be replaced with only a single instance of :char.
      #               defaults to true. only matters if you don't provide :replacements. If you do, you'll need to write your regex such
      #               that it matches multiple characters.
      #   :strip_endings - remove replacement characters from the beginning and end of the safe string. Defaults to true.
      #   :downcase - make the input string lower case. Defautls to true.
      def url_safe(s, options = {})
        return s if s.blank?
        s = s.downcase if options.fetch(:downcase, true)
        collapse = options.fetch(:collapse, true)
        default_regex = /[^'a-zA-Z0-9]#{"+" if collapse}/
        s.gsub! /(&amp;)|&/, 'and'
        replacements = options[:replacements] || { options.fetch(:char,"-") => default_regex, "" => /'#{"+" if collapse}/}
        replacements.each do |replacement, regex|
          s = s.gsub(regex,replacement)
        end
        if options.fetch(:strip_endings,true)
          replacement_strings = replacements.keys.map{|k| "(#{Regexp.escape(k)})" unless k.blank?}.compact.join("|")
          s = s.gsub(/(^#{replacement_strings})|(#{replacement_strings}$)/,"")
        end
        return s
      end
      
      # Generate integers
      # Options:
      #   :start => 1, The integer to start with
      #   :end => nil, the last integer to generate, when nil this becomes an infinite sequence
      #   :increment => 1, the amount to add for each iteration
      def int_generator(options = {})
        start = options.fetch(:start,1)
        last = options[:end]
        increment = options.fetch(:increment, 1)
        raise ArgumentError if increment == 0
        raise ArgumentError if last && (increment > 0 && start > last) || (increment < 0 && start < last)
        Generator.new do |g|
          i = start
          loop do
            g.yield i
            return if !last.nil? && (increment > 0 && i >= last) || (increment < 0 && i <= last)
            i = i + increment
          end
        end
      end
      
      # accepts a block that will be passed a candidate string and should return true if it is unique.
      # Options:
      # => :separator => "-", a string that will be injected between the base string and the uniqifier
      # => :endings => generator, a Generator that provides endings to be placed at the end of the base.
      #                           defaults to the set of positive integers starting with two.
      def uniquify(base, options = {})
        sep = options.fetch(:separator, "-")
        endings = options[:endings] || int_generator(:start => 2)
        return base if yield base
        while endings.next? do
          candidate = base+sep+endings.next.to_s
          return candidate if yield candidate
        end
        raise ArgumentError.new("No unique construction found for \"#{base}\"")
      end
    end
  end
end
