#!/usr/bin/env ruby
#
# Copyright (c) 2000-2001 Akinori MUSHA
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

RCS_ID = %q$Idaemons: /home/cvs/pkgtools/pkgdepfix,v 1.7 2001/06/07 03:10:04 knu Exp $
RCS_REVISION = RCS_ID.split[2]
MYNAME = File.basename($0)

require "optparse"
require "pkgtools"
require "readline"

COLUMNSIZE = 24
NEXTLINE = "\n%*s" % [5 + COLUMNSIZE, '']

def main(argv)
  usage = <<-"EOF"
usage: #{MYNAME} [-hv] [old_pkgname new_pkgname]
    EOF

  banner = <<-"EOF"
#{MYNAME} rev.#{RCS_REVISION}

#{usage}
    EOF

  OptionParser.new(banner, COLUMNSIZE) do |opts|
    opts.on("-h", "--help", "Show this message") {
      print opts
      exit 0
    }

    opts.on("-v", "--verbose", "Be verbose") {
      |$verbose|
    }

    opts.on_tail <<-"EOF".chomp

When two package names are given, #{MYNAME} replaces all the dependencies
recorded as old_pkgname with new_pkgname.  Otherwise #{MYNAME} runs
in the interactive mode and helps you fix stale dependencies.
      EOF

    argv = opts.parse(*argv)

    if argv.size == 2
      oldpkgname, newpkgname = *argv

      if $verbose
	progress_message "Replacing dependencies: #{oldpkgname} -> #{newpkgname}"
      end

      update_pkgdep(oldpkgname, newpkgname)

      exit
    end

    unless argv.empty?
      print opts
      return 0
    end

    $pkgnames = $pkgdb.glob.collect { |pkg| pkg.to_s }

    Readline.completion_proc = proc { |prefix|
      len = prefix.size
      $pkgnames.select { |pkgname| prefix == pkgname[0,len] }
    }

    req_hash = {}
    fix_hash = {}
    all_list = []

    $pkgnames.each do |pkgname|
      puts "Checking #{pkgname}"

      deps = $pkgdb.pkgdep(pkgname) or next

      deps.each do |dep|
	catch (:next_dep) {
	  unless $pkgnames.include?(dep)
	    puts "Stale dependency: #{pkgname} -> #{dep}:"

	    fix = fix_hash[dep]

	    begin
	      deppkg = PkgInfo.new(dep)

	      if fix.nil?
		guess = $pkgnames.select { |name|
		  /^#{Regexp.quote(deppkg.name)}-[^-]+$/ =~ name
		}.sort

		unless guess.empty?
		  fix = guess.pop.to_s
		end
	      end
	    rescue => e
	      puts e.message
	    end

	    catch (:do_fix) {
	      if fix
		skip = (fix == :skip)

		if all_list.include?(dep)
		  if skip
		    puts "Skipped."
		    throw :next_dep
		  end

		  throw :do_fix
		end

		case fix
		when :skip
		  prompt = "Skip this?"
		when :delete
		  prompt = "Delete this?"
		else
		  prompt = fix + " ?"
		end

		ans = prompt_yesnoall(prompt, true)

		if ans == :all
		  all_list << dep
		end

		if ans
		  if skip
		    throw :next_dep
		  end

		  throw :do_fix
		end
	      end

	      fix = input_pkg

	      case fix
	      when '.'
		puts "Abort."
		exit
	      when :skip
		puts "Skipped."
		fix_hash[dep] = :skip
		throw :next_dep
	      when :skip_all
		fix_hash[dep] = :skip
		all_list << dep
		throw :next_dep
	      when :delete_all
		all_list << dep
		fix = :delete
	      end
	    }

	    contents_file = $pkgdb.pkg_contents(pkgname)

	    begin
	      File.open(contents_file, "r+") do |f|
		lines = []

		f.collect { |line|
		  if /^(@pkgdep +)#{Regexp.quote(dep)}$/ =~ line
		    lines << $1 + fix + "\n" unless fix == :delete
		  else
		    lines << line
		  end
		}

		f.rewind
		f.print lines
		len = f.pos
		f.close
		File.truncate contents_file, len

		case fix
		when :delete
		  puts "Deleted."
		else
		  puts "Fixed. (-> #{fix})"
		end

		fix_hash[dep] = fix
		dep = fix
	      end
	    rescue => e
	      puts "Failed to rewrite #{contents_file}: " + e.message
	    end
	  end

	  unless fix == :delete
	    if req_hash.include?(dep)
	      req_hash[dep] << pkgname
	    else
	      req_hash[dep] = [pkgname]
	    end
	  end
	}
      end
    end

    $pkgnames.each do |pkgname|
      req_file = $pkgdb.pkg_required_by(pkgname)

      if req_hash.include?(pkgname)
	File.open(req_file, "w") do |f|
	  f.puts req_hash[pkgname].sort
	end
      else
	File.unlink(req_file) if File.exist?(req_file)
      end
    end
  end

  0
end

def input_pkg()
  loop do
    input = Readline.readline("New dependency (? to help): ", true)

    if input.nil?
      ans = prompt_yesnoall("\nDelete this?", true)

      if ans == :all
	return :delete_all
      elsif ans
	return :delete
      end

      next
    end

    input.strip!

    case input
    when '.'
      return '.'
    when '?'
      print <<-EOF
 [Enter] to skip, [Ctrl]+[D] to delete, [.][Enter] to abort, [Tab] to complete
      EOF
      next
    when ''
      ans = prompt_yesnoall("Skip this?", true)

      if ans == :all
	return :skip_all
      elsif ans
	return :skip
      end

      next
    else
      if $pkgnames.include?(input)
	return input
      end

      puts "No such package installed: #{input}"
    end
  end

  # not reached
end

def signal_handler(sig)
  puts "\nInterrupted."

  system 'stty', 'sane'
  exit
end

if $0 == __FILE__
  for sig in [2, 3, 15]
    trap(sig) do
      signal_handler(sig)
    end
  end

  exit main(ARGV)
end
