#!/usr/bin/env ruby

ROOT = File.expand_path('..', File.dirname(__FILE__))
$:.unshift "#{ROOT}/lib"

require 'minitest'
require 'minitest/autorun'
require 'minitest/focus'
require 'pry'
require 'stringio'
require 'fileutils'
require 'cast'

# a dir to cd into for creating files and such
TEST_DIR = "#{File.dirname(__FILE__)}/var"

# --------------------------------------------------------------------
#                         Helpers for testing
# --------------------------------------------------------------------

class Array
  def same_list?(other)
    self.length == other.length or
      return false
    self.zip(other).all? do |mine, yours|
      mine.equal? yours or
        return false
    end
  end
end

class Integer
  #
  # Return a `self'-element array containing the result of the given
  # block.
  #
  def of(&blk)
    Array.new(self, &blk)
  end
end

module Minitest::Assertions
  INDENT = '    '
  #
  # Assert that the given string is parsed as expected.  The given
  # string is of the format:
  #
  # <program>
  # ----
  # <expected inspect string>
  #
  # The <program> part is yielded to obtain the AST.
  #
  def check_ast(test_data)
    inp, exp = test_data.split(/^----+\n/)
    ast = yield(inp)
    assert_tree(ast)
    assert(ast.is_a?(C::Node))
    assert_equal_inspect_strs(exp, ast.inspect)
  end
  #
  # Assert that the given Node#inspect strings are equal.
  #
  def assert_equal_inspect_strs(exp, out)
    # remove EOL space
    out = out.gsub(/ *$/, '')
    exp = exp.gsub(/ *$/, '')

    # normalize BOL space
    exp.gsub!(%r'^#{INDENT}*') do |s|
      levels = s.length / INDENT.length
      C::Node::INSPECT_TAB*levels
    end

    # compare
    msg = "Debug strings unequal:\n#{juxtapose('Expected', exp, 'Output', out)}"
    assert(out == exp, msg)
  end
  #
  # Return a string of `s1' and `s2' side by side with a dividing line
  # in between indicating differences.  `h1' and `h2' are the column
  # headings.
  #
  def juxtapose(h1, s1, h2, s2)
    s1 = s1.split("\n")
    s2 = s2.split("\n")
    rows = [s1.length, s2.length].max
    wl = s1.map{|line| line.length}.max
    wr = s2.map{|line| line.length}.max
    ret = ''
    ret << "#{('-'*wl)}----#{'-'*wr}\n"
    ret << "#{h1.ljust(wl)} || #{h2}\n"
    ret << "#{('-'*wl)}-++-#{'-'*wr}\n"
    (0...rows).each do |i|
      if i >= s1.length
        ret << "#{' '*wl}  > #{s2[i]}\n"
      elsif i >= s2.length
        ret << "#{s1[i].ljust(wl)} <\n"
      elsif s1[i] == s2[i]
        ret << "#{s1[i].ljust(wl)} || #{s2[i]}\n"
      else
        ret << "#{s1[i].ljust(wl)} <> #{s2[i]}\n"
      end
    end
    ret << "#{('-'*wl)}----#{'-'*wr}\n"
    return ret
  end
  #
  # Assert that an exception of the given class is raised, and that
  # the message matches the given regex.
  #
  def assert_error(klass, re)
    ex = nil
    assert_raises(klass) do
      begin
        yield
      rescue Exception => ex
        raise
      end
    end
    assert_match(re, ex.message)
  end
  #
  # Assert that the given ast's nodes' parents are correct, and there
  # aren't non-Nodes where there shouldn't be.
  #
  def assert_tree(ast)
    meth = 'unknown method'
    caller.each do |line|
      if line =~ /in `(test_.*?)'/  #`
        meth = $1
        break
      end
    end
    filename = "#{self.class}_#{meth}.out"
    begin
      assert_tree1(ast, nil)
      assert(true)
    rescue BadTreeError => e
      require 'pp'
      open("#{filename}", 'w'){|f| PP.pp(ast, f)}
      flunk("#{e.message}.  Output dumped to `#{filename}'.")
    end
  end
  #
  def assert_tree1(x, parent)
    if x.is_a? C::Node
      parent.equal? x.parent or
        raise BadTreeError, "#{x.class}:0x#{(x.id << 1).to_s(16)} has #{x.parent ? 'wrong' : 'no'} parent"
      x.fields.each do |field|
        next if !field.child?
        val = x.send(field.reader)
        next if val.nil?
        val.is_a? C::Node or
          raise BadTreeError, "#{x.class}:0x#{(x.id << 1).to_s(16)} is a non-Node child"
        assert_tree1(val, x)
      end
    end
  end
  class BadTreeError < StandardError; end
  #
  # Assert that `arg' is a C::NodeList.
  #
  def assert_list(arg)
    assert_kind_of(C::NodeList, arg)
  end
  #
  # Assert that `arg' is an empty C::NodeList.
  #
  def assert_empty_list(arg)
    assert_list arg
    assert(arg.empty?)
  end
  #
  # Assert that the elements of exp are the same as those of out, and
  # are in the same order.
  #
  def assert_same_list(exp, out)
    assert_equal(exp.length, out.length, "Checking length")
    (0...exp.length).each do |i|
      assert_same(exp[i], out[i], "At index #{i} (of 0...#{exp.length})")
    end
  end
  #
  # Assert that out is ==, but not the same as exp (i.e., it is a
  # copy).
  #
  def assert_copy(exp, out)
    refute_same exp, out
    assert_equal exp, out
  end
end
