# -*- coding: utf-8 -*-
#

require 're2'

module CollabQA
  class Log
    def guess_failed_build
      if not (@lines.grep(RE2('^sbuild \(Debian sbuild\) '))[0] or @lines.grep(RE2('^Build started at 20'))[0])
        @reasons = ['TIMEOUT']
        return
      elsif not @lines.grep(RE2('^dpkg-source: (info: )?extracting '))[0]
        @reasons = ["BUILDDEPS"]
        return
      end
      REASONS.each do |t|
        if @lines.grep(t[0])[0]
          @reasons << t[1]
        end
      end
    end

    def extract_log_build
      guess_failed if @reasons.nil?
      if @reasons.include?('TIMEOUT')
        @extract = []
        @sum_ml = []
        @sum_1l = ''
      elsif @reasons.include?('BUILDDEPS')
        extract_log_builddeps
      elsif @reasons.include?('RUBY_EXTCONF_FAILED')
        extract_line_with_context(RUBY_EXTCONF_SEARCH_RULE)
      elsif @reasons.include?('OBSOLETE_CODE')
        extract_line_with_context(OBSOLETE_CODE_SEARCH_RULE)
      elsif @reasons.include?('PATCH_FAILED')
        extract_line_with_given_context(
          PATCH_FAILED_SEARCH_RULE,
          RE2('^Unpack source'),
          RE2('^FAILED \[dpkg-source died\]')
        )
      elsif @reasons.include?('CONFIGURE_ERROR')
        extract_log_configure
        # Remove ld/gcc reasons to avoid unrelated config.log errors.
        @reasons.reject! { |r| r =~ RE2('LD_ERROR|GCC_ERROR') }    
      elsif @reasons.include?('LD_ERROR')
        extract_log_ld
      elsif @reasons.include?('CLANG_LD_ERROR')
        extract_log_ld("clang")
      elsif @reasons.include?('GCC_ERROR')
        extract_log_compiler
      elsif @reasons.include?('CLANG_ERROR')
        extract_log_compiler("clang")
      elsif @reasons.include?('BUILD_TIMEOUT')
        extract_log_timeout
      elsif @reasons.include?('RUBY_TEST_FAILED')
        extract_ruby_test
      elsif @reasons.include?('TESTS_FAILURES')
        extract_line_with_summary(TESTS_FAILURES_SEARCH_RULE, 'Tests failures')
      else
        extract_log_default
      end
    end

    CLANG_SEARCH_RULE=RE2('(^clang: error: ([^linker command failed]|.*)|^error: (invalid|unknown)| fatal error:.*file not found)')
    GCC_SEARCH_RULE=RE2('(\d|.*cc.*): (internal compiler |fatal )?(E|e)rror: ')

    RUBY_TEST_SEARCH_RULE=RE2('ERROR: Test "ruby[0-9.]*" failed. Exiting.')
    RUBY_EXTCONF_SEARCH_RULE=RE2('extconf failed: \(Gem::InstallError\)')

    OBSOLETE_CODE_SEARCH_RULE=RE2('^ERROR: .* is obsolete and no longer supported. .*')
    TIMEOUT_SEARCH_RULE=RE2('Build killed with signal .* after .* minutes of inactivity')
    PATCH_FAILED_SEARCH_RULE=RE2('^dpkg-source: error: expected .* diff .*')

    #TESTS_FAILURES_SEARCH_RULE=RE2('recipe for target \'override_dh_auto_test\' failed')
    TESTS_FAILURES_SEARCH_RULE=RE2('recipe for target \'[A-Za-z_]*test[A-Za-z_]*\' failed')

    REASONS = [
      [ RE2('^Source-dependencies not satisfied; skipping'), 'BUILDDEPS' ],
      [ RE2('^dpkg-buildpackage: warning: Build dependencies\/conflicts unsatisfied; aborting.$'), 'BUILDDEPS' ],
      [ GCC_SEARCH_RULE, 'GCC_ERROR' ],
      [ RE2('ld returned 1 exit status$'), 'LD_ERROR' ],
      [ CLANG_SEARCH_RULE, 'CLANG_ERROR' ],
      [ RE2('Caught signal \'Terminated\': terminating immediately'), 'CLANG_TIMEOUT' ],
      [ RE2('linker command failed with exit code'), 'CLANG_LD_ERROR' ],
      [ RE2('^Compilation failed: .* warnings$'), 'COMPIL_FAILED' ],
      [ RE2('^error CS\d+: .*$'), 'COMPIL_FAILED' ],
      [ RE2('^Traceback \(most recent call last\):$'), 'PYTHON_TRACEBACK' ],
      [ RE2('^Can\'t find source for'), 'CANT_FIND_SOURCE' ],
      [ RE2('^FAILED \[dsc verification\]$'), 'DSC_VERIF' ],
      [ RE2(' not in arch list: \w+ -- skipping$'), 'NOTINARCH' ],
      [ RE2('^After installing, the following source dependencies are still unsatisfied:$'), 'BDEPUNSAT'],
      [ RE2('^ImportError:'), 'PYIMPORT'],
      [ RE2('^config.status: error:'), 'CONFIGURE_ERROR'],
      [ RE2('^configure: error:'), 'CONFIGURE_ERROR'],
      [ RE2('Segmentation fault'), 'SEGMENTATION_FAULT'],
      [ TIMEOUT_SEARCH_RULE, 'BUILD_TIMEOUT' ],
      [ RUBY_TEST_SEARCH_RULE, 'RUBY_TEST_FAILED' ],
      [ RUBY_EXTCONF_SEARCH_RULE, 'RUBY_EXTCONF_FAILED' ],
      [ OBSOLETE_CODE_SEARCH_RULE, 'OBSOLETE_CODE' ],
      [ PATCH_FAILED_SEARCH_RULE, 'PATCH_FAILED'],
      [ TESTS_FAILURES_SEARCH_RULE, 'TESTS_FAILURES']
    ]

    GCC_LINES = [
      RE2('^\s+from '),
      RE2('^In file included from '),
      RE2(': In (member )?function '),
      RE2(': warning: '),
      RE2(': fatal error: '),
      RE2(': error: '),
      RE2(': In constructor '),
      RE2('^\s*\^+\s*$'),
      RE2(' is deprecated. Use'),
      RE2(': At global scope:'),
      RE2('internal compiler error:')
    ]

    CLANG_LINES = [
      RE2(': the clang compiler does not support '),
      RE2(' invalid value '),
      RE2(' when generating multiple output files'),
      RE2(' unknown argument: ')
    ]

    RUN_GCC_RE = [
      RE2('^\s*(if )?(\/bin\/sh )?(\S+\/)*(\S+-linux-gnu-)?(gcc|xgcc|cc|g\+\+|c\+\+|nasm|g77|(.*\/)libtool) '),
      RE2('debian\/rules build$'),
      RE2('^make(\[\d+\])?: Entering directory'),
      RE2('^\/bin\/sh ..\/libtool '),
      RE2('\[echo\] Compiling source code')
    ]

    MAKE_ERROR_RE = RE2('^make(\[\d+\])?: \*\*\* .* Error')

    LD_LINES_CONTEXT = [
      RE2(' In function '),
      RE2(': first defined here'),
      RE2(': warning: .* is deprecated; use .* instead'),
      RE2(': warning: cannot find entry symbol')

    ]

    LD_LINES_ERRORS = [
      RE2('undefined reference to'),
      RE2(' final link failed:'),
      RE2(': cannot find '),
      RE2(': unknown architecture of input file'),
      RE2(': hidden symbol '),
      RE2(': multiple definition of '),
      RE2(' cannot be preloaded: ignored.'),
      RE2('could not read symbols:')
    ]

    def extract_log_compiler(cc="gcc")
      if cc == "clang"
         ig = @lines.grep_index(CLANG_SEARCH_RULE)[0]
          @SEARCH_LINES = CLANG_LINES
      else
        ig = @lines.grep_index(GCC_SEARCH_RULE)[0]
          @SEARCH_LINES = GCC_LINES
      end

      ih = ig - 1
      while (match_one_amongst?(@lines[ih], @SEARCH_LINES))
        ih -= 1
      end
      ih_full = ih
      ih = ih + 1
      while (!match_one_amongst?(@lines[ih_full], RUN_GCC_RE) && ih_full > 0)
        #puts "FNML: #{lines[ih_full]}"
        ih_full -= 1
      end
      ig_full = ig
      while (match_one_amongst?(@lines[ig_full], @SEARCH_LINES)) && ig_full < @lines.length
        ig_full += 1
      end
      while @lines[ig_full] !~ MAKE_ERROR_RE && ig_full < @lines.length
        ig_full += 1
      end
      @extract = @lines[ih_full..ig_full]
      @sum_ml = @lines[ih..ig]
      @sum_1l = @lines[ig]
      if @sum_1l =~ RE2('^\/')
        filename, rest = @sum_1l.split(/:/,2)
        filename = filename.split(/\//)[-1]
        @sum_1l = filename + ':' + rest
      end
    end

    def extract_log_ld(cc="gcc")
      if cc == "clang"
        end_full = @lines.grep_index(RE2('linker command failed with exit code'))[0]
      else
        end_full = @lines.grep_index(RE2('ld returned 1 exit status$'))[0]
      end
      end_ml = end_full - 1
      ih = end_ml 
      while (match_one_amongst?(@lines[ih - 1], LD_LINES_ERRORS + LD_LINES_CONTEXT))
        ih -= 1
      end
      beg_ml = ih
      while (@lines[ih - 1] !~ RE2('^\s*(\/(\w+\/)*)?(\w+-linux-gnu-)?(ld|gcc|xgcc|cc|g\+\+|c\+\+|nasm|g77|sh .*libtool)')) and ih > 1
        ih -= 1
      end
      beg_full = ih - 1
      @extract = @lines[beg_full..end_full]
      @sum_ml = @lines[beg_ml..end_ml]
      i = beg_ml
      while  i <= end_ml and not (match_one_amongst?(@lines[i], LD_LINES_ERRORS))
        i += 1
      end
      @sum_1l = @lines[i]
      if @sum_1l =~ RE2('^\/')
        filename, rest = @sum_1l.split(/:/,2)
        filename = filename.split(/\//)[-1]
        @sum_1l = filename + ':' + rest
      end
    end

    STRIP_LOG_LINES = [
      RE2('^Get:\d'),
      RE2('^Unpacking .*from .*deb'),
      RE2('^Selecting previously deselected package'),
      RE2('^\s*Authentication warning overridden.$'),
      RE2('^\s*debconf: delaying package configuration, since'),
      RE2('^\s*Fetched .* in'),
      RE2('^\s*\(Reading database \.\.\. .* files and directories currently installed.\)$')
    ]

    def extract_log_builddeps
      if not @lines.grep(RE2('^│ Install .* build dependencies \(apt-based resolver\) ')).empty?
        extract_log_builddeps_new
      else
        extract_log_builddeps_old
      end
    end

    def extract_log_builddeps_new
      @sum_ml = []
      @sum_1l = ""
      beg_full = @lines.grep_index(RE2('^│ Install (.*) build dependencies \(apt-based resolver\) '))[-1]
      @lines[beg_full].match(/^│ Install (.*) build dependencies \(apt-based resolver\) /)
      pkg = $1

      @extract = @lines[(beg_full-1)..-1]
      if not (e = @extract.grep_index(RE2('^E: Broken packages$'))).empty?
        @extract = @extract[0..e[0]]
      elsif not (e = @extract.grep_index(RE2('^apt-get failed.$'))).empty?
        @extract = @extract[0..e[0]]
      else
        g = @extract.grep_index(RE2('^│ Cleanup '))[0] - 2
        @extract = @extract[0..e]
      end
      @sum_ml = @extract

      @sum_1l = 'DEPS'
      if (b = @extract.index('The following packages have unmet dependencies:'))
        e = @extract.index('E: Broken packages') || @extract.index('E: Unable to correct problems, you have held broken packages.') || @extract.index('apt-get failed.')
        if e - b == 2 # only one line
          l = @extract[b+1]
          pkge = Regexp::escape(pkg)
          if l =~ /^ sbuild-build-depends-#{pkge}-dummy : Depends: (.*) but it is not installable$/
            @sum_1l = "build-dependency not installable: #{$1}"
          elsif l =~ /^ sbuild-build-depends-#{pkge}-dummy : Depends: (.* but .* is to be installed)$/
            @sum_1l = "unsatisfiable build-dependency: #{$1}"
          elsif l =~ /^ sbuild-build-depends-#{pkge}-dummy : Depends: (.*) but it is not going to be installed$/
            @sum_1l = "build-dependency not installable: #{$1}"
          elsif l =~ /^ sbuild-build-depends-#{pkge}-dummy : Depends: (.*)$/
            @sum_1l = "unsatisfiable build-dependency: #{$1} (versioned dep on a virtual pkg?)"
          end
        else # multi-line
          lines = @extract[b+1..e-1]
          if lines.all? { |l| l =~ RE2('Depends: .* but (it is not installable|it is not going to be installed)$') }
            lines.map! { |l| l.gsub(/.*Depends: (.*) but (it is not installable|it is not going to be installed)$/, '\1') }
            @sum_1l = "unsatisfiable build-dependencies: #{lines.join(', ')}"
          elsif lines.all? { |l| l =~ RE2('Depends: .*\)$') }
            lines.map! { |l| l.gsub(/.*Depends: (.*\))$/, '\1') }
            @sum_1l = "unsatisfiable build-dependencies (purely virtual?): #{lines.join(', ')}"
          else
            @sum_1l = "unsatisfiable build-dependencies: XXX"
          end
        end
      elsif not (e = @extract.grep_index(RE2('^Errors were encountered while processing:$'))).empty?
        lines = @extract[e[0]..-1]
        e2 = lines.grep_index(RE2('^E: Sub-process \/usr\/bin\/dpkg returned an error code'))
        lines = lines[1..(e2[0]-1)]
        @sum_1l = "Errors while processing: #{lines.join(' ')}"
      end
    end

    def extract_log_builddeps_old
      beg_full = @lines.index('** Using build dependencies supplied by package:')
      beg_full = 0 if beg_full.nil?
      if not @lines.grep(RE2('^Source-dependencies not satisfied; skipping')).empty?
        if not (g = @lines.grep(RE2('^(E: )?The following packages have unmet dependencies:$'))).empty?
          beg_ml = @lines.index(g[0]) + 1
          end_full = @lines.grep_index(RE2('^(E: )?Broken packages$'))[-1]
          end_ml = end_full - 1
          @extract = @lines[beg_full..end_full]
          @sum_ml = @lines[beg_ml..end_ml]
          @sum_1l = "Unsatisfiable build-dependencies" # default
          l2 = @sum_ml.grep(RE2(': Depends: '))
          if not l2.empty?
            l3 = l2[0].lstrip
            l3.gsub!(/ but it is not going to be installed/,'')
            @sum_1l = "Unsatisfiable build-dependency: #{l3}"
          end
          return
        elsif not (g = @lines.grep_index(RE2('^E: Couldn\'t find package '))).empty?
          end_full = g[0]
          @extract = @lines[beg_full..end_full]
          @sum_ml = [@lines[g[0]]]
          if @lines[g[0]] =~ /^E: Couldn\'t find package (.*)$/
            @sum_1l = "Nonexistent build-dependency: #{$1}"
          else
            raise "WTF?"
          end
          return
        elsif not (g = @lines.grep_index(RE2('^E: Package .* has no installation candidate$'))).empty?
          end_full = g[0]
          @extract = @lines[beg_full..end_full]
          @sum_ml = [@lines[end_full]]
          if @lines[end_full] =~ /^E: Package (.*) has no installation candidate$/
            @sum_1l = "Nonexistent build-dependency: #{$1}"
          else
            raise "WTF?"
          end
          return
        elsif not (g = @lines.grep_index(RE2('^After installing, the following source dependencies are still unsatisfied:$'))).empty?
          end_full = g[0] + 1
          end_ml = end_full
          beg_ml = g[0]
          @extract = @lines[beg_full..end_full]
          @sum_ml = @lines[beg_ml..end_ml]
          @sum_1l = "Unsatisfiable build-dependency: #{@lines[end_ml]}"
          return
        elsif not (g = @lines.grep_index(RE2('^Package installation not possible$'))).empty?
          end_full = g[0]
          @sum_ml = [@lines[end_full - 1]]
          @extract = @lines[beg_full..end_full]
          @sum_1l = "Unsatisfiable build-dependency: #{@sum_ml[0]}"
          return
        elsif not (g = @lines.grep_index(RE2('^ subprocess installed post-installation script returned error exit status'))).empty?
          end_full = g[0]
          h = @lines[0..end_full].grep_index(RE2('^Setting up'))
          if h.empty?
            @sum_ml = [@lines[end_full]]
          else
            @sum_ml = @lines[h[-1]..end_full]
          end
          @extract = @lines[h[-1]..end_full]
          @sum_1l = "Dependencies installation failed."
          return
        elsif not (g = @lines.grep_index(RE2('^E: Sub-process \/usr\/bin\/dpkg returned an error code'))).empty?
          end_full = g[0]
          h = @lines.grep_index(RE2('^Errors were encountered while processing:$'))
          if h.empty?
            @sum_ml = [@lines[end_full]]
          else
            @sum_ml = @lines[h[0]..end_full]
          end
          @extract = @lines[beg_full..end_full]
          @sum_1l = "Dependencies installation failed."
          return
        elsif not (g = @lines.grep_index(RE2('^Unable to fetch build-depends'))).empty?
          end_full = g[0]
          h = @lines.grep_index(RE2('^Failed to fetch'))
          if h.empty?
            @sum_ml = [@lines[end_full]]
          else
            @sum_ml = @lines[h[0]..end_full]
          end
          @extract = @lines[beg_full..end_full]
          @sum_1l = "Dependencies fetching failed."
          return
        elsif not (g = @lines.grep_index(RE2('^E: Unable to fetch some archives, maybe run apt-get update'))).empty?
          end_full = g[0]
          h = @lines.grep_index(RE2('^Failed to fetch'))
          if h.empty?
            @sum_ml = [@lines[end_full]]
          else
            @sum_ml = @lines[h[0]..end_full]
          end
          @extract = @lines[beg_full..end_full]
          @sum_1l = "Dependencies fetching failed."
          return
        else
          end_full = @lines.grep_index(RE2('^Source-dependencies not satisfied; skipping'))[0]
          @extract = @lines[beg_full..end_full]
          @sum_1l = "XXX"
        end
      elsif not (l = @lines.grep_index(RE2('^dpkg-buildpackage: warning: Build dependencies\/conflicts unsatisfied; aborting.$'))).empty?
        end_full = l[0]
        @extract = @lines[beg_full..end_full]
        i = end_full - 1
        @extract.map! do |l|
          if match_one_amongst?(l, STRIP_LOG_LINES)
            "[...]"
          else
            l
          end
        end
        last = nil
        @extract.reject! { |e| r = (e == last) ; last = e ; r }
        @sum_ml = @lines[i..i]
        @sum_1l = @lines[i].gsub(/^dpkg-checkbuilddeps: /,'')
      else
        # copy lines until the end of the file
        end_full = @lines.length - 1
        i = end_full
        while not (i == 0 or match_one_amongst?(@lines[i], [ RE2('Setting up'), RE2('Unpacking')]))
          i -= 1
        end
        beg_ml = i
        @sum_ml = @lines[i..end_full]
        @sum_1l = 'Dependencies installation failed'
        @extract = @lines[beg_full..end_full]
      end
    end

    CONFIGURE_LINES = [
        RE2('^config.status: error:'),
        RE2('^configure: error:')
    ]

    def extract_log_configure

      index_clog = @lines.grep_index(RE2('^==> config.log <==$'))

      if not index_clog.empty? then
        end_full = index_clog[0] - 1
      else
        index =  @lines.grep_index(RE2('^make:.*Error'))[-1]
        if index == nil then
          index =  @lines.grep_index(RE2('^make:'))[-1]
        end
          end_full = index - 1

      end

      end_ml = end_full
      i = end_full

      CONFIGURE_LINES.each { |l|
        ind = @lines.grep_index(l)
        if ind.length > 0 then
            i = ind[0]
            break
        end
      }

      # normally, the last line gives the command that cause the error
      beg_ml = i - 1
      # get a minimum context
      beg_full = i - MIN_LINES

      @extract = @lines[beg_full..end_full]
      @sum_ml = @lines[beg_ml..end_ml]
      @sum_1l = @lines[i]
      @sum_1l.gsub!(/\:$/, '')
    end

    RUBY_FAILURES_LINES=RE2('(RuntimeError:|\(RuntimeError\)|\(LoadError\)|^LoadError:|^NoMethodError:| *NoMethodError:|\(NoMethodError\)|\(TypeError\)|^TypeError:|^SyntaxError:|\(SyntaxError\)\^NameError:|\(NameError\)|::Error:|Failure\/Error:|^Errno::.*:|No such file or directory|\(fatal\)|Running tests for)')
    
    def extract_ruby_test()
      
      end_full = @lines.grep_index(RUBY_TEST_SEARCH_RULE)[-1]
      
      index = lines.grep_index(RUBY_FAILURES_LINES)[-1]
        
      if index
        beg_full = index
        @sum_1l = @lines[end_full].gsub!(/. Exiting./, ': ') + @lines[beg_full]
      else
        beg_full = @lines.grep_index(RE2('gem2deb\/test_runner\.rb'))[-1]
        @sum_1l = @lines[end_full].gsub!(/ Exiting./, '')
      end

      @extract = @lines[beg_full..end_full]
      @sum_ml = @lines[beg_full..end_full]
    end

    def extract_line_with_context(rule, context_lines = WANT_LINES)

      index = @lines.grep_index(rule)[0]

      raise "WHERE SHOULD I START?" if (index == nil)

      beg_ml = end_ml = end_full = index

      # get a minimum context
      beg_full = end_full - context_lines

      @extract = @lines[beg_full..end_full]
      @sum_ml = @lines[beg_ml..end_ml]
      @sum_1l = @lines[index]
    end
    
    def extract_line_with_summary(rule, summary)

      extract_line_with_context(rule)
      @sum_1l = summary
    end
    
    def extract_line_with_given_context(rule, beg_rule, end_rule)

      # find key line
      index   = @lines.grep_index(rule)[0]
      raise "WHERE SHOULD I START?" if index == nil

      # find other points
      beg_idx = @lines.grep_index(beg_rule)[0] if beg_rule
      end_idx = @lines.grep_index(end_rule)[0] if end_rule
      
      # use defaults if not given/found
      end_idx = index if end_idx == nil
      beg_idx = end_idx - WANT_LINES if beg_idx == nil
      beg_idx = 0 if beg_idx < 0

      @extract = @lines[beg_idx..end_idx]
      @sum_ml = @lines[beg_idx..end_idx]
      @sum_1l = @lines[index]
    end
    
    #
    # Extract log for timeout fails, trying to clean all the dots and other
    # garbage, that get's generated during the build.
    #
    def extract_log_timeout()
      
      index = @lines.grep_index(TIMEOUT_SEARCH_RULE)[0]

      raise "WHERE SHOULD I START?" if (index == nil)
      
      full_lines = []
      idx = index - 1
      
      # get prior lines without garbage ',.:'
      while idx >= 0 and full_lines.size < WANT_LINES
        if @lines[idx].chomp.empty? or @lines[idx].match(RE2('(\w|\d)'))
          full_lines << @lines[idx]
        end
        
        idx -= 1
      end
     
      # clean dots '....'
      line = @lines[index].tr('.', '')

      full_lines = full_lines.reverse
      full_lines << line
      
      @extract = full_lines
      @sum_ml  = [line]
      @sum_1l  = line
    end

    WANT_LINES = 15 # if less than X lines, don't bother, and send them all.
    MIN_LINES = 5 # we want some minimal context

    FAILURE_MESSAGES = [
      RE2(': File exists$'),
      RE2(': No such file or directory$'),
      RE2('is not a valid libtool archive$'),
      RE2('^Failed .* test scripts, .* okay. .* subtests failed'),
      RE2('^make(\[\d+\])?: \*\*\* No rule to make target .* Stop.$'),
      RE2('^make(\[\d+\])?: \*\*\* No targets specified and no makefile found.  Stop.$'),
      RE2(': not found$'),
      RE2(': command not found$', :case_sensitive => false),
      RE2(': missing operand$'),
      RE2('LaTeX Error: '),
      RE2(': error CS\d+: '),
      RE2('; use \`pycentral pycentraldir\' to determine the installation directory'),
      RE2('^ImportError:'),
      RE2('File format not recognized'),
      RE2('libtool: link: cannot find the library'),
      RE2(' is already defined$'),
      RE2('Could not find module '),
      RE2(' invalid option '),
      RE2('configure: error: The pkg-config script could not be found or is too old.'),
      RE2('configure: error: installation or configuration problem:'),
      RE2('configure: error: can only configure for one host and one target at a time'),
      RE2('configure: error: C compiler cannot create executables'),
      RE2('dh_install: \w+ missing files '),
      RE2('dpkg-source: unrepresentable changes to source'),
      RE2('dpkg-.*: error: '),
      RE2('dh_haskell_(build|configure): .\/setup returned exit code 1'),
      RE2('configure: error: '),
      RE2('ERROR: The following new or changed copyright notices discovered:'),
      RE2('dh_makeshlibs: dpkg-gensymbols .* returned exit code'),
      RE2('dh_makeshlibs: failing due to earlier errors'),
      RE2('error CS\d+:'),
      RE2('dh_install:.*missing files'),
      RE2('java.*cannot find symbol'),
      RE2('g\+\+: error: unrecognized option'),
      RE2('g\+\+: error: unrecognized command line option'),
      RE2('^dh_auto_test:.*returned exit code '),
      RUBY_FAILURES_LINES
    ]
    
    MULTILINE_FAILURE_MESSAGES = [
      [ RE2('dh_makeshlibs: dpkg-gensymbols .*'), RE2('returned exit code') ]
    ]

    POSSIBLE_CUTS = [
      RE2('^make(\[\d+\])?: Entering directory'),
      RE2('^\/bin\/sh ..\/libtool '),
      RE2('^i486-linux-gnu-gcc '),
      RE2('^ar cru '),
    ]

    def extract_log_default
      # find start
      if not (l = @lines.grep_index(RE2('^\s+(\/usr\/bin\/)?fakeroot debian\/rules binary'))).empty?
        bf = l[0]
      elsif not (l = @lines.grep_index(RE2('^\s+((\/usr\/bin\/)?fakeroot )?debian\/rules build'))).empty?
        bf = l[0]
      elsif not (l = @lines.grep_index(RE2('^\s+(\/usr\/bin\/)?fakeroot debian\/rules clean'))).empty?
        bf = l[0]
      elsif not (l = @lines.grep_index(RE2('^dpkg-source: extracting '))).empty?
        bf = l[0]
      elsif not (l = @lines.grep_index(RE2('^dpkg-source: error: LC_ALL=C patch '))).empty?
        bf = l[0]
      elsif not (l = @lines.grep_index(RE2(PATCH_FAILED_SEARCH_RULE))).empty?
        bf = l[0]
      else
        raise "WHERE SHOULD I START?"
      end
      lines = @lines[bf..-1]
      # find end
      if not (l2 = lines.grep_index(RE2('^dh_auto_test: .* returned exit code '))).empty?
        ef = l2[0]
      elsif not (l2 = lines.grep_index(RE2('^make(\[\d+\])?: \*\*\* .* Error'))).empty?
        ef = l2[0]
      elsif not (l2 = lines.grep_index(RE2('^dpkg-buildpackage: failure: .* gave error'))).empty?
        ef = l2[0]
      elsif not (l2 = lines.grep_index(RE2('^Build finished at'))).empty?
        ef = l2[0]
      else
        # stop at end.
        ef = lines.length - 1
      end
      # try to reduce extract
      lines = lines[0..ef]
      if lines.length > WANT_LINES
        POSSIBLE_CUTS.each do |cut|
          if not (l = lines.grep_index(cut)).empty?
            l.reverse.each do |i|
              if lines[i..-1].length >= MIN_LINES
                lines = lines[i..-1]
                break
              end
            end
          end
        end
      end
      # finish
      @extract = lines
      # handle maven special case
      if not lines.grep_index(RE2('maven')).empty? and not (l2 = lines.grep_index(RE2(' required artifact is missing.'))).empty?
        ml = lines[0..l2[0]]
        l1 = ml.grep_index(RE2('(^| )Missing:'))
        ml = ml[l1[0]..-1]
        artifacts = ml.grep(/(.* |^)\d+\) /).map { |e| e.gsub(/(.* |^)\d+\) (.*)/, '\2') }.join(', ')
        @sum_1l = "Missing required artifact: #{artifacts}"
        @sum_ml = [@sum_1l]
      else
        lines = lines.reverse

        lines.each_index do |i|
          if match_one_amongst?(lines[i], FAILURE_MESSAGES)
            @sum_1l = lines[i]
            @sum_ml = [lines[i]]
            return
          end
          
          MULTILINE_FAILURE_MESSAGES.each { |r|
            if lines[i] =~ r[0]
              if lines[i-1] =~ r[1]
                @sum_1l = lines[i] + ' ' + lines[i-1].chomp
                @sum_ml = [@sum_1l]
                return
              end
            end
          }
        end
        @sum_1l = "XXX"
        @sum_ml = ["UNKNOWN"]
      end
    end
  end
end
