#!/usr/bin/env ruby

require "optparse"

require "rubygems"
require "diff/lcs"
require "diff/lcs/hunk"

require File.expand_path(File.dirname(__FILE__) + "/../lib/testrunner")

module TestTool
  # Code inside this method is based on ldiff.rb.
  def self.write_diff(lines_old, lines_new)
    diffs = Diff::LCS.diff(lines_old, lines_new)
    return if diffs.empty?

    file_length_difference = 0
    oldhunk = nil
    hunk = nil

    diffs.each do |piece|
      begin
        hunk = Diff::LCS::Hunk.new(
          lines_old,
          lines_new,
          piece,
          3,
          file_length_difference
        )
        file_length_difference = hunk.file_length_difference

        next unless oldhunk

        if hunk.overlaps?(oldhunk)
          hunk.unshift(oldhunk)
        else
          print oldhunk.diff(:unified)
          print "\n"
        end
      ensure
        oldhunk = hunk
      end
    end

    print oldhunk.diff(:unified)
    print "\n"
  end

  def self.run_test(runner, file)
    begin
      runner.run(file)
      puts "OK   " + file
      true
    rescue TestRunFailed => e
      puts "FAIL " + file
      puts
      if e.expected_stdout && (e.expected_stdout != e.actual_stdout)
        puts "Standard output differences:"
        puts "----------------------------"
        write_diff e.actual_stdout.split("\n"), e.expected_stdout.split("\n")
        puts
      end
      if e.expected_stderr && (e.expected_stderr != e.actual_stderr)
        puts "Error output differences:"
        puts "-------------------------"
        write_diff e.expected_stderr.split("\n"), e.actual_stderr.split("\n")
        puts
      end
      false
    rescue Exception => e
      puts "ERROR " + file
      puts e.message
      false
    end
  end

  def self.build_file_list(files_or_dirs)
    result = []
    files_or_dirs.each do |file_or_dir|
      if File.directory?(file_or_dir)
        result += Dir[file_or_dir + "/**/*.test"]
      else
        result << file_or_dir
      end
    end
    result
  end

  # Define options...
  option_parser = OptionParser.new do |option_parser|
    option_parser.banner = "Usage: test_tool command file_or_dir..."

    option_parser.separator ""
    option_parser.separator "Options:"

    option_parser.on("-h", "--help", "write this help") do
      puts option_parser
      exit
    end

    option_parser.on("-v", "--version", "write version information") do
      puts "0.3"
      exit
    end
  end

  # ...and parse the options.
  begin
    option_parser.parse!(ARGV)
  rescue OptionParser::InvalidOption => e
    abort e
  rescue OptionParser::MissingArgument => e
    abort e
  end

  # Check the command and files/dirs.
  abort "Specify a command and at least one file/direcotry," if ARGV.length == 0
  abort "Specify at least one file/direcotry," if ARGV.length == 1

  # Now do the real work.
  files = build_file_list(ARGV[1..-1])
  runner = TestRunner.new(ARGV[0])
  failure_count = 0
  files.each do |file|
    failure_count += 1 unless run_test(runner, file)
  end
  puts files.size.to_s + " tests, " + failure_count.to_s + " failures"
end
