#!/usr/bin/python
import sys
import os
import pgdb
import urllib
import os.path

MOZ_ROOT = None
AUTHOR = "author: "

class PatchFile:
    def __init__(self, filename):
        self.filename = filename
        self.hunks = []

class Hunk:
    def __init__(self, line_num):
        self.line_num = line_num
        self.str = None
        self.macro = None
        self.blame = None
        self.author = None

    def addLine(self, line):
        if not self.str:
            self.str = line
        else:
            self.str = self.str + "\n" + line

    def __str__(self):
        s = str(self.line_num) + ":"
        if self.str:
            s = s + self.str
        elif self.macro:
            s = s + " In macro " + self.macro
        return s

def stripDirPrefix(filename):
    return filename.replace(MOZ_ROOT, "", 1)

#format is list of [filename, list of (line #, contents)
def makeListOfLines(file):
    patchlines = open(file).read().split("\n");
    files = []
    current_file = None
    for line in patchlines:
        prefix = line[0:4]
        if prefix == "--- " or prefix == "":
            continue
        elif prefix == "+++ ":
            current_file = PatchFile(stripDirPrefix(line[4:]))
            files.append(current_file)
        elif prefix == '@@ -':
            comma = line.index(",")
            current_file.hunks.append(Hunk(int(line[4:comma])))
        elif prefix[0] == '-' or prefix[0] == '+' :
            current_file.hunks[-1].addLine(line)
    return files

# parse lines like
# Expression is within a JSVAL_TO_BOOLEAN macro at /home/tglek/work/mozilla/js/src/jsxdrapi.c:569:26
def makeMacroListOfLines(file):
    patchlines = open(file).read().split("\n");
    files = {}
    dup_guard = {}
    for line in patchlines:
        if line in dup_guard or (not line[0:23] == "Expression is within a "):
            continue
        dup_guard[line] = None
        tokens = line.split(" ")
        errorloc = tokens[-1].split(":")
        filename = stripDirPrefix(errorloc[0])
        patchfile = None
        try:
            patchfile = files[filename]
        except:
            patchfile = PatchFile(filename)
            files[filename] = patchfile
        hunk = Hunk(int(errorloc[1]))
        hunk.macro = tokens[4]
        patchfile.hunks.append(hunk)
    return files.values()

def insertOrDelete(files, table, isInsert, cursor):
    for file in files:
        for hunk in file.hunks:
            if isInsert:
                cursor.execute("INSERT INTO "+table+" VALUES(%s, %d, %s, %s, %s)", 
                               (file.filename, hunk.line_num, hunk.str, hunk.macro, hunk.author))
            else:
                cursor.execute("DELETE FROM "+table+" WHERE file = %s and line = %d",
                              (file.filename, hunk.line_num))

def readTable(cursor, table):
    cursor.execute("SELECT file, line , hunk, macro FROM %s ORDER BY file, line" % (table))
    files = {}
    file = None
    for row in cursor.fetchall():
        if not file or row[0] != file.filename:
            file = PatchFile(row[0])
            files[file.filename] = file
        hunk = Hunk(row[1])
        file.hunks.append(hunk)
        if row[2]:
            hunk.addLine(row[2])
        elif row[3]:
            hunk.macro = row[3]
    return files

def getDirAndFile(filename):
    try:
        i = filename.rindex("/")
        return (MOZ_ROOT + filename[0:i+1], filename[i+1:])
    except ValueError:
        return (None, filename)
    
#dbfilename is the stuff after mozilla, starts wit 
def report(dbfilename, hunk):
    if True == True:
        (dir, filename) = getDirAndFile(dbfilename)
        cwd = os.getcwd()
        os.chdir(dir)
        #get the rev # of that particular line
        revision = os.popen("""cvs annotate '%s' 2> /dev/null""" % (filename), 'r').read().split("\n")[hunk.line_num - 1].split(" ")[0];
        log = os.popen("""cvs log -r%s -bN '%s' 2> /dev/null""" % (revision, filename)).read().split("\n")
        revline = "revision %s" % revision
        # then fine the log entry to get author + commit message
        for line in range(0, len(log)):
            if log[line] != revline:
                continue
            infoline = log[line + 1]
            start = infoline.index(AUTHOR);
            stop = infoline.index(";", start + 1);
            hunk.author = infoline[start + len(AUTHOR):stop].replace("%", "@", 1)
            comment = log[line + 2]
            break
        os.chdir(cwd)
        mail = os.popen("""mail -s "Prbool violation in %s" tglek@mozilla.com""" % filename, 'w') 
        mail.write("""%s introduced a new PRBool problem in revision %s of %s.\n
Commit message: %s\n
Error: %s:
 %s\n
http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla%s&rev=&cvsroot=/cvsroot#%d"""
                   % (hunk.author, revision, filename, comment, dbfilename, hunk, dbfilename, hunk.line_num))
        mail.close()

def diff(oldls, newls):
    # if hunks don't match, mark old file for deletion from db and new one for insertion
    adds = {}
    for newfile in newls.values():
        try:
            oldfile = oldls.pop(newfile.filename)
            new_i = 0
            old_i = 0
            # this doesn't flag anything as an error
            # but ensures that old entries get deleted
            if len(newfile.hunks) != len(oldfile.hunks):
                adds[newfile.filename] = newfile
                oldls[newfile.filename] = oldfile
            while new_i < len(newfile.hunks):
                newhunk = newfile.hunks[new_i]
                new_i = new_i + 1
                if old_i < len(oldfile.hunks):
                    oldhunk = oldfile.hunks[old_i]
                    old_i = old_i + 1
                    if not (newhunk.str == oldhunk.str and newhunk.macro == oldhunk.macro):
                        report(newfile.filename, newhunk)
                        adds[newfile.filename] = newfile
                        oldls[newfile.filename] = oldfile
                else:
                    report(newfile.filename, newhunk)
                    adds[newfile.filename] = newfile
                    oldls[newfile.filename] = oldfile                
        except KeyError:
            for hunk in newfile.hunks:
                report(newfile.filename, hunk)
            adds[newfile.filename] = newfile
    return (adds.values(), oldls.values())

def execSql(filels):
    newls = {}
    for file in filels:
        newls[file.filename] = file
    filels = None
    conn = pgdb.connect()
    cursor = conn.cursor()
    # save old values locally, nuke them, save new values and compare
    oldls = readTable(cursor, "prbool")
    (addls, removels) = diff(oldls, newls)
    insertOrDelete(removels, "prbool", False, cursor);
    insertOrDelete(addls, "prbool", True, cursor);
    cursor.execute("INSERT INTO prbool_stats VALUES((SELECT count(*) from prbool where macro is null), (SELECT count(*) from prbool where macro is not null))") 
    conn.commit()

if __name__=="__main__":
    # make the paths nicer to look at0
    sys.argv = map(os.path.abspath, sys.argv)
    MOZ_ROOT = sys.argv[1]
    ls = makeListOfLines(sys.argv[2])
    ls.extend(makeMacroListOfLines(sys.argv[3]))
    execSql(ls)
