class Shindo::Tests

def after(&block)

def after(&block)
  @afters.last.push(block)
end

def assert(type, expectation, description, &block)

def assert(type, expectation, description, &block)
  return if Thread.main[:exit] || Thread.current[:reload]
  description = [@description, description].compact.join(' ')
  if block_given?
    begin
      for before in @befores.flatten.compact
        before.call
      end
      value, success = case type
      when :raises
        raises?(expectation, &block)
      when :returns
        returns?(expectation, &block)
      end
      for after in @afters.flatten.compact
        after.call
      end
    rescue => error
      success = false
      value = error
    end
    if @pending
      display_pending(description)
      @pending = false
    elsif success
      display_success(description)
    else
      display_failure(description)
      case value
      when Exception, Interrupt
        display_error(value)
      else
        @message ||= [
          "expected => #{expectation.inspect}",
          "returned => #{value.inspect}"
        ]
        Thread.current[:formatador].indent do
          Thread.current[:formatador].display_lines([*@message].map {|message| "[red]#{message}[/]"})
        end
        @message = nil
      end
      if Thread.current[:interactive] && STDOUT.tty?
        prompt(description, &block)
      end
    end
  else
    display_pending(description)
  end
  success
end

def before(&block)

def before(&block)
  @befores.last.push(block)
end

def display_description(description)

def display_description(description)
  unless @described
    Thread.current[:formatador].display(Thread.current[:file])
    print ' '
    @described = true
  end
end

def display_description(description)

def display_description(description)
  Thread.current[:formatador].display_line(description)
end

def display_description_stack

def display_description_stack
  return if @description_stack.empty?
  Thread.current[:formatador].indent do
    Thread.current[:formatador].display_line(@description_stack.pop)
    display_description_stack
  end
end

def display_error(error)

def display_error(error)
  display_description_stack
  Thread.current[:formatador].indent do
    Thread.current[:formatador].display_line("[red]#{error.message} (#{error.class})[/]")
    unless error.backtrace.empty?
      Thread.current[:formatador].indent do
        Thread.current[:formatador].display_lines(error.backtrace.map {|line| "[red]#{line}[/]"})
      end
    end
  end
end

def display_error(error)

def display_error(error)
  Thread.current[:formatador].display_line("[red]#{error.message} (#{error.class})[/]")
  unless error.backtrace.empty?
    Thread.current[:formatador].indent do
      Thread.current[:formatador].display_lines(error.backtrace.map {|line| "[red]#{line}[/]"})
    end
  end
end

def display_failure(description)

def display_failure(description)
  Thread.current[:totals][:failed] += 1
  Thread.current[:formatador].display_line
  display_description_stack
  Thread.current[:formatador].display_line("[red]- #{description}[/]")
end

def display_failure(description)

def display_failure(description)
  Thread.current[:totals][:failed] += 1
  Thread.current[:formatador].display_line("[red]- #{description}[/]")
end

def display_pending(description)

def display_pending(description)
  Thread.current[:totals][:pending] += 1
  print Formatador.parse("[yellow]#[/]")
end

def display_pending(description)

def display_pending(description)
  Thread.current[:totals][:pending] += 1
  Thread.current[:formatador].display_line("[yellow]# #{description}[/]")
end

def display_success(description)

def display_success(description)
  Thread.current[:totals][:succeeded] += 1
  print Formatador.parse("[green]+[/]")
end

def display_success(description)

def display_success(description)
  Thread.current[:totals][:succeeded] += 1
  Thread.current[:formatador].display_line("[green]+ #{description}[/]")
end

def initialize(description, tags = [], &block)

def initialize(description, tags = [], &block)
  @afters     = []
  @befores    = []
  @description_stack  = []
  @tag_stack          = []
  Thread.current[:formatador] = Formatador.new
  Thread.current[:reload] = false
  Thread.current[:tags] ||= []
  Thread.current[:totals] ||= { :failed => 0, :pending => 0, :skipped => 0, :succeeded => 0 }
  @if_tagged = []
  @unless_tagged = []
  for tag in Thread.current[:tags]
    case tag[0...1]
    when '+'
      @if_tagged << tag[1..-1]
    when '-'
      @unless_tagged << tag[1..-1]
    end
  end
  Thread.current[:formatador].display_line
  tests(description, tags, &block)
end

def pending

def pending
  catch(:pending) do
    @pending = true
  end
  throw(:pending)
end

def prompt(description, &block)

