#!/usr/bin/env ruby

require 'optparse'
require 'yaml'
require 'fileutils'
require 'pathname'

bold = ["\033[0m", "\033[1m"]

@options = {}

def system!(cmd)
  system(cmd) or raise "failed to run #{cmd}"
end

def sanitize(str, where)
  raise "unsanitary string in #{where}" if (str =~ /[^\w.-]/)
  str
end

def sanitize_path(str, where)
  raise "unsanitary string in #{where}" if (str =~ /[^@\w\\ '\/.:+-]/)
  str
end

def info(str)
  puts str if @options[:verbose]
end

################################

OptionParser.new do |opts|
  opts.banner = "Usage: build [options] <build-description>.yml"

  opts.on("-v", "--verbose", "be more verbose") do |v|
    @options[:verbose] = v
  end
  @options[:markup] = true
  opts.on("-m", "--[no-]markup", "markup the output using ANSI escape codes") do |m|
    @options[:markup] = m
  end

  opts.on("-r REL", "--release REL", "release name") do |v|
    @options[:release] = v
  end

  opts.on("-d DEST", "--destination DEST", "directory to place signature in") do |v|
    @options[:destination] = v
  end
  opts.on("-c SIGNER", "--compare-to SIGNER", "compare other manifests to SIGNER's, if not given pick first") do |v|
    @options[:compareto] = v
  end

  opts.on("-p PROG", "--verify-program PROG", "specify verification program to use (default is gpg)") do |v|
    @options[:program] = v
  end
end.parse!

base_dir = Pathname.new(__FILE__).expand_path.dirname.parent

build_desc_file = ARGV.shift or raise "must supply YAML build description file"

build_desc = YAML.load_file(build_desc_file)

in_sums = []

result_dir = 'result'

package_name = build_desc["name"] or raise "must supply name"
package_name = sanitize(package_name, "package name")

destination = @options[:destination] || File.join(base_dir, "sigs", package_name)
release = @options[:release] || "current"
release = sanitize(release, "release")
verbose = @options[:verbose]
bold = ['', ''] unless @options[:markup]

program = @options[:program] || "gpg"

release_path = File.join(destination, release)

File.exist?(release_path) or raise "#{release_path} does not exist"

result_file = "#{package_name}-build.assert"
sig_file = "#{result_file}.sig"

current_manifest = nil
if @options[:compareto]
  # Load a specific 'golden' manifest to compare to
  compareto = @options[:compareto]
  result_path = sanitize_path(File.join(release_path, compareto, result_file), "result path")
  result = YAML.load_file(result_path)
  current_manifest = result['out_manifest']
end

did_fail = false

Dir.foreach(release_path) do |signer_dir|
  next if signer_dir == "." or signer_dir == ".."
  signer_path = sanitize_path(File.join(release_path, signer_dir), "signer path")
  next if !File.directory?(signer_path)
  result_path = sanitize_path(File.join(signer_path, result_file), "result path")
  sig_path = sanitize_path(File.join(signer_path, sig_file), "result path")

  if !File.exist?(result_path)
    puts "missing result at #{result_path}"
    next
  end

  if !File.exist?(sig_path)
    puts "missing signature at #{sig_path}"
    next
  end

  result = YAML.load_file(result_path)
  system("#{program} --keyserver pgp.mit.edu --recv-keys `#{program} --quiet --batch --verify \"#{File.join(signer_path, 'signature.pgp')}\" \"#{result_path}\" 2>&1 | head -n1 | grep \"key ID\" | awk '{ print $15 }'` > /dev/null 2>&1")
  out = `#{program} --quiet --batch --verify \"#{sig_path}\" \"#{result_path}\" 2>&1`
  if $? != 0
    out.each_line do |line|
      if line =~ /^gpg: Signature made/
        info(line)
      else
        puts line
      end
    end
    puts "#{bold[1]}#{signer_dir}: BAD SIGNATURE#{bold[0]}"
    puts
    did_fail = true
  elsif current_manifest and (result['out_manifest'] != current_manifest or result['release'] != release or result['name'] != package_name)
    out.each_line do |line|
      if line =~ /^gpg: Signature made/
        info(line)
      elsif line =~ /^gpg: Good signature/
        info(line)
      elsif line =~ /^gpg:                 aka/
        info(line)
      else
        puts line
      end
    end
    puts "#{bold[1]}#{signer_dir}: MISMATCH#{bold[0]}"
    puts
    if verbose
      lines1 = current_manifest.each_line
      lines2 = result['out_manifest'].each_line
      lines1.zip(lines2) do |line|
        if line[0] != line[1]
          puts "- "+line[0]
          puts "+ "+line[1]
        end
      end
    end
    did_fail = true
  else
    out.each_line do |line|
      if line =~ /^gpg: Signature made/
        info(line)
      elsif line =~ /^gpg: Good signature/
        info(line)
      elsif line =~ /^gpg:                 aka/
        info(line)
      else
        puts line
      end
    end
    puts "#{bold[1]}#{signer_dir}: OK#{bold[0]}"
    puts
  end
  if !current_manifest
    # take first manifest as 'current' to compare against
    current_manifest = result['out_manifest']
  end
end

exit 1 if did_fail
