#
# Small event handler to automatically open URLs and file
# bug reports at a bugzilla of your choiche
#
# This class requires python2.4 because of the urllib2 usage
#

def seppuku_spliturl(url):
    """
    Split GET URL to return the host base and the query
    as a param dictionary
    """
    import urllib
    (uri,query)  = urllib.splitquery(url)
    param = {}
    for par in query.split("&"):
        (key,value) = urllib.splitvalue(par)
        if not key or len(key) == 0 or not value:
            continue
        key = urllib.unquote(key)
        value = urllib.unquote(value)
        param[key] = value

    return (uri,param)



def seppuku_login(opener, login, user, password):
    """
    We need to post to query.cgi with the parameters
    Bugzilla_login and Bugzilla_password and will scan
    the resulting page then

    @param opened = cookie enabled urllib2 opener
    @param login = http://bugs.openembedded.org/query.cgi?
    @param user  = Your username
    @param password  = Your password
    """
    import urllib
    param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
    result = opener.open(login + param)

    if result.code != 200:
        return False
    txt = result.read()
    if not '<a href="relogin.cgi">Log&nbsp;out</a>' in txt:
        return False

    return True

def seppuku_find_bug_report_old():
    from HTMLParser import HTMLParser

    class BugQueryExtractor(HTMLParser):
        STATE_NONE             = 0
        STATE_FOUND_TR         = 1
        STATE_FOUND_NUMBER     = 2
        STATE_FOUND_PRIO       = 3
        STATE_FOUND_PRIO2      = 4
        STATE_FOUND_NAME       = 5
        STATE_FOUND_PLATFORM   = 6
        STATE_FOUND_STATUS     = 7
        STATE_FOUND_WHATEVER   = 8 # I don't know this field
        STATE_FOUND_DESCRIPTION =9

        def __init__(self):
            HTMLParser.__init__(self)
            self.state = self.STATE_NONE
            self.bugs = []
            self.bug  = None

        def handle_starttag(self, tag, attr):
            if self.state == self.STATE_NONE and tag.lower() == "tr":
                if len(attr) == 1 and attr[0][0] == 'class' and \
                    ('bz_normal' in attr[0][1] or 'bz_blocker' in attr[0][1] or 'bz_enhancement' in attr[0][1] or 'bz_major' in attr[0][1] or 'bz_minor' in attr[0][1] or 'bz_trivial' in attr[0][1] or 'bz_critical' in attr[0][1] or 'bz_wishlist' in attr[0][1]) \
                    and 'bz_P' in attr[0][1]:
                    self.state = self.STATE_FOUND_TR
            elif self.state == self.STATE_FOUND_TR and tag.lower() == "td":
                self.state += 1

        def handle_endtag(self, tag):
            if tag.lower() == "tr":
                if self.state != self.STATE_NONE:
                    self.bugs.append( (self.bug,self.status) )
                self.state = self.STATE_NONE
                self.bug  = None
            if self.state > 1 and tag.lower() == "td":
                self.state += 1

        def handle_data(self,data):
            data = data.strip()

            # skip garbage
            if len(data) == 0:
                return

            if self.state == self.STATE_FOUND_NUMBER:
                """
                #1995 in bugs.oe.org has [SEC] additionally to the number and we want to ignore it
                """
                if not self.bug:
                    self.bug = data
            elif self.state == self.STATE_FOUND_STATUS:
                self.status = data

        def result(self):
            return self.bugs

    return BugQueryExtractor()



def seppuku_find_bug_report(debug_file, opener, query, product, component, bugname):
    """
    Find a bug report with the sane name and return the bug id
    and the status.

    @param opener = urllib2 opener
    @param query  = e.g. http://bugs.openembedded.org/query.cgi?
    @param product = search for this product
    @param component = search for this component
    @param bugname = the bug to search for

    http://bugs.openembedded.org/buglist.cgi?short_desc_type=substring&short_desc=manual+test+bug&product=Openembedded&emailreporter2=1&emailtype2=substring&email2=freyther%40yahoo.com
    but it does not support ctype=csv...
    """
    import urllib
    product   = urllib.quote(product)
    component = urllib.quote(component)
    bugname   = urllib.quote(bugname)

    file = "%(query)sproduct=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
    print >> debug_file, "Trying %s" % file
    result = opener.open(file)
    if result.code != 200:
        raise "Can not query the bugzilla at all"
    txt = result.read()
    scanner = seppuku_find_bug_report_old()
    scanner.feed(txt)
    if len(scanner.result()) == 0:
        print >> debug_file, "Scanner failed to scan the html site"
        print >> debug_file, "%(query)sproduct=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
        print >> debug_file, txt
        return (False,None)
    else: # silently pick the first result
        print >> debug_file, "Result of bug search is "
        print >> debug_file, txt
        (number,status) = scanner.result()[0]
        return (not status in ["CLOS", "RESO", "VERI"],number)

def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
    """
    Reopen a bug report and append to the comment

    Same as with opening a new report, some bits need to be inside the url

    http://bugs.openembedded.org/process_bug.cgi?id=239&bug_file_loc=http%3A%2F%2F&version=Angstrom&longdesclength=2&product=Openembedded&component=Build&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other&knob=reopen&short_desc=foo
    """

    import urllib2
    (uri, param) = seppuku_spliturl( file )

    # Prepare the post
    param["product"]        = product
    param["component"]      = component
    param["longdesclength"] = 2
    param["short_desc"]     = bugname
    param["knob"]           = "reopen"
    param["id"]             = bug_number
    param["comment"]        = text

    try:
        result = poster.open( uri, param )
    except urllib2.HTTPError, e:
        print e.geturl()
        print e.info()
        return False
    except Exception, e:
        print e
        return False

    if result.code != 200:
        return False
    else:
        return True

