#!/usr/local/bin/ruby
# usage:
#
#   ri  name...
#
# where name can be 
#
#   Class | Class::method | Class#method | Class.method | method
#
# All names may be abbreviated to their minimum unbiguous form. If a name
# _is_ ambiguous, all valid options will be listed.
#
# The form '.' method matches either class or instance methods, while 
# #method matches only instance and ::method matches only class methods.

require 'rdoc/ri/ri_paths'
require 'rdoc/ri/ri_cache'
require 'rdoc/ri/ri_util'
require 'rdoc/ri/ri_reader'
require 'rdoc/ri/ri_formatter'
require 'rdoc/ri/ri_options'

######################################################################

def display_usage
  RI::Options::OptionList.usage(short_form=true)
#  File.open(__FILE__) do |f|
#    f.gets
#    puts $1 while (f.gets =~ /^# ?(.*)/)
#  end
#  exit
end


######################################################################

class  RiDisplay

  def initialize
    @options = RI::Options.instance
    @options.parse
    paths = RI::Paths::PATH
    if paths.empty?
      $stderr.puts "No ri documentation found in:"
      [ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR].each do |d|
        $stderr.puts "     #{d}"
      end
      $stderr.puts "\nWas rdoc run to create documentation?"
      exit 1                   
    end
    @ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
    @formatter = @options.formatter.new(@options, "     ")
  end    
  
  
  ######################################################################

  def setup_pager
    require 'tempfile'

    @save_stdout = STDOUT.clone
    STDOUT.reopen(Tempfile.new("ri_"))
  end

  ######################################################################

  def page_output
    path = STDOUT.path
    STDOUT.reopen(@save_stdout)
    @save_stdout = nil
    paged = false
    for pager in [ ENV['PAGER'], "less", "more <", 'pager' ].compact.uniq
      if system("#{pager} #{path}")
        paged = true
        break
      end
    end
    if !paged
      @options.use_stdout = true
      puts File.read(path)
    end
  end

  ######################################################################
  
  def display_params(method)

    params = method.params

    if params[0,1] == "("
      if method.is_singleton
        params = method.full_name + params
      else
        params = method.name + params
      end
    end
    params.split(/\n/).each {|p| @formatter.wrap(p) }
  end

  ######################################################################
  
  def display_flow(flow)
    if !flow || flow.empty?
      @formatter.wrap("(no description...)")
    else
      @formatter.display_flow(flow)
    end
  end

######################################################################

def display_method_info(method_entry)
  method = @ri_reader.get_method(method_entry)
  @formatter.draw_line(method.full_name)
  display_params(method)
  @formatter.draw_line
  display_flow(method.comment)
  if method.aliases && !method.aliases.empty?
    @formatter.blankline
    aka = "(also known as "
    aka << method.aliases.map {|a| a.name }.join(", ") 
    aka << ")"
    @formatter.wrap(aka)
  end
end

######################################################################

def display_class_info(class_entry)
  klass = @ri_reader.get_class(class_entry)
  @formatter.draw_line(klass.display_name + ": " + klass.full_name)
  display_flow(klass.comment)
  @formatter.draw_line

  unless klass.includes.empty?
    @formatter.blankline
    @formatter.display_heading("Includes:", 2, "")
    incs = []
    klass.includes.each do |inc|
      inc_desc = @ri_reader.find_class_by_name(inc.name)
      if inc_desc
        str = inc.name + "("
        str << inc_desc.instance_methods.map{|m| m.name}.join(", ")
        str << ")"
        incs << str
      else
        incs << inc.name
      end
    end
    @formatter.wrap(incs.sort.join(', '))
  end

  unless klass.constants.empty?
    @formatter.blankline
    @formatter.display_heading("Constants:", 2, "")
    len = 0
    klass.constants.each { |c| len = c.name.length if c.name.length > len }
    len += 2
    klass.constants.each do |c|
      @formatter.wrap(c.value, 
                      @formatter.indent+((c.name+":").ljust(len)))
    end 
  end

  unless klass.class_methods.empty?
    @formatter.blankline
    @formatter.display_heading("Class methods:", 2, "")
    @formatter.wrap(klass.class_methods.map{|m| m.name}.sort.join(', '))
  end

  unless klass.instance_methods.empty?
    @formatter.blankline
    @formatter.display_heading("Instance methods:", 2, "")
    @formatter.wrap(klass.instance_methods.map{|m| m.name}.sort.join(', '))
  end

  unless klass.attributes.empty?
    @formatter.blankline
    @formatter.wrap("Attributes:", "")
    @formatter.wrap(klass.attributes.map{|a| a.name}.sort.join(', '))
  end
end

######################################################################

# If the list of matching methods contains exactly one entry, or
# if it contains an entry that exactly matches the requested method,
# then display that entry, otherwise display the list of
# matching method names

def report_method_stuff(requested_method_name, methods)
  if methods.size == 1
    display_method_info(methods[0])
  else
    entries = methods.find_all {|m| m.name == requested_method_name}
    if entries.size == 1
      display_method_info(entries[0])
    else
      puts "More than one method matched your request. You can refine"
      puts "your search by asking for information on one of:\n\n"
      @formatter.wrap(methods.map {|m| m.full_name} .join(", "))
    end
  end
end

######################################################################

def report_class_stuff(requested_class_name, namespaces)
  if namespaces.size == 1
    display_class_info(namespaces[0])
  else 
    entries = namespaces.find_all {|m| m.name == requested_class_name}
    if entries.size == 1
      display_class_info(entries[0])
    else
      puts "More than one class or module matched your request. You can refine"
      puts "your search by asking for information on one of:\n\n"
      @formatter.wrap(namespaces.map {|m| m.full_name}.join(", "))
    end
  end
end

######################################################################


def display_info_for(arg)
  desc = NameDescriptor.new(arg)

  namespaces = @ri_reader.top_level_namespace

  for class_name in desc.class_names
    namespaces = @ri_reader.lookup_namespace_in(class_name, namespaces)
    if namespaces.empty?
      raise RiError.new("Nothing known about #{arg}")
    end
  end

  setup_pager unless @options.use_stdout
  
  begin
    if desc.method_name.nil?
      report_class_stuff(desc.class_names.join('::'), namespaces)
    else
      methods = @ri_reader.find_methods(desc.method_name, 
                                        desc.is_class_method,
                                        namespaces)
      
      if methods.empty?
        raise RiError.new("Nothing known about #{arg}")
      else
        report_method_stuff(desc.method_name, methods)
      end
    end

    page_output unless @options.use_stdout
  ensure
    STDOUT.reopen(@save_stdout) if @save_stdout
  end
    
end
end

######################################################################

if ARGV.size.zero?
  display_usage
else
  ri = RiDisplay.new
  begin
    ARGV.each do |arg|
      ri.display_info_for(arg)
    end
  rescue RiError => e
    $stderr.puts(e.message)
    exit(1)
  end
end
