#
# toplevel.rb
#

class ToplevelInstaller

  Version   = '3.2.2'
  Copyright = 'Copyright (c) 2000-2003 Minero Aoki'

  TASKS = [
    [ 'config',   'saves your configurations' ],
    [ 'show',     'shows current configuration' ],
    [ 'setup',    'compiles ruby extentions and others' ],
    [ 'install',  'installs files' ],
    [ 'clean',    "does `make clean' for each extention" ],
    [ 'distclean',"does `make distclean' for each extention" ]
  ]

  def ToplevelInstaller.invoke
    instance().invoke
  end

  @singleton = nil

  def ToplevelInstaller.instance
    @singleton ||= new(File.dirname($0))
    @singleton
  end

  include MetaConfigAPI

  def initialize(ardir_root)
    @config = nil
    @options = { 'verbose' => true }
    @ardir = File.expand_path(ardir_root)
  end

  def inspect
    "#<#{self.class} #{__id__()}>"
  end

  def invoke
    run_metaconfigs
    task = parsearg_global()
    @config = load_config(task)
    __send__ "parsearg_#{task}"
    init_installers
    __send__ "exec_#{task}"
  end

  def run_metaconfigs
    eval_file_ifexist "#{@ardir}/metaconfig"
  end

  def load_config(task)
    case task
    when 'config'
      ConfigTable.new
    when 'clean', 'distclean'
      if File.exist?('config.save')
      then ConfigTable.load
      else ConfigTable.new
      end
    else
      ConfigTable.load
    end
  end

  def init_installers
    @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
  end

  #
  # Hook Script API bases
  #

  def srcdir_root
    @ardir
  end

  def objdir_root
    '.'
  end

  def relpath
    '.'
  end

  #
  # Option Parsing
  #

  def parsearg_global
    valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/

    while arg = ARGV.shift
      case arg
      when /\A\w+\z/
        raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
        return arg

      when '-q', '--quiet'
        @options['verbose'] = false

      when       '--verbose'
        @options['verbose'] = true

      when '-h', '--help'
        print_usage $stdout
        exit 0

      when '-v', '--version'
        puts "#{File.basename($0)} version #{Version}"
        exit 0
      
      when '--copyright'
        puts Copyright
        exit 0

      else
        raise InstallError, "unknown global option '#{arg}'"
      end
    end

    raise InstallError, <<EOS
No task or global option given.
Typical installation procedure is:
    $ ruby #{File.basename($0)} config
    $ ruby #{File.basename($0)} setup
    # ruby #{File.basename($0)} install  (may require root privilege)
EOS
  end


  def parsearg_no_options
    raise InstallError, "#{task}:  unknown options: #{ARGV.join ' '}"\
        unless ARGV.empty?
  end

  alias parsearg_show       parsearg_no_options
  alias parsearg_setup      parsearg_no_options
  alias parsearg_clean      parsearg_no_options
  alias parsearg_distclean  parsearg_no_options

  def parsearg_config
    re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
    @options['config-opt'] = []

    while i = ARGV.shift
      if /\A--?\z/ =~ i
        @options['config-opt'] = ARGV.dup
        break
      end
      m = re.match(i) or raise InstallError, "config: unknown option #{i}"
      name, value = m.to_a[1,2]
      if value
        if ConfigTable.bool_config?(name)
          raise InstallError, "config: --#{name} allows only yes/no for argument"\
              unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value
          value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
        end
      else
        raise InstallError, "config: --#{name} requires argument"\
            unless ConfigTable.bool_config?(name)
        value = 'yes'
      end
      @config[name] = value
    end
  end

  def parsearg_install
    @options['no-harm'] = false
    @options['install-prefix'] = ''
    while a = ARGV.shift
      case a
      when /\A--no-harm\z/
        @options['no-harm'] = true
      when /\A--prefix=(.*)\z/
        path = $1
        path = File.expand_path(path) unless path[0,1] == '/'
        @options['install-prefix'] = path
      else
        raise InstallError, "install: unknown option #{a}"
      end
    end
  end

  def print_usage(out)
    out.puts 'Typical Installation Procedure:'
    out.puts "  $ ruby #{File.basename $0} config"
    out.puts "  $ ruby #{File.basename $0} setup"
    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
    out.puts
    out.puts 'Detailed Usage:'
    out.puts "  ruby #{File.basename $0} <global option>"
    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"

    fmt = "  %-20s %s\n"
    out.puts
    out.puts 'Global options:'
    out.printf fmt, '-q,--quiet',   'suppress message outputs'
    out.printf fmt, '   --verbose', 'output messages verbosely'
    out.printf fmt, '-h,--help',    'print this message'
    out.printf fmt, '-v,--version', 'print version and quit'
    out.printf fmt, '   --copyright',  'print copyright and quit'

    out.puts
    out.puts 'Tasks:'
    TASKS.each do |name, desc|
      out.printf "  %-10s  %s\n", name, desc
    end

    out.puts
    out.puts 'Options for config:'
    ConfigTable.each_definition do |name, (default, arg, desc, default2)|
      out.printf "  %-20s %s [%s]\n",
                 '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
                 desc,
                 default2 || default
    end
    out.printf "  %-20s %s [%s]\n",
        '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"

    out.puts
    out.puts 'Options for install:'
    out.printf "  %-20s %s [%s]\n",
        '--no-harm', 'only display what to do if given', 'off'
    out.printf "  %-20s %s [%s]\n",
        '--prefix',  'install path prefix', '$prefix'

    out.puts
  end

  #
  # Task Handlers
  #

  def exec_config
    @installer.exec_config
    @config.save   # must be final
  end

  def exec_setup
    @installer.exec_setup
  end

  def exec_install
    @installer.exec_install
  end

  def exec_show
    ConfigTable.each_name do |k|
      v = @config.get_raw(k)
      if not v or v.empty?
        v = '(not specified)'
      end
      printf "%-10s %s\n", k, v
    end
  end

  def exec_clean
    @installer.exec_clean
  end

  def exec_distclean
    @installer.exec_distclean
  end

