#
# inst.rb
#
#   Copyright (c) 1999 Minero Aoki <aamine@dp.u-netsurf.ne.jp>
#

require 'tempfile'

require 'amstd/futils'
require 'amstd/rbparams'
require 'amstd/bug'


class Installer

  extend FileUtils
  include FileUtils

  #
  # name => [ default, argument, discription ]
  #
  Options = {
    'bin-dir'   => [ RubyParams::BINDIR,
                     'path',
                     'path for binary' ],
    'rb-dir'    => [ RubyParams::SITE_RB,
                     'path',
                     'path for ruby script' ],
    'so-dir'    => [ RubyParams::SITE_SO,
                     'path',
                     'path for ruby extention' ],
    'ruby-path' => [ RubyParams::RUBY_PATH,
                     'path',
                     'path of ruby interpreter' ]
  }

  OptionOrder = %w( bin-dir rb-dir so-dir ruby-path )


  def initialize
    @verbose = true
    @config = {}
    @task = nil
    @wrong_arg = []
  end

  attr :config
  attr :task
  attr :wrong_arg

  ConfigFile = 'config.save'

  def set_config( argv )
    Options.each do |k,v|
      @config[ k ] = v[0]
    end
    if exist? ConfigFile then
      read_config
    end

    exp = /\A--(#{Options.keys.join '|'})=/

    argv ||= ARGV
    argv.delete_if do |i|
      case i
      when exp
        name = $1
        ar   = $'.strip

        unless @config[ name ] then
          raise ArgumentError, "unknown argument '--#{name}'"
        end
        @config[ name ] = ar
        true

      when 'config', 'setup', 'install'
        if @task then
          raise ArgumentError, "too many # of task: '#{@task}' and '#{i}'"
        end
        @task = i
        true

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

      else
        @wrong_arg.push i.dup
        false
      end
    end

    unless @task then
      raise ArgumentError, "task not given: try 'ruby setup.rb --help'"
    end
  end

  def write_config
    File.open( ConfigFile, 'w' ) do |f|
      @config.each do |k,v|
        f.printf "%s=%s\n", k, v
      end
    end
  end

  def read_config
    File.foreach( ConfigFile ) do |line|
      k, v = line.split( '=', 2 )
      @config[ k.strip ] = v.strip
    end
  end

  def print_usage
    print <<S

Usage:

  ruby setup.rb [<options>] config|setup|install

Options:

S
    OptionOrder.each do |name|
      i = Options[name]
      default = i[0]
      arg     = i[1]
      desc    = i[2]
      printf( "  %-20s %s [%s]\n",
              "--#{name}=#{arg}", desc, default )
    end
    puts
  end


  class << self

    def execute
      i = new
      begin
        i.set_config ARGV
      rescue
raise
        puts $!
        exit 1
      end
      unless i.wrong_arg.empty? then
        puts "unknown arguments '#{i.wrong_arg.join %<', '>}'"
        exit 1
      end
      i.execute
    end

  end

  def execute
    case @task
    when 'config'
      write_config

    when 'setup'
      try_setup

    when 'install'
      try_install

=begin c
    when 'setup-install'
      try_setup
      try_install
=end c

    else
      bug!
    end
  end

  def try_setup
    puts 'entering setup phase...'
    begin
      com_setup
    rescue
      puts 'setup failed'
      raise
    end
    puts 'setup done.'
  end

  def try_install
    puts 'entering install phase...'
    begin
      com_install
    rescue
      puts 'install failed'
      raise
    end
    puts 'installation done.'
  end


  def into_dir( libn )
    unless directory? libn then
      # for raw package
      puts "inst.rb: skip #{libn} (not critical)"
      return
    end
    chdir( libn ) do
      yield
    end
  end


  def install_bin( fn )
    install fn, isdir( @config['bin-dir'] ), 0755
  end


  def setup_library( libn )
    compile_all_in libn
  end

  def install_library( libn )
    lib_install( libn ) do |rb_to, so_to|
      install_rb rb_to
      each_dir do |ext|
        if so_dir? '.' then
          make_install so_to
        end
      end
    end
  end

  def lib_install( libname )
    into_dir( libname ) do
      yield fjoin(@config['rb-dir'], libname),
            fjoin(@config['so-dir'], libname)
    end
  end


  def install_rb( to = nil )
    to ||= @config['rb-dir']
    Dir.foreach( '.' ) do |fn|
      if /\.rb\z/o === fn and file? fn then
        install fn, isdir( to ), 0644
      end
    end
  end


  def extconf
    unless is_newer? 'Makefile', 'extconf.rb' then
      system 'ruby extconf.rb'
    end
  end

  def make
    command 'make'
  end

  def compile_all_in( dir )
    chdir( dir ) do
      each_sodir do |extname|
        extconf
        make
      end
    end
  end

  def make_install( to = nil )
    to ||= @config['so-dir']
    isdir to
    find_so('.').each do |fn|
      install fn, to, 0555
    end
  end

  def find_so( dir = '.' )
    fnames = nil
    Dir.open( dir ) {|d| fnames = d.to_a }
    exp = /\.#{RubyParams::DLEXT}\z/
    arr = fnames.find_all {|fn| exp === fn }
    unless arr then
      raise ArgumentError,
        "can't find ruby extention library in '#{expand dir}'"
    end

    arr
  end

  def so_dir?( dn = '.' )
    file? fjoin( dn, 'MANIFEST' )
  end

  def each_sodir
    each_dir do |dn|
      if so_dir? then
        yield dn
      end
    end
  end
        
  def each_dir
    Dir.foreach( '.' ) do |fn|
      next if /\A\./o === fn
      if directory? fn then
        chdir( fn ) do
          yield fn
        end
      end
    end
  end


  def install_all( to )
    Dir.foreach( '.' ) do |fn|
      if file? fn then
        install fn, to, 644
      end
    end
  end


  def add_rubypath( fn, opt = '' )
    opt = ' ' + opt unless opt.empty?

    tmpf = Tempfile.open( 'rbinst' )
    File.open( fn ) do |f|
      first = f.gets
      tmpf.puts "\#!#{@config['ruby-path']}#{opt}"
      tmpf << first unless /\A\#\!/o === first
      f.each {|i| tmpf << i }
    end
    tmpf.close
    
    mod = File.stat( fn ).mode
    tmpf.open
    open( fn, 'w' ) do |wf|
      tmpf.each {|i| wf << i }
    end
    chmod mod, fn

    tmpf.close true
  end

end
