class XCPretty::Parser

def current_issue

def current_issue
  @current_issue ||= {}
end

def current_linker_failure

def current_linker_failure
  @linker_failure ||= {files: []}
end

def error_or_warning_is_present

def error_or_warning_is_present
  current_issue[:reason] && current_issue[:cursor] && current_issue[:line]
end

def failures_per_suite

def failures_per_suite
  @failures ||= {}
end

def format_compile_error

def format_compile_error
  error = current_issue.dup
  @current_issue = {}
  @formatting_error = false
  formatter.format_compile_error(error[:file_name],
                                 error[:file_path],
                                 error[:reason],
                                 error[:line],
                                 error[:cursor])
end

def format_compile_warning

def format_compile_warning
  warning = current_issue.dup
  @current_issue = {}
  @formatting_warning = false
  formatter.format_compile_warning(warning[:file_name],
                                   warning[:file_path],
                                   warning[:reason],
                                   warning[:line],
                                   warning[:cursor])
end

def format_duplicate_symbols

def format_duplicate_symbols
  result = formatter.format_duplicate_symbols(
    current_linker_failure[:message],
    current_linker_failure[:files]
  )
  reset_linker_format_state
  result
end

def format_summary_if_needed(executed_message)

def format_summary_if_needed(executed_message)
  return "" unless should_format_summary?
  @formatted_summary = true
  formatter.format_test_summary(executed_message, failures_per_suite)
end

def format_undefined_symbols

def format_undefined_symbols
  result = formatter.format_undefined_symbols(
    current_linker_failure[:message],
    current_linker_failure[:symbol],
    current_linker_failure[:reference]
  )
  reset_linker_format_state
  result
end

def initialize(formatter)

def initialize(formatter)
  load_dependencies
  @formatter = formatter
end

def load_dependencies

def load_dependencies
  unless @@loaded ||= false
    require 'erb'
    @@loaded = true
  end
end

def parse(text)

def parse(text)
  update_test_state(text)
  update_error_state(text)
  update_linker_failure_state(text)
  return format_compile_error if should_format_error?
  return format_compile_warning if should_format_warning?
  return format_undefined_symbols if should_format_undefined_symbols?
  return format_duplicate_symbols if should_format_duplicate_symbols?
  case text
  when ANALYZE_MATCHER
    formatter.format_analyze($2, $1)
  when BUILD_TARGET_MATCHER
    formatter.format_build_target($1, $2, $3)
  when AGGREGATE_TARGET_MATCHER
    formatter.format_aggregate_target($1, $2, $3)
  when ANALYZE_TARGET_MATCHER
    formatter.format_analyze_target($1, $2, $3)
  when CLEAN_REMOVE_MATCHER
    formatter.format_clean_remove
  when CLEAN_TARGET_MATCHER
    formatter.format_clean_target($1, $2, $3)
  when COPY_STRINGS_MATCHER
    formatter.format_copy_strings_file($1)
  when CHECK_DEPENDENCIES_MATCHER
    formatter.format_check_dependencies
  when CLANG_ERROR_MATCHER
    formatter.format_error($1)
  when CODESIGN_FRAMEWORK_MATCHER
    formatter.format_codesign($1)
  when CODESIGN_MATCHER
    formatter.format_codesign($1)
  when CHECK_DEPENDENCIES_ERRORS_MATCHER
    formatter.format_error($1)
  when PROVISIONING_PROFILE_REQUIRED_MATCHER
    formatter.format_error($1)
  when NO_CERTIFICATE_MATCHER
    formatter.format_error($1)
  when COMPILE_MATCHER
    formatter.format_compile($2, $1)
  when COMPILE_COMMAND_MATCHER
    formatter.format_compile_command($1, $2)
  when COMPILE_XIB_MATCHER
    formatter.format_compile_xib($2, $1)
  when COMPILE_STORYBOARD_MATCHER
    formatter.format_compile_storyboard($2, $1)
  when COPY_HEADER_MATCHER
    formatter.format_copy_header_file($1, $2)
  when COPY_PLIST_MATCHER
    formatter.format_copy_plist_file($1, $2)
  when CPRESOURCE_MATCHER
    formatter.format_cpresource($1)
  when EXECUTED_MATCHER
    format_summary_if_needed(text)
  when RESTARTING_TESTS_MATCHER
    formatter.format_failing_test(@test_suite, @test_case, "Test crashed", "n/a")
  when UI_FAILING_TEST_MATCHER
    formatter.format_failing_test(@test_suite, @test_case, $2, $1)
  when FAILING_TEST_MATCHER
    formatter.format_failing_test($2, $3, ERB::Util.html_escape($4), $1)
  when FATAL_ERROR_MATCHER
    formatter.format_error($1)
  when FILE_MISSING_ERROR_MATCHER
    formatter.format_file_missing_error($1, $2)
  when GENERATE_DSYM_MATCHER
    formatter.format_generate_dsym($1)
  when LD_WARNING_MATCHER
    formatter.format_ld_warning($1 + $2)
  when LD_ERROR_MATCHER
    formatter.format_error($1)
  when LIBTOOL_MATCHER
    formatter.format_libtool($1)
  when LINKING_MATCHER
    formatter.format_linking($1, $2, $3)
  when MODULE_INCLUDES_ERROR_MATCHER
    formatter.format_error($1)
  when TEST_CASE_MEASURED_MATCHER
    formatter.format_measuring_test($1, $2, $3)
  when TEST_CASE_PENDING_MATCHER
    formatter.format_pending_test($1, $2)
  when TEST_CASE_PASSED_MATCHER
    formatter.format_passing_test($1, $2, $3)
  when PODS_ERROR_MATCHER
    formatter.format_error($1)
  when PROCESS_INFO_PLIST_MATCHER
    formatter.format_process_info_plist(*unescaped($2, $1))
  when PHASE_SCRIPT_EXECUTION_MATCHER
    formatter.format_phase_script_execution(*unescaped($1))
  when PHASE_SUCCESS_MATCHER
    formatter.format_phase_success($1)
  when PROCESS_PCH_MATCHER
    formatter.format_process_pch($1)
  when PROCESS_PCH_COMMAND_MATCHER
    formatter.format_process_pch_command($1)
  when PREPROCESS_MATCHER
    formatter.format_preprocess($1)
  when PBXCP_MATCHER
    formatter.format_pbxcp($1)
  when TESTS_RUN_COMPLETION_MATCHER
    formatter.format_test_run_finished($1, $3)
  when TEST_SUITE_STARTED_MATCHER
    formatter.format_test_run_started($1)
  when TEST_SUITE_START_MATCHER
    formatter.format_test_suite_started($1)
  when TIFFUTIL_MATCHER
    formatter.format_tiffutil($1)
  when TOUCH_MATCHER
    formatter.format_touch($1, $2)
  when WRITE_FILE_MATCHER
    formatter.format_write_file($1)
  when WRITE_AUXILIARY_FILES
    formatter.format_write_auxiliary_files
  when SHELL_COMMAND_MATCHER
    formatter.format_shell_command($1, $2)
  when GENERIC_WARNING_MATCHER
    formatter.format_warning($1)
  when WILL_NOT_BE_CODE_SIGNED_MATCHER
    formatter.format_will_not_be_code_signed($1)
  else
    formatter.format_other(text)
  end