def prompt(description, &block)
  return if Thread.main[:exit] || Thread.current[:reload]
  Thread.current[:formatador].display("Action? [c,e,i,q,r,t,?]? ")
  choice = STDIN.gets.strip
  continue = false
  Thread.current[:formatador].display_line
  Thread.current[:formatador].indent do
    case choice
    when 'c', 'continue'
      continue = true
    when /^e .*/, /^eval .*/
      begin
        value = eval(choice[2..-1], @gestalt.bindings.last)
        if value.nil?
          value = 'nil'
        end
        Thread.current[:formatador].display_line(value)
      rescue => error
        display_error(error)
      end
    when 'i', 'interactive', 'irb'
      Thread.current[:formatador].display_line('Starting interactive session...')
      if @irb.nil?
        require 'irb'
        ARGV.clear # Avoid passing args to IRB
        IRB.setup(nil)
        @irb = IRB::Irb.new(nil)
        IRB.conf[:MAIN_CONTEXT] = @irb.context
        IRB.conf[:PROMPT][:SHINDO] = {}
      end
      for key, value in IRB.conf[:PROMPT][:SIMPLE]
        IRB.conf[:PROMPT][:SHINDO][key] = "#{Thread.current[:formatador].indentation}#{value}"
      end
      @irb.context.prompt_mode = :SHINDO
      @irb.context.workspace = IRB::WorkSpace.new(@gestalt.bindings.last)
      begin
        @irb.eval_input
      rescue SystemExit
      end
    when 'q', 'quit', 'exit'
      Thread.current[:formatador].display_line("Exiting...")
      Thread.main[:exit] = true
    when 'r', 'reload'
      Thread.current[:formatador].display_line("Reloading...")
      Thread.current[:reload] = true
    when 't', 'backtrace', 'trace'
      if @gestalt.calls.empty?
        Thread.current[:formatador].display_line("[red]No methods were called, so no backtrace was captured.[/]")
      else
        @gestalt.display_calls
      end
    when '?', 'help'
      Thread.current[:formatador].display_lines([
        'c - ignore this error and continue',
        'i - interactive mode',
        'q - quit Shindo',
        'r - reload and run the tests again',
        't - display backtrace',
        '? - display help'
      ])
    else
      Thread.current[:formatador].display_line("[red]#{choice} is not a valid choice, please try again.[/]")
    end
    Thread.current[:formatador].display_line
  end
  unless continue || Thread.main[:exit]
    Thread.current[:formatador].display_line("[red]- #{description}[/]")
    prompt(description, &block)
  end
end

def raises(error, &block)

def raises(error, &block)
  assert(:raises, error, "raises #{error.inspect}", &block)
end

def raises?(expectation, &block)

def raises?(expectation, &block)
  value = begin
    instance_eval(&block)
  rescue => error
    error
  end
  [value, value.is_a?(expectation)]
end

def raises?(expectation, &block)

def raises?(expectation, &block)
  @gestalt = Gestalt.new({'formatador' => Thread.current[:formatador]})
  [value = @gestalt.run(&block), value.is_a?(expectation)]
end

def returns(expectation, &block)

def returns(expectation, &block)
  assert(:returns, expectation, "returns #{expectation.inspect}", &block)
end

def returns?(expectation, &block)

def returns?(expectation, &block)
  [value = instance_eval(&block), value == expectation]
end

def returns?(expectation, &block)

def returns?(expectation, &block)
  @gestalt = Gestalt.new({'formatador' => Thread.current[:formatador]})
  [value = @gestalt.run(&block), value == expectation]
end

def test(description = 'returns true', &block)

def test(description = 'returns true', &block)
  assert(:returns, true, description, &block)
end

def tests(description, tags = [], &block)

def tests(description, tags = [], &block)
  return self if Thread.main[:exit] || Thread.current[:reload]
  tags = [*tags]
  @tag_stack.push(tags)
  @befores.push([])
  @afters.push([])
  @description = nil
  description ||= 'Shindo.tests'
  description = "[bold]#{description}[normal]"
  unless tags.empty?
    description << " (#{tags.join(', ')})"
  end
  @description_stack.push(description)
  # if the test includes +tags and discludes -tags, evaluate it
  if (@if_tagged.empty? || !(@if_tagged & @tag_stack.flatten).empty?) &&
      (@unless_tagged.empty? || (@unless_tagged & @tag_stack.flatten).empty?)
    if block_given?
      begin
        display_description(description)
        Thread.current[:formatador].indent { instance_eval(&block) }
      rescue => error
        display_error(error)
      end
    else
      @description = description
    end
  else
    display_description("[light_black]#{description}[/]")
  end
  @description_stack.pop
  @afters.pop
  @befores.pop
  @tag_stack.pop
  Thread.exit if Thread.main[:exit] || Thread.current[:reload]
  self
end