require "formula"
require "csv"

class Descriptions
  CACHE_FILE = HOMEBREW_CACHE + "desc_cache"

  def self.cache
    @cache || self.load_cache
  end

  # If the cache file exists, load it into, and return, a hash; otherwise,
  # return nil.
  def self.load_cache
    if CACHE_FILE.exist?
      @cache = {}
      CSV.foreach(CACHE_FILE) { |name, desc| @cache[name] = desc }
      @cache
    end
  end

  # Write the cache to disk after ensuring the existence of the containing
  # directory.
  def self.save_cache
    HOMEBREW_CACHE.mkpath
    CSV.open(CACHE_FILE, 'w') do |csv|
      @cache.each do |name, desc|
        csv << [name, desc]
      end
    end
  end

  # Create a hash mapping all formulae to their descriptions;
  # save it for future use.
  def self.generate_cache
    @cache = {}
    Formula.map do |f|
      @cache[f.full_name] = f.desc
    end
    self.save_cache
  end

  # Return true if the cache exists, and neither Homebrew nor any of the Taps
  # repos were updated more recently than it was.
  def self.cache_fresh?
    if CACHE_FILE.exist?
      cache_date = File.mtime(CACHE_FILE)

      ref_master = ".git/refs/heads/master"
      master = HOMEBREW_REPOSITORY/ref_master

      last_update = (master.exist? ? File.mtime(master) : Time.at(0))

      Dir.glob(HOMEBREW_LIBRARY/"Taps/**"/ref_master).each do |repo|
        repo_mtime = File.mtime(repo)
        last_update = repo_mtime if repo_mtime > last_update
      end
      last_update <= cache_date
    end
  end

  # Create the cache if it doesn't already exist.
  def self.ensure_cache
    self.generate_cache unless self.cache_fresh? && self.cache
  end

  # Take a {Report}, as generated by cmd/update.rb.
  # Unless the cache file exists, do nothing.
  # If it does exist, but the Report is empty, just touch the cache file.
  # Otherwise, use the report to update the cache.
  def self.update_cache(report)
    if CACHE_FILE.exist?
      if report.empty?
        FileUtils.touch CACHE_FILE
      else
        renamings = report.select_formula(:R)
        alterations = report.select_formula(:A) + report.select_formula(:M) +
                      renamings.map(&:last)
        self.cache_formulae(alterations, :save => false)
        self.uncache_formulae(report.select_formula(:D) +
                              renamings.map(&:first))
      end
    end
  end

  # Given an array of formula names, add them and their descriptions to the
  # cache. Save the updated cache to disk, unless explicitly told not to.
  def self.cache_formulae(formula_names, options = { :save => true })
    if self.cache
      formula_names.each { |name| @cache[name] = Formula[name].desc }
      self.save_cache if options[:save]
    end
  end

  # Given an array of formula names, remove them and their descriptions from
  # the cache. Save the updated cache to disk, unless explicitly told not to.
  def self.uncache_formulae(formula_names, options = { :save => true })
    if self.cache
      formula_names.each { |name| @cache.delete(name) }
      self.save_cache if options[:save]
    end
  end

  # Given an array of formula names, return a {Descriptions} object mapping
  # those names to their descriptions.
  def self.named(names)
    self.ensure_cache

    results = {}
    unless names.empty?
      results = names.inject({}) do |accum, name|
        accum[name] = @cache[name]
        accum
      end
    end

    new(results)
  end

  # Given a regex, find all formulae whose specified fields contain a match.
  def self.search(regex, field = :either)
    self.ensure_cache

    results = case field
    when :name
      @cache.select { |name, _| name =~ regex }
    when :desc
      @cache.select { |_, desc| desc =~ regex }
    when :either
      @cache.select { |name, desc| (name =~ regex) || (desc =~ regex) }
    end

    results = Hash[results] if RUBY_VERSION <= "1.8.7"

    new(results)
  end

  # Create an actual instance.
  def initialize(descriptions)
    @descriptions = descriptions
  end

  # Take search results -- a hash mapping formula names to descriptions -- and
  # print them.
  def print
    blank = "#{Tty.yellow}[no description]#{Tty.reset}"
    @descriptions.keys.sort.each do |name|
      description = @descriptions[name] || blank
      puts "#{Tty.white}#{name}:#{Tty.reset} #{description}"
    end
  end
end