end

def reset_linker_format_state

def reset_linker_format_state
  @linker_failure = nil
  @formatting_linker_failure = false
end

def should_format_duplicate_symbols?

def should_format_duplicate_symbols?
  current_linker_failure[:message] && current_linker_failure[:files].count > 1
end

def should_format_error?

TODO: clean up the mess around all this
def should_format_error?
  @formatting_error && error_or_warning_is_present
end

def should_format_summary?

def should_format_summary?
  @tests_done && !@formatted_summary
end

def should_format_undefined_symbols?

def should_format_undefined_symbols?
  current_linker_failure[:message] && current_linker_failure[:symbol] && current_linker_failure[:reference]
end

def should_format_warning?

def should_format_warning?
  @formatting_warning && error_or_warning_is_present
end

def store_failure(file: nil, test_suite: nil, test_case: nil, reason: nil)

def store_failure(file: nil, test_suite: nil, test_case: nil, reason: nil)
  failures_per_suite[test_suite] ||= []
  failures_per_suite[test_suite] << {
    file_path: file,
    reason: reason,
    test_case: test_case
  }
end

def unescaped(*escaped_values)

def unescaped(*escaped_values)
  escaped_values.map { |v| v.delete('\\') }
end

def update_error_state(text)

@ return Hash { :file_name, :file_path, :reason, :line }
def update_error_state(text)
  update_error = lambda {
    current_issue[:reason]    = $3
    current_issue[:file_path] = $1
    current_issue[:file_name] = $2
  }
  if text =~ COMPILE_ERROR_MATCHER
    @formatting_error = true
    update_error.call
  elsif text =~ COMPILE_WARNING_MATCHER
    @formatting_warning = true
    update_error.call
  elsif text =~ CURSOR_MATCHER
    current_issue[:cursor]    = $1.chomp
  elsif @formatting_error || @formatting_warning
    current_issue[:line]      = text.chomp
  end
end

def update_linker_failure_state(text)

def update_linker_failure_state(text)
  if text =~ LINKER_UNDEFINED_SYMBOLS_MATCHER ||
     text =~ LINKER_DUPLICATE_SYMBOLS_MATCHER
    current_linker_failure[:message] = $1
    @formatting_linker_failure = true
  end
  return unless @formatting_linker_failure
  case text
  when SYMBOL_REFERENCED_FROM_MATCHER
    current_linker_failure[:symbol] = $1
  when LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER
    current_linker_failure[:reference] = text.strip
  when LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER
    current_linker_failure[:files] << $1
  end
end

def update_test_state(text)

def update_test_state(text)
  case text
  when TEST_SUITE_STARTED_MATCHER
    @tests_done = false
    @formatted_summary = false
    @failures = {}
  when TEST_CASE_STARTED_MATCHER
    @test_suite = $1
    @test_case = $2
  when TESTS_RUN_COMPLETION_MATCHER
    @tests_done = true
  when FAILING_TEST_MATCHER
    store_failure(file: $1, test_suite: $2, test_case: $3, reason: $4)
  when UI_FAILING_TEST_MATCHER
    store_failure(file: $1, test_suite: @test_suite, test_case: @test_case, reason: $2)
  when RESTARTING_TESTS_MATCHER
    store_failure(file: "n/a", test_suite: @test_suite, test_case: @test_case, reason: "Test crashed")
  end
end