require 'collab-qa/log-parser-build'
require 'collab-qa/log-parser-piuparts'
require 'collab-qa/log-parser-instest'
require 'collab-qa/encoding'

if defined?(Encoding)
  Encoding.default_external = 'UTF-8'
end

module Enumerable
  def grep_index(re)
    ind = []
    each_with_index do |e,i|
      if re === e
        ind << i
      end
    end
    return ind
  end
end

module CollabQA
  class Log
    attr_reader :file, :data, :lines, :package, :version, :result, :time, :reasons, :sum_1l, :sum_ml, :extract, :logtype, :comment

    def initialize(file)
      @file = file
      @data = IO::read(@file)
      
      #
      # Encoding crap
      #
      # Ruby string.encode! is not working as expected: it didn't check for
      # valid byte sequences:
      # 
      #   - Oniguruma didn't warn about this bad sequences:
      # 
      #       From oniguruma docs:
      #         A-6. Problems
      # 
      #           + Invalid encoding byte sequence is not checked.
      # 
      #             ex. UTF-8
      # 
      #             * Invalid first byte is treated as a character.
      #               /./u =~ "\xa3"
      # 
      #             * Incomplete byte sequence is not checked.
      #               /\w+/ =~ "a\xf3\x8ec"
      #               
      #   - Running RE2 on this recoded lines, warns about positional errors:
      # 
      #       - ""RE2: invalid startpos, endpos pair""
      #
      begin
        # force encoding.
        #if @data.respond_to?(:encode!)
        #  @data.encode!('UTF-16', 'UTF-8', :invalid => :replace, :replace => '')
        #  @data.encode!('UTF-8', 'UTF-16')
        #end      
        @lines = @data.split(/\n/)
      rescue
        # Read lines again and remove the invalid ones
        @lines = IO::readlines(@file)
        @lines.map! { |l| to_valid_utf8(l.chomp!)  }
      end

      @reasons, @sum_1l, @sum_ml, @extract = nil
      @logtype = nil
      @package, @version, @result, @time, @buildtime = 'UNKNOWN'
      @comment = nil

      dbh = @lines.grep(/^DC-Build-Header:/)[0]
      if dbh
        junk, @package, @version, rest = dbh.split(' ', 4)
        @logtype = :build
        dbs = @lines.grep(/^DC-(Build-)?Status:/)[0]
        if dbs
          junk, @result, @time = dbs.split(' ', 3)
          @time = @time.to_f
        else
          @result = "Unknown"
          @time = 0
        end
        # handle retries, remove first log
        reti = @lines.grep_index(/DC-Message: Failed, but took only.*Retrying, you never know./)[-1]
        if reti
          @lines = @lines[0..0] + @lines[(reti+1)..-1]
        end
        dbn = @lines.grep(/^Build needed/)[0]
        if dbn
          t, rest = dbn.split(' ',4)
          @buildtime = t.split(/:|,/).map { |i| i.to_i }.inject(0) { |a, b| a * 60 + b }
        end
      else
        dph = @lines.grep(/^DC-Piuparts-Header:/)[0]
        if dph
          @logtype = :piuparts
          junk, @package, rest = dph.split(' ', 4)
          # guess the version from the log. yeah, tricky.
          @version = 'UKN'
          @lines.each do |l|
            if (m = l.match(/ Setting up (.*) \((.*)\).../))
              next if m[1] != @package
              @version = m[2]
            end
          end
          dps = @lines.grep(/^DC-Piuparts-Status:/)[0]
          if dps
            junk, @result, @time = dps.split(' ', 3)
            @time = @time.to_f
          else
            @result = "Unknown"
            @time = 0
          end
        else
          dph = @lines.grep(/^IT-Header:/)[0]
          if dph
            @logtype = :instest
            junk, @package, rest = dph.split(' ', 4)
            ver = @lines.grep(/^-- Finding version: /)[0]
            if ver
              @version = ver.split(' ')[4]
            else
              @version = 'UNKNOWN'
            end
            dps = @lines.grep(/^-- Result: /)[0]
            if dps
              @result = dps.split(' ')[2]
              @time = @lines.grep(/^-- Total time: /)[4].to_f
            else
              @result = "Unknown"
              @time = 0
            end
          end
        end
      end
    end

    def match_one_amongst?(line, regexps)
      regexps.each do |e|
        return true if line =~ e
      end
      return false
    end

    def guess_failed
      @reasons = []
      return if @result == "OK"
      if @logtype == :build
        guess_failed_build
      elsif @logtype == :piuparts
        guess_failed_piuparts
      elsif @logtype == :instest
        guess_failed_instest
      end
    end

    def extract_log
      return if @result == 'OK'
      guess_failed if @reasons.nil?
      if @logtype == :build
        extract_log_build
      elsif @logtype == :piuparts
        extract_log_piuparts
      elsif @logtype == :instest
        extract_log_instest
      end
    end

    def reasons_to_s
      if @reasons.nil?
        return "REASONS_NOT_COMPUTED"
      elsif @reasons.empty?
        return "UNKNOWN"
      else
        return "#{@reasons.join('/')}"
      end
    end

    def oneline_to_s(disptime = false)
      if disptime
        return "#{@package} #{@version} #{@result} #{@time} #{@buildtime} [#{reasons_to_s}] #{@sum_1l}\n"
      else
        return "#{@package} #{@version} #{@result} [#{reasons_to_s}] #{@sum_1l}\n"
      end
    end

    def to_s
      s = "#{@package} #{@version} #{@result} #{@time}\n"
      s += "Reason: #{reasons_to_s}\n"
      if @extract
        if @sum_1l
          s += "\n1-line summary:\n"
          s += "#{@sum_1l}\n"
        end
        if @sum_ml
          s += "\nmulti-line summary:\n"
          s += "#{@sum_ml.join("\n")}\n"
        end
        s += "\nexcerpt for mail:\n"
        s += "#{@extract.join("\n")}\n"
      end
      s
    end

    def to_mail(date, fullname, email, bugtype, filename = "")
      sdate = date.gsub(/\//, '')
      bfooter = <<-EOF
A list of current common problems and possible solutions is available at
http://wiki.debian.org/qa.debian.org/FTBFS . You're welcome to contribute!

About the archive rebuild: The rebuild was done on EC2 VM instances from
Amazon Web Services, using a clean, minimal and up-to-date chroot. Every
failed build was retried once to eliminate random failures.
      EOF
      if @logtype == :instest
        s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: #{@sum_1l}

Package: #{@package}
Version: #{@version}
Severity: serious
User: debian-qa@lists.debian.org
Usertags: instest-#{sdate} instest

Hi,

While testing the installation of all packages in sid, I ran
into the following problem:

> #{@extract.map { |l| l.gsub(/^\s+/,'') }.join("\n> ")}
#{@comment != nil ? "\n" + @comment.rstrip + "\n": ''}
The full build log is available from:
 http://aws-logs.debian.net/ftbfs-logs/#{date}/#{@package}.log

It is reproducible by installing your package in a clean chroot, using
the debconf Noninteractive frontend, and priority: critical.

This test was carried out using about 50 AMD64 nodes of the Grid'5000
platform.  Internet was not accessible from the nodes.
EOF
        return s
      elsif @logtype == :piuparts
        s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: piuparts test fails: #{@sum_1l}

Package: #{@package}
Version: #{@version}
Severity: serious
User: debian-qa@lists.debian.org
Usertags: piuparts-#{sdate} piuparts

Hi,

During tests using piuparts of all packages in lenny,
I ran into the following problem:

> #{@extract.join("\n> ")}

It is reproducible by installing your package in a clean chroot - cleaned
up using:
 debfoster -o MaxPriority=required -o UseRecommends=no -f -n apt debfoster
EOF
        return s
      else # rebuild
        if bugtype == "dash"
          s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: FTBFS with dash: #{@sum_1l}

Package: #{@package}
Version: #{@version}
Severity: important
User: debian-qa@lists.debian.org
Usertags: qa-ftbfs-dash-#{sdate} qa-ftbfs-dash

Hi,

During a rebuild of all packages in sid using /bin/dash as /bin/sh,
your package failed to build.

Relevant part (hopefully):
> #{@extract.join("\n> ")}

The full build log is available from:
   http://aws-logs.debian.net/ftbfs-logs/#{date}/#{filename}

This bug is part of the "dash" release goal (see
http://release.debian.org/lenny/goals.txt). If you intend to fix this
bug yourself, please do so ASAP (and please indicate so in the bug
log, by tagging it "pending", for example). If you don't, someone will
probably take care of preparing an NMU (please note that 0-day NMU
rules apply for bugs part of a release goal).

If you want more information about dash as /bin/sh, you can read:
http://lists.debian.org/debian-release/2008/01/msg00189.html
https://wiki.ubuntu.com/DashAsBinSh

#{bfooter}
EOF
        elsif bugtype == 'squeeze'
          s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: FTBFS in squeeze: #{@sum_1l}

Package: #{@package}
Version: #{@version}
Severity: serious
Tags: squeeze sid
User: debian-qa@lists.debian.org
Usertags: qa-ftbfs-#{sdate} qa-ftbfs
Justification: FTBFS on amd64

Hi,

During a rebuild of all packages in a squeeze chroot, your package failed
to build on amd64.

Relevant part (hopefully):
> #{@extract.join("\n> ")}

The full build log is available from:
   http://aws-logs.debian.net/ftbfs-logs/#{date}/#{filename}

It is possible that your package builds fine in sid, but you should make sure
that your package also builds fine in squeeze before the release.

#{bfooter}
EOF
        elsif bugtype == 'noarchall'
          s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: FTBFS: #{@sum_1l}

Source: #{@package}
Version: #{@version}
Severity: serious
Tags: jessie sid
User: debian-qa@lists.debian.org
Usertags: qa-ftbfs-#{sdate} qa-ftbfs qa-ftbfs-buildarch
Justification: FTBFS on amd64

Hi,

During a rebuild of all packages in sid, your package failed to build on
amd64.

This rebuild was done by building only architecture:any binary packages
(binary-arch target of debian/rules), and using a recent dpkg that uses the
build-arch target if available.
Also, only the Build-Depends were installed, not the Build-Depends-Indep.

Relevant part (hopefully):
> #{@extract.join("\n> ")}

The full build log is available from:
   http://aws-logs.debian.net/ftbfs-logs/#{date}/#{filename}

#{bfooter}
EOF
        elsif bugtype == 'wheezy'
          s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: FTBFS: #{@sum_1l}

Source: #{@package}
Version: #{@version}
Severity: serious
Tags: wheezy sid
User: debian-qa@lists.debian.org
Usertags: qa-ftbfs-#{sdate} qa-ftbfs
Justification: FTBFS in wheezy on amd64

Hi,

During a rebuild of all packages in *wheezy*, your package failed to
build on amd64.

Relevant part (hopefully):
> #{@extract.join("\n> ")}

The full build log is available from:
   http://aws-logs.debian.net/ftbfs-logs/#{date}/#{filename}

#{bfooter}
EOF
        else
          s = <<-EOF
From: #{fullname} <#{email}>
To: submit@bugs.debian.org
Subject: #{@package}: FTBFS: #{@sum_1l}

Source: #{@package}
Version: #{@version}
Severity: serious
Tags: jessie sid
User: debian-qa@lists.debian.org
Usertags: qa-ftbfs-#{sdate} qa-ftbfs
Justification: FTBFS on amd64

Hi,

During a rebuild of all packages in sid, your package failed to build on
amd64.

Relevant part (hopefully):
> #{@extract.join("\n> ")}

The full build log is available from:
   http://aws-logs.debian.net/ftbfs-logs/#{date}/#{filename}

#{bfooter}
EOF
        end
        return s.chomp
      end
    end
  end
end