end


class ToplevelInstallerMulti < ToplevelInstaller

  include HookUtils
  include HookScriptAPI
  include FileOperations

  def initialize(ardir)
    super
    @packages = all_dirs_in("#{@ardir}/packages")
    raise 'no package exists' if @packages.empty?
  end

  def run_metaconfigs
    eval_file_ifexist "#{@ardir}/metaconfig"
    @packages.each do |name|
      eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
    end
  end

  def init_installers
    @installers = {}
    @packages.each do |pack|
      @installers[pack] = Installer.new(@config, @options,
                                       "#{@ardir}/packages/#{pack}",
                                       "packages/#{pack}")
    end

    with    = extract_selection(config('with'))
    without = extract_selection(config('without'))
    @selected = @installers.keys.select {|name|
                  (with.empty? or with.include?(name)) \
                      and not without.include?(name)
                }
  end

  def extract_selection(list)
    a = list.split(/,/)
    a.each do |name|
      raise InstallError, "no such package: #{name}" \
              unless @installers.key?(name)
    end
    a
  end

  def print_usage(f)
    super
    f.puts 'Inluded packages:'
    f.puts '  ' + @packages.sort.join(' ')
    f.puts
  end

  #
  # multi-package metaconfig API
  #

  attr_reader :packages

  def declare_packages(list)
    raise 'package list is empty' if list.empty?
    list.each do |name|
      raise "directory packages/#{name} does not exist"\
              unless File.dir?("#{@ardir}/packages/#{name}")
    end
    @packages = list
  end

  #
  # Task Handlers
  #

  def exec_config
    run_hook 'pre-config'
    each_selected_installers {|inst| inst.exec_config }
    run_hook 'post-config'
    @config.save   # must be final
  end

  def exec_setup
    run_hook 'pre-setup'
    each_selected_installers {|inst| inst.exec_setup }
    run_hook 'post-setup'
  end

  def exec_install
    run_hook 'pre-install'
    each_selected_installers {|inst| inst.exec_install }
    run_hook 'post-install'
  end

  def exec_clean
    rm_f 'config.save'
    run_hook 'pre-clean'
    each_selected_installers {|inst| inst.exec_clean }
    run_hook 'post-clean'
  end

  def exec_distclean
    rm_f 'config.save'
    run_hook 'pre-distclean'
    each_selected_installers {|inst| inst.exec_distclean }
    run_hook 'post-distclean'
  end

  #
  # lib
  #

  def each_selected_installers
    Dir.mkdir 'packages' unless File.dir?('packages')
    @selected.each do |pack|
      $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
      Dir.chdir "packages/#{pack}"
      yield @installers[pack]
      Dir.chdir '../..'
    end
  end

  def verbose?
    @options['verbose']
  end

  def no_harm?
    @options['no-harm']
  end

end
