#!/usr/bin/env python

import os, sys, re

class multidict:
    def __init__(self, elts=()):
        self.data = {}
        for key,value in elts:
            self[key] = value
    
    def __getitem__(self, item):
        return self.data[item]
    def __setitem__(self, key, value):
        if key in self.data:
            self.data[key].append(value)
        else:
            self.data[key] = [value]
    def items(self):
        return self.data.items()
    def values(self):
        return self.data.values()
    def keys(self):
        return self.data.keys()
    def __len__(self):
        return len(self.data)

kDiagnosticRE = re.compile(': (error|warning): (.*)')
kAssertionRE = re.compile('Assertion failed: (.*, function .*, file .*, line [0-9]+\\.)')

def readInfo(path, opts):
    lastProgress = [-100,0]
    def progress(pos):
        pct = (100. * pos) / (size * 2)
        if (pct - lastProgress[0]) >= 10:
            lastProgress[0] = pct
            print '%d/%d = %.2f%%' % (pos, size*2, pct)

    f = open(path)
    data = f.read()
    f.close()

    if opts.truncate != -1:
        data = data[:opts.truncate]

    size = len(data)
    warnings = multidict()
    errors = multidict()
    for m in kDiagnosticRE.finditer(data):
        progress(m.end())
        if m.group(1) == 'error':
            d = errors
        else:
            d = warnings
        d[m.group(2)] = m
    warnings = warnings.items()
    errors = errors.items()
    assertions = multidict()
    for m in kAssertionRE.finditer(data):
        print '%d/%d = %.2f%%' % (size + m.end(), size, (float(m.end()) / (size*2)) * 100.)
        assertions[m.group(1)] = m
    assertions = assertions.items()

    # Manual scan for stack traces
    aborts = multidict()
    if 0:
        prevLine = None
        lnIter = iter(data.split('\n'))
        for ln in lnIter:
            m = kStackDumpLineRE.match(ln)
            if m:
                stack = [m.group(2)]
                for ln in lnIter:
                    m = kStackDumpLineRE.match(ln)
                    if not m:
                        break
                    stack.append(m.group(2))
                if prevLine is None or not kAssertionRE.match(prevLine):
                    aborts[tuple(stack)] = stack
            prevLine = ln

    sections = [
        (warnings, 'Warnings'),
        (errors, 'Errors'),
        (assertions, 'Assertions'),
        (aborts.items(), 'Aborts'),
        ]

    if opts.ascending:
        sections.reverse()

    for l,title in sections:
        l.sort(key = lambda (a,b): -len(b))
        if l:
            print '-- %d %s (%d kinds) --' % (sum([len(b) for a,b in l]), title, len(l))
            for name,elts in l:
                print '%5d:' % len(elts), name

def main():
    global options
    from optparse import OptionParser
    parser = OptionParser("usage: %prog [options] {inputs}")
    parser.add_option("", "--ascending", dest="ascending",
                      help="Print output in ascending order of severity.",
                      action="store_true", default=False)
    parser.add_option("", "--truncate", dest="truncate",
                      help="Truncate input file (for testing).",
                      type=int, action="store", default=-1)
    (opts, args) = parser.parse_args()
    
    if not args:
        parser.error('No inputs specified')

    for arg in args:
        readInfo(arg, opts)

if __name__=='__main__':
    main()
