require 'mumukit'
require 'mumukit/inspection'
require 'yaml'

require_relative 'with_test_parser'

module EvalExpectationsOnAST
  def eval_in_gobstones(binding, ast)
    pattern_generator = expectations[type]

    if pattern_generator
      !!(ast =~ pattern_generator[binding])
    else
      true
    end
  end

  def use(regexp)
    lambda { |_| regexp }
  end

  def subject_for(binding)
    if binding == 'program'
      StonesSpec::Subject::Program
    else
      StonesSpec::Subject.from(binding)
    end
  end

  def check_repeat_of(target)
    use(/AST\(repeat\s*#{target}/)
  end
end

class Mumukit::Inspection::PlainInspection
  include EvalExpectationsOnAST

  def expectations
    {
      'HasBinding' => lambda { |binding| subject_for(binding).ast_regexp },
      'HasForeach' => use(/AST\(foreach/),
      'HasRepeat' => check_repeat_of('.+'),
      'HasVariable' => use(/AST\(assignVarName/),
      'HasWhile' => use(/AST\(while/)
    }
  end
end

class Mumukit::Inspection::TargetedInspection
  include EvalExpectationsOnAST

  def expectations
    {
      'HasArity' => lambda { |binding| /#{subject_for(binding).ast_regexp}\s*AST\((\s*\w+){#{target}}\)/ },
      'HasRepeatOf' => check_repeat_of("AST\\(literal\\s*#{target}\\)"),
      'HasUsage' => use(/AST\((proc|func)Call\s*#{target}$/)
    }
  end
end

class Mumukit::Inspection::NegatedInspection
  def eval_in_gobstones(binding, ast)
    !@inspection.eval_in_gobstones(binding, ast)
  end
end

class GobstonesExpectationsHook < Mumukit::Defaults::ExpectationsHook
  include StonesSpec::WithTempfile
  include WithTestParser

  def run!(request)
    content = request[:content]
    expectations = request[:expectations]

    if content.strip.empty?
      return expectations.map { |exp| {'expectation' => exp, 'result' => false } }
    end

    ast = generate_ast! content
    all_expectations = expectations + (default_expectations_for parse_test request)

    all_expectations.map { |exp| {'expectation' => exp, 'result' => run_expectation!(exp, ast)} }
  end

  private

  def default_expectations_for(test)
    StonesSpec::Subject.from(test[:subject]).default_expectations
  end

  def run_expectation!(expectation, ast)
    Mumukit::Inspection.parse(expectation['inspection']).eval_in_gobstones(expectation['binding'], ast)
  end

  def generate_ast!(source_code)
    %x"#{gobstones_command} #{write_tempfile(source_code, 'gbs').path} --print-ast --target parse --no-print-retvals --silent"
  end

  def gobstones_command
    @config['gobstones_command']
  end
end