#!/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.11 2001/06/15 08:19:05 knu Exp $
RCS_REVISION = RCS_ID.split[2]
MYNAME = File.basename($0)

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

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

$fix_origin = false

$pkg_completion_proc = proc { |head|
  len = head.size
  $pkgnames.select { |pkgname| head == pkgname[0,len] }
}

def main(argv)
  usage = <<-"EOF"
usage: #{MYNAME} [-hov] [-s /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("-s", "--substitute=/OLD/NEW/", "Substitute all the dependencies recorded#{NEXTLINE}as OLD with NEW and exit") {
      |expr|
      if expr.empty?
	STDERR.puts "Illegal expression: " + expr
	STDERR.print opts
	exit 64
      end

      sep = expr.slice!(0,1)

      oldpkgname, newpkgname = expr.split(sep)

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

      update_pkgdep(oldpkgname, newpkgname)

      exit 0
    }

    opts.on("-o", "--fix-origin", "Turn on the origin fixer") {
      |$fix_origin|
    }

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

    begin
      argv = opts.parse(*argv)

      unless argv.empty?
	STDERR.print opts
	exit 64
      end

      $pkgnames = $pkgdb.glob('*', false)

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

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

	contents_file = $pkgdb.pkg_contents(pkgname)

	if $fix_origin
	  pkg = PkgInfo.new(pkgname)

	  origin = pkg.origin

	  if origin
	    portdir = File.join($ports_dir, origin)

	    unless File.directory? portdir
	      puts "Stale origin: '#{origin}'"
	      origin = nil
	    end
	  else
	    puts "Missing origin."
	  end

	  unless origin
	    print "Guessing... "
	    STDOUT.flush

	    begin
	      guess = $portsdb.glob(pkg.name).max { |a, b|
		matchlen(pkgname, a.pkgname.to_s) <=> matchlen(pkgname, b.pkgname.to_s)
	      }

	      if guess
		guess = guess.origin

		puts ''

		if confirm_port(guess)
		  origin = guess
		end
	      else
		puts "no idea."
	      end
	    rescue => e
	      puts e.message	    
	    end

	    unless origin
	      origin = input_port
	    end

	    case origin
	    when '.'
	      puts "Abort."
	      exit
	    when :skip
	      puts "Skipped."
	    else
	      begin
		File.open(contents_file, "r+") do |f|
		  lines = f.reject { |line| /^@comment +ORIGIN:/ =~ line }

		  f.rewind
		  f.print lines
		  f.puts "@comment ORIGIN:" + origin
		  len = f.pos
		  f.close
		  File.truncate contents_file, len
		end

		puts "Fixed. (-> #{origin})"
	      rescue => e
		puts "Failed to rewrite #{contents_file}: " + e.message
	      end
	    end
	  end
	end

	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)
		deppkgname = deppkg.to_s
		deppkg_re = /^#{Regexp.quote(deppkg.name)}-[^-]+$/

		if fix.nil?
		  fix = $pkgnames.select { |name|
		    deppkg_re =~ name
		  }.max { |a, b|
		    matchlen(deppkgname, a) <=> matchlen(deppkgname, b)
		  }
		end
	      rescue => e
		puts e.message
	      end

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

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

		    throw :fix_pkgdep
		  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 :fix_pkgdep
		  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
	      }

	      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
    rescue OptionParser::ParseError => e
      STDERR.puts "#{MYNAME}: #{e}", usage
      exit 64
    end
  end

  0
end

def input_pkg()
  Readline.completion_proc = $pkg_completion_proc

  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 input_port()
  Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC

  Dir.chdir($ports_dir)

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

    input.nil? and next

    input.strip!
    input.chomp!('/')

    case input
    when '.'
      return '.'
    when '?'
      print <<-EOF
[Enter] to skip, [.][Enter] to abort, [Tab] to complete
      EOF
      next
    when ''
      if prompt_yesno("Skip this?", true)
	return :skip
      end

      next
    else
      confirm_port(input) and return input
    end
  end

  # not reached
end

def confirm_port(origin)
  Dir.chdir($ports_dir)

  if %r,^[^/]+/[^/]+$, =~ origin
    makefile = File.join(origin, 'Makefile')

    unless File.file? makefile
      return prompt_yesno("Makefile not found in #{origin}.  Force it?", false)
    end

    pkgname = `cd #{origin} && make -V PKGNAME`.chomp

    return prompt_yesno("#{origin} (#{pkgname}): Use this?", true)
  end

  puts "Not in due form <category/portname>: #{input}"

  false
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