def seppuku_file_bug(poster, file, product, component, bugname, text):
    """
    Create a completely new bug report


    http://bugs.openembedded.org/post_bug.cgi?bug_file_loc=http%3A%2F%2F&version=Angstrom&product=Openembedded&component=Build&short_desc=foo&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other

    You are forced to add some default values to the bugzilla query and stop with '&'

    @param opener  urllib2 opener
    @param file    The url used to file a bug report
    @param product Product
    @param component Component
    @param bugname  Name of the to be created bug
    @param text Text
    """

    import urllib2
    (uri, param) = seppuku_spliturl( file )
    param["product"]    = product
    param["component"]  = component
    param["short_desc"] = bugname
    param["comment"]    = text

    try:
        result = poster.open( uri, param )
    except urllib2.HTTPError, e:
        print e.geturl()
        print e.info()
        return False
    except Exception, e:
        print e
        return False

    # scan the result for a bug number
    # it will look like 
    # '<title>Bug 2742 Submitted</title>'
    import re
    res = re.findall(("\>Bug (?P<int>\d+) Submitted"), result.read() )
    if result.code != 200 or len(res) != 1:
        return None 
    else:
        return res[0] 

def seppuku_create_attachment(debug, poster, attach_query, product, component, bug_number, text, file):
    """

    Create a new attachment for the failed report
    """

    if not bug_number:
        import bb
        bb.note("Can't create an attachment, no bugnumber passed to method")
        return False

    import urllib2
    param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }

    try:
        result = poster.open( attach_query, param )
    except urllib2.HTTPError, e:
        print e.geturl()
        print e.info()
        return False
    except Exception, e:
        print e
        return False

    print >> debug, result.read()
    if result.code != 200:
        return False
    else:
        return True


addhandler seppuku_eventhandler
python seppuku_eventhandler() {
    """
    Report task failures to the bugzilla
    and succeeded builds to the box
    """
    from bb.event import NotHandled, getName
    from bb import data, mkdirhier, build
    import bb, os, glob

    event = e
    data = e.data
    name = getName(event)
    if name == "MsgNote":
       # avoid recursion
       return NotHandled

    # Try to load our exotic libraries
    try:
        import MultipartPostHandler
    except:
        bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
        return NotHandled

    try:
        import urllib2, cookielib
    except:
        bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
        return NotHandled

    if name == "PkgFailed":
        if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
            build.exec_task('do_clean', data)
    elif name == "TaskFailed":
        cj = cookielib.CookieJar()
        opener  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
        poster  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
        login   = bb.data.getVar("SEPPUKU_LOGIN", data, True)
        query   = bb.data.getVar("SEPPUKU_QUERY", data, True)
        newbug  = bb.data.getVar("SEPPUKU_NEWREPORT",  data, True)
        reopen  = bb.data.getVar("SEPPUKU_ADDCOMMENT",  data, True)
        attach  = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
        user    = bb.data.getVar("SEPPUKU_USER",  data, True)
        passw   = bb.data.getVar("SEPPUKU_PASS",  data, True)
        product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
        component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
        # evil hack to figure out what is going on
        debug_file = open(os.path.join(bb.data.getVar("TMPDIR", data, True),"..","seppuku-log"),"a")

        if not seppuku_login(opener, login, user, passw):
            bb.note("Login to bugzilla failed")
            print >> debug_file, "Login to bugzilla failed"
            return NotHandled
        else:
            print >> debug_file, "Logged into the box"

        file = None
        if name == "TaskFailed":
            bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
                                                               "pv"      : bb.data.getVar("PV", data, True),
                                                               "pr"      : bb.data.getVar("PR", data, True),
                                                               "task"    : e.task }
            log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
            text     = "The package failed to build at %s for machine %s" % (bb.data.getVar('DATETIME', data, True), bb.data.getVar( 'MACHINE', data, True ) )
            if len(log_file) != 0:
                print >> debug_file, "Adding log file %s" % log_file[0]
                file = open(log_file[0], 'r')
            else:
                print >> debug_file, "No log file found for the glob"
        else:
            print >> debug_file, "Unknown name '%s'" % name
            assert False

        (bug_open, bug_number) = seppuku_find_bug_report(debug_file, opener, query, product, component, bugname)
        print >> debug_file, "Bug is open: %s and bug number: %s" % (bug_open, bug_number)

        # The bug is present and still open, attach an error log
        if bug_number and bug_open:
            print >> debug_file, "The bug is known as '%s'" % bug_number
            if file:
                if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
                     print >> debug_file, "Failed to attach the build log for bug #%s" % bug_number
                else:
                     print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
            else:
                     print >> debug_file, "Not trying to create an attachment for bug #%s" % bug_number
            return NotHandled

        if bug_number and not bug_open:
            if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
                print >> debug_file, "Failed to reopen the bug #%s" % bug_number
            else:
                print >> debug_file, "Reopened the bug #%s" % bug_number
        else:	
            bug_number = seppuku_file_bug(poster, newbug, product, component, bugname, text)
            if not bug_number:
                print >> debug_file, "Couldn't acquire a new bug_numer, filing a bugreport failed"
            else:
                print >> debug_file, "The new bug_number: '%s'" % bug_number

        if bug_number and file:
            if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
                print >> debug_file, "Failed to attach the build log for bug #%" % bug_number
            else:
                print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
        else:
            print >> debug_file, "Not trying to create an attachment for bug #%" % bug_number

    return NotHandled
}