module LiterateRandomizer

# The main class. Each instance has its own random number generator and can work against its own training source-material.
class Randomizer
  # The default punctuation distribution. Punctuation is pulled randomly from this array. It can contain any string.
  DEFAULT_PUNCTUATION_DISTRIBUTION = %w{. . . . . . . . . . . . . . . . ? !}

  # LiterateRandomizer prefers to not end sentences with words that match the following regexp:
  PREPOSITION_REGEX = /^(had|the|to|or|and|a|in|that|it|if|of|is|was|for|on|as|an|your|our|my|per|until)$/


  # The source of all random values. Must implement: #rand(limit)
  #
  # Default: Random.new()
  attr_accessor :randomizer

  # To end setences, one of the strings in this array is selected at random (uniform-distribution)
  #
  # Default: DEFAULT_PUNCTUATION_DISTRIBUTION
  attr_accessor :punctuation_distribution

  # an instance of SourceParser attached to the source_material
  attr_reader :source_parser

  # The random-generator model
  attr_reader :model

  private

  # Check to see if the sentence ends in a PREPOSITION_REGEX word.
  # If so, add more words up to max-words until it does.
  def extend_trailing_preposition(max_words,words)
    while words.length < max_words && words[-1] && words[-1][PREPOSITION_REGEX]
      words << model.next_word(words[-1],randomizer)
    end
    words
  end

  public
  # Initialize a new instance. Each Markov randomizer instance can run against its own source_material.
  #
  # Options:
  #
  # * :source_material => string OR
  # * :source_material_file => filename
  # * :punctuation_distribution => DEFAULT_PUNCTUATION_DISTRIBUTION
  #   punctiation is randomly selected from this array
  #
  # Advanced options: (primiarilly for testing)
  #
  # * :randomizer => Random.new # must respond to #rand(limit)
  # * :source_parser => SourceParser.new options
  # * :model => MarkovModel.new :source_parser => source_parser
  def initialize(options={})
    @init_options = options
    @randomizer = randomizer || Random.new
    @punctuation_distribution = options[:punctuation_distribution] || DEFAULT_PUNCTUATION_DISTRIBUTION
    @source_parser = options[:source_parser] || SourceParser.new(options)
    @model = options[:model] || MarkovModel.new(:source_parser => source_parser)
  end

  # Returns a quick summary of the instance.
  def inspect
    "#<#{self.class}: #{model.words.length} words, #{model.markov_chains.length} word-chains, #{model.first_words.length} first_words>"
  end

  # return a random word
  def word
    @cached_word_keys ||= model.words.keys
    @cached_word_keys[rand(@cached_word_keys.length)]
  end

  # return a random first word of a sentence
  def first_word
    @cached_first_word_keys ||= model.first_words.keys
    @cached_first_word_keys[rand(@cached_first_word_keys.length)]
  end

  # return a random number generated by randomizer
  def rand(limit=nil)
    @randomizer.rand(limit)
  end

  # return a random end-sentence string from punctuation_distribution
  def punctuation
    @punctuation_distribution[rand(@punctuation_distribution.length)]
  end

  # return a random sentence
  #
  # Options:
  #
  # * :first_word => nil - the start word
  # * :words => range or int - number of words in sentence
  # * :punctuation => nil - punction to end the sentence with (nil == randomly selected from punctuation_distribution)
  def sentence(options={})
    word = options[:first_word] || self.first_word
    num_words_option = options[:words] || (3..15)
    count = Util.rand_count(num_words_option,randomizer)
    punctuation = options[:punctuation] || self.punctuation

    words = count.times.collect do
      word.tap {word = model.next_word(word,randomizer)}
    end.compact

    words = extend_trailing_preposition(Util.max(num_words_option), words)

    Util.capitalize words.compact.join(" ") + punctuation
  end

  # return a random paragraph
  #
  # Options:
  #
  # * :first_word => nil - the first word of the paragraph
  # * :words => range or int - number of words in sentence
  # * :sentences => range or int - number of sentences in paragraph
  # * :punctuation => nil - punction to end the paragraph with (nil == randomly selected from punctuation_distribution)
  def paragraph(options={})
    count = Util.rand_count(options[:sentences] || (5..15),randomizer)

    count.times.collect do |i|
      op = options.clone
      op.delete :punctuation unless i==count-1
      op.delete :first_word unless i==0
      sentence op
    end.join(" ")
  end

  # return random paragraphs
  #
  # Options:
  #
  # * :first_word => nil - the first word of the paragraph
  # * :words => range or int - number of words in sentence
  # * :sentences => range or int - number of sentences in paragraph
  # * :paragraphs => range or int - number of paragraphs in paragraph
  # * :join => "\n\n" - join the paragraphs. if :join => false, returns an array of the paragraphs
  # * :punctuation => nil - punction to end the paragraph with (nil == randomly selected from punctuation_distribution)
  def paragraphs(options={})
    count = Util.rand_count(options[:paragraphs] || (3..5),randomizer)
    join_str = options[:join]

    res = count.times.collect do |i|
      op = options.clone
      op.delete :punctuation unless i==count-1
      op.delete :first_word unless i==0
      paragraph op
    end

    join_str!=false ? res.join(join_str || "\n\n") : res
  end

end
end
