#!/usr/bin/ruby

require 'find'
require 'pathname'
$:.unshift Pathname.new(__FILE__).dirname.parent.realpath+'Library'+'Homebrew'
require 'env'

def prune
  n=0
  dirs=Array.new
  $root.find do |path|
    if path.directory?
      name=path.relative_path_from($root).to_s
      if name == '.git' or name == 'Cellar' or name == 'Library/Homebrew' or name == 'Library/Formula'
        Find.prune
      else
        dirs<<path
      end
    elsif path.symlink?
      resolved_path=path.dirname+path.readlink
      unless resolved_path.exist?
        path.unlink
        n+=1
      end
    end
  end
  # entries lists '.' and '..' so 2 is minimum basically
  dirs.sort.reverse_each do |d|
    if d.children.length == 0
      d.rmdir
      n+=1
    end
  end
  return n
end

def shift_formulae
  name=Pathname.new ARGV.shift

  return name if name.directory? and name.parent.realpath == $cellar
  return File.basename(name, '.rb') if name.file? and name.extname == '.rb' and name.parent.realpath == $formula

  name=name.to_s
  raise "#{name} is an invalid name for a formula" if name.include? '/'

  return name if ($formula+(name+'.rb')).file?
  return name if ($cellar+name).directory?

  raise "No formula or keg for #{name} found"
end

def __class name
  #remove invalid characters and camelcase
  name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase }
end

def __rb name
  $formula+(name+'.rb')
end

def __obj name
  require "#{__rb name}"
  o=eval(__class(name)).new
  o.name=name
  return o
end

def rm keg
  #TODO if multiple versions don't rm all unless --force
  path=$cellar+keg
  path.rmtree
  puts "#{path} removed (#{prune} files)"
end

def ln name
  keg=$cellar+name
  keg=keg.realpath

  if keg.parent.parent == $root
    # we are one dir too high
    kids=keg.children
    raise "#{keg} is empty :(" if kids.length == 0
    raise "There are multiple versions of #{keg.basename} installed please specify one" if kids.length > 1
    keg=keg.children.first
    raise "#{keg} is not a directory" unless keg.directory?
  elsif keg.parent.parent.parent != $root
    raise '#{keg} is not a keg'
  end

  # yeah indeed, you have to force anything you need in the main tree into
  # these directories :P 
  # NOTE that not everything needs to be in the main tree
  # TODO consider using hardlinks
  $n=0
  lnd(keg, 'etc') {nil}
  lnd(keg, 'include') {nil}
  
  lnd(keg, 'lib') do |path|
    :mkpath if ['pkgconfig','php'].include? path.to_s
  end

  lnd(keg, 'bin') do |path| 
    if path.extname.to_s == '.app'
      # no need to put .app bundles in the path, just use spotlight, or the
      # open command
      :skip
    else
      :mkpath
    end
  end

  lnd(keg, 'share') do |path|
    includes=(1..9).collect {|x| "man/man#{x}"} <<'man'<<'doc'<<'locale'<<'info'
    :mkpath if includes.include? path.to_s
  end

  return $n
end

def symlink_relative_to from, to
  tod=to.dirname
  tod.mkpath
  Dir.chdir(tod) do
    #TODO use ruby function so we get exceptions
    `ln -sf "#{from.relative_path_from tod}"`
    $n+=1
  end
end

# symlinks a directory recursively into our FHS tree
def lnd keg, start
  start=keg+start
  return unless start.directory?

  start.find do |from|
    next if from == start

    prune=false    
    relative_path=from.relative_path_from keg
    to=$root+relative_path

    if from.directory?
      cmd=yield from.relative_path_from(start)
      
      if :skip == cmd
        Find.prune
      elsif :mkpath == cmd
        to.mkpath
        $n+=1
      else
        symlink_relative_to from, to
        Find.prune
      end
    elsif from.file?
      symlink_relative_to from, to
    end
  end
end

def usage
  name=File.basename $0
  <<-EOS
Usage: #{name} [abv] [prune] [--prefix] [--cache]
Usage: #{name} [install] [ln] [rm] [info] [list] beverage
  EOS
end

######################################################################### impl
begin
  #TODO proper options parsing so --options can go first if necessary
  
  case ARGV.shift
    when 'abv'
      `find #{$cellar} -type f | wc -l`.strip+' files, '+`du -hd0 #{$cellar} | cut -d"\t" -f1`.strip
    when 'prune'
      puts "Pruned #{prune} files"
    when '--prefix'
      # we use the cwd because __FILE__ can be relative and expand_path
      # resolves the symlink for the working directory if fed a relative path
      # NOTE we don't use Dir.pwd because it resolves the symlink :P #rubysucks
      cwd=Pathname.new `pwd`.strip
      puts File.expand_path(cwd+__FILE__+'../../')
    when '--cache'
      puts File.expand_path('~/Library/Application Support/Homebrew')
    when '-h', '--help', '--usage', '-?'
      puts usage
    when '-v', '--version'
      puts HOMEBREW_VERSION
    when 'list'
      puts `find #{$cellar+shift_formulae}`
    when 'install'
      name=shift_formulae
      beginning = Time.now
      o=__obj(name)
      raise "#{o.prefix} already exists!" if o.prefix.exist?
      o.prefix.mkpath
      o.brew { o.install }
      ohai 'Finishing up'
      o.clean
      ln name
      puts "#{o.prefix}: "+`find #{o.prefix} -type f | wc -l`.strip+
                           ' files, '+
                           `du -hd0 #{o.prefix} | cut -d"\t" -f1`.strip+
                           ", built in #{Time.now - beginning} seconds"
    when 'ln'
      puts "Created #{ln shift_formulae} links"
    when 'rm'
      rm shift_formulae
    when 'info'
      o=__obj shift_formulae
      puts "#{o.name} #{o.version}"
      puts o.homepage
    else
      puts usage
  end

rescue StandardError, Interrupt => e
  if ARGV.include? '--verbose' or ENV['HOMEBREW_DEBUG']
    raise
  elsif e.kind_of? Interrupt
    puts # seeimgly a newline is typical
    exit 130
  elsif e.kind_of? StandardError and not e.kind_of? NameError
    puts "\033[1;31mError\033[0;0m: #{e}"
    exit 1
  else
    raise
  end
end