module PryDebugger

  module Breakpoints
    def breakpoints(breakpoints)
      steps = []
      breakpoints.each_with_index do |b, index|
        next_b = breakpoints[index+1]
        steps << {
          cmd: b[0],
          expected: b[1],
          next_cmd: next_b ? next_b[0] : nil,
          index: index
        }
      end

      PryDebugger.breakpoints =
          steps.map do |step|
            Proc.new do |label, binding_, output|
              compare(step, label, binding_, output)
              step[:next_cmd]
            end
          end
    end

    def compare(step, label, binding_, output)
      exp = step[:expected]
      if exp.is_a? Proc
        exp.call binding_, output
      elsif exp.is_a? Hash
        if exp[:output_includes]
          expect(output).to include exp[:output_includes]
        else
          expect(output).to eq exp[:output]
        end
      elsif not exp.nil?
        err = <<-TEXT
[#{step[:index]}] #{step[:cmd]} => '#{exp}', got '#{label}'
        TEXT
        expect(label).to eq(exp), err
      end
    end
  end

  class InputPipe

    def readline(*args)
      repl_binding = binding.callers[1]
      pry_ = repl_binding.eval('@pry')
      binding_ = pry_.current_binding

      next_cmd = PryDebugger.enter_breakpoint binding_
      next_cmd || 'c'
    end

  end

  class OutputPipe < StringIO

    def print(*args)
      #STDOUT.print *args
      super
    end

    def take_away
      result = string.clone
      truncate(0)
      rewind
      result.gsub(/\e\[([;\d]+)?m/, '')
    end

  end

  extend self

  def inject
    Pry.config.input = InputPipe.new
    @output = OutputPipe.new
    Pry.config.output = @output
  end

  def breakpoints
    @breakpoints_procs
  end

  def breakpoints=(breakpoints)
    @breakpoints_procs = breakpoints
    @breakpoint_call = 0
  end

  def enter_breakpoint(binding_)
    raise 'Next breakpoint handler missing' if @breakpoints_procs.size == 0
    #puts (@breakpoint_call += 1)
    output = @output.take_away
    output.match(/^ (=>|⛔️) .*#(.*)/)
    label = ($2 || '').strip
    @breakpoints_procs.shift.call label, binding_, output.strip
  end

end