"""
@author: sumit
"""

import sys
import urllib2
import re
import copy

try:
    import markup
    import mongo

except:
    print __doc__
    sys.exit( 1 )

# Add all the jira story prefixes in this array
VALID_STORY_PREFIXES = ['PB-','PB_','GROW-','GROW_','NO-PB','NO_PB']

class DataFromLog(object):
    """
    The main class to hold the objects from the log file containing all the details with the key as story number.
    The object of this class can be generated by either calling the constructor with all/some of the values or by
    calling getters and setters.
    """

    def __init__(self, **kwargs):
        if kwargs.get('revisionNumber') is not None:
            self.revisionNumber = kwargs.get('revisionNumber')

        if kwargs.get('storyNumber') is not None:
            self.storyNumber = kwargs.get('storyNumber')

        if kwargs.get('userName') is not None :
            self.userName = kwargs.get('userName')

        if kwargs.get('filesModified') is not None :
            self.filesModified = kwargs.get('filesModified')

        if kwargs.get('comment') is not None :
            self.comment = kwargs.get('comment')

    def setRevisionNumber(self, value):
        self.revisionNumber = value

    def getRevisionNumber(self):
        return self.revisionNumber

    def setStoryNumber(self, value):
        self.storyNumber = value

    def getStoryNumber(self):
        return self.storyNumber

    def setUserName(self, value):
        self.userName = value

    def getUserName(self):
        return self.userName

    def setFilesModified(self, value):
        self.filesModified = value

    def getFilesModified(self):
        return self.filesModified

    def setComments(self, value):
        self.comment = value

    def getComments(self):
        return self.comment


class GenerateHTML(object):

    def __init__(self, **kwargs):

        if kwargs.get('url') is not None :
            self.url = kwargs.get('url')

    def _compare_keys(self, x, y):
        """
        This function is used to extract the numeric value from the Story Number and compare it in the dict to return sorted elements
        """
        if x != 'NO-PB' and x != 'NO_PB':
            x = re.search('\d+', x).group(0)

        if y != 'NO-PB' and y != 'NO_PB':
            y = re.search('\d+', y).group(0)

        try:
            x = int(x)
        except ValueError:
            xint = False
        else:
            xint = True
        try:
            y = int(y)
        except ValueError:
            if xint:
                return -1
            return cmp(x.lower(), y.lower())
            # or cmp(x, y) if we want case sensitivity.
        else:
            if xint:
                return cmp(x, y)
            return 1

    def generateHTMLView (self, logObjectDict, isDetailed = None) :
        """
        This function generates the HTML view from the logObject.
        It generates the summary view by default.
        If the isDetailed flag is passed as TRUE then only it generates the detailed HTML view.
        """
        page = markup.page(case='lower')
        page.init(title="SVN Revision History", header="", footer="")

        js = """function toggleExpansion(elementIdStr) {
                 if ("none" == document.getElementById(elementIdStr + "-List").style.display) {
                     //alert ("one");
                     document.getElementById(elementIdStr + "-ExOrNot").innerHTML = "[-]";
                     document.getElementById(elementIdStr + "-List").style.display = "block";
                 } else {
                     //alert ("two");
                     document.getElementById(elementIdStr + "-ExOrNot").innerHTML = "[+]";
                     document.getElementById(elementIdStr + "-List").style.display = "none";
                 }
             }"""

        css = """body
                 {
                     padding-left: 25px;
                     padding-top: 15px;
                 }
                 div.description
                 {
                     border-bottom: 1px solid;
                     border-left: 1px solid;
                     border-right: 1px solid;
                     border-top: 1px dashed;
                     border-color: #6d6f78;
                     background: #eeece1;
                     padding: 5px;
                     width: 980px;
                 }
                 div.storyNumber
                 {
                     font-weight: bold;
                     border-left: 1px solid;
                     border-right: 1px solid;
                     border-top: 1px solid;
                     border-color: blue;
                     background: #c8ccd9;
                     padding: 5px;
                     width: 980px;
                 }"""

        page.script(js, type="text/javascript")
        page.style(css)
        page.div()

        if isDetailed:
            page.h1("Revision History - Detailed View")
        else:
            page.h1("Revision History - Summary View")
        page.div.close( )
        NOPBStories = []

        for pbStory in sorted(logObjectDict, cmp=self._compare_keys):
            # Generate a empty div for gap
            page.div()
            page.p(" ")
            page.div.close()

            # Remove the NO-PB/NO_PB sotries from the list and store it in temp object to use later
            if (pbStory == "NO-PB" or pbStory == "NO_PB") :
                NOPBStories = logObjectDict[pbStory]
                del logObjectDict[pbStory]
                continue

            # Loop through rest objects and generate HTML view
            logObjectArray = logObjectDict[pbStory]
            jiraStoryNumber = logObjectArray[0].getStoryNumber()
            storyLink = '<a href="http://jira.corp.pinger.com/browse/' + jiraStoryNumber + '" target="_blank">' + jiraStoryNumber + '</a>'
            page.div(storyLink, id = pbStory, class_ = 'storyNumber')
            page.div(class_ = 'description')
            self.generateHtmlList(page, logObjectArray, isDetailed)
            page.div.close()
            page.div.close()

        # Put all the NO-PB/NO_PB stories here
        if (len(NOPBStories) > 0) :
            page.div()
            page.p(" ")
            page.div.close()
            page.div("NO-PB", id = "NO-PB", class_ = 'storyNumber')
            page.div(class_ = 'description')
            self.generateHtmlList(page, NOPBStories, isDetailed)
            page.div.close()
            page.div.close()
        print page


    def generateHtmlList (self, page, logObjectArray, isDetailed=False, ul_class=None, li_class=None) :
        """
        This function generates the list view per story.
        Based on the detailed view if requested, it also generates a list view of all the files that have been modified.
        """
        page.ul()
        for x in range(0, len(logObjectArray)):

            page.li("<strong>Revision</strong> " + logObjectArray[x].getRevisionNumber() + ' by ' + logObjectArray[x].getUserName() + " : ")
            page.ul()
            page.li("<strong>Check-in description: </strong>" + logObjectArray[x].getComments())

            if isDetailed :
                id = logObjectArray[x].getRevisionNumber()
                page.li('<b id="' + id + '-ExOrNot" onclick="toggleExpansion(&#39;' + id + '&#39;)" > [-]</b> File check-ins')
                page.ul(id = id + "-List")
                fileArray = logObjectArray[x].getFilesModified()
                for file in fileArray :
                    page.li(file)
                page.ul.close()

            page.ul.close()

        page.ul.close()

    def generateSummaryView(self, logObjectDict):
        """
        This function generates the summary HTML view for the stories.
        """
        self.generateHTMLView(logObjectDict)

    def generateDetailedView(self, logObjectDict):
        """
        This function generates the detailed HTML view for the stories.
        """
        self.generateHTMLView(logObjectDict, 'TRUE')

class ParseSVNLog(object):
    """
    This class which reads the svn log file line by line, parse it and store the information needed based on the story number.
    This can be used to either use a file from SVN or from a local file.
    """

    def openFileFromURL(self, svnFilePath):
        # take input
        username = "svn_username"
        password = "svn_password"

        # create a password manager
        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        # Add the username and password.
        password_mgr.add_password(None, svnFilePath, username, password)

        handler = urllib2.HTTPBasicAuthHandler(password_mgr)

        # create "opener" (OpenerDirector instance)
        opener = urllib2.build_opener(handler)

        # use the opener to fetch a URL
        opener.open(svnFilePath)

        # Install the opener.
        # Now all calls to urllib2.urlopen use our opener.
        urllib2.install_opener(opener)

        response = urllib2.urlopen(svnFilePath)
        file = response.read()
        return file

    def parseLog(self, isDetailed=None, svnFilePath=None):

        if svnFilePath is not None:
            file = self.openFileFromURL(svnFilePath)
        else:
            file = open("svn_log.txt","r") # The file containing the output of "svn log"

        # Initialize the counters and objects
        counter         = 0
        fileCounter     = 0
        fileArray       = []
        startComment    = 0
        comment         = ''
        isThisFirstLine = 0
        logObject       = DataFromLog()
        logObjectDict   = dict()

        for line in file:
            if ((line.find("------------------------------------------------------------------------") > -1) and isThisFirstLine == 1):
                # Extract all the story numbers
                stories = self.getAllStories(comment)
                logObject.setFilesModified(fileArray)
                logObject.setComments(comment)

                # Loop through all the stories and put the logObject in the list
                for pbStory in stories:
                    logObject.setStoryNumber(pbStory)
                    storyNumber = self.extractStoryNumber(pbStory)
                    if (pbStory in logObjectDict.keys()):
                        revisionArray = logObjectDict[pbStory]
                        logObjectCopy = copy.deepcopy(logObject)
                        revisionArray.append(logObjectCopy)
                        logObjectDict[pbStory] = revisionArray

                    else:
                        logObjectArray = []
                        logObjectCopy = copy.deepcopy(logObject)
                        logObjectArray.append(logObjectCopy)
                        logObjectDict[pbStory] = logObjectArray

                # Reset flags and counters
                counter = 0
                fileCounter = 0
                fileArray[:] = []
                startComment = 0
                comment = ''
                logObject.setStoryNumber('')
                logObject.setRevisionNumber('')
                logObject.setUserName('')
                logObject.setFilesModified('')
                logObject.setComments('')

                continue

            # To ignore the first ---- line
            if isThisFirstLine == 0:
                isThisFirstLine = 1
                continue

            if (fileCounter > 0) and (line in ['\n', '\r\n']):
                startComment = 1
                fileCounter = 0

            if line in ['\n', '\r\n']:
                continue

            # Set revision and user name
            if counter == 0:
                revision = line.split()[0]
                name = line.split()[2]
                logObject.setRevisionNumber(revision)
                logObject.setUserName(name)

            if counter == 2:
                fileCounter = 1

            # Set files modified
            if fileCounter > 0:
                fileArray.append(line)

            # Set comments
            if startComment == 1:
                comment = comment + line
            counter += 1

        # Generate HTML page based on the type of view requested
        generateReportObj = GenerateHTML()
        if isDetailed:
            generateReportObj.generateDetailedView(logObjectDict)
        else:
            generateReportObj.generateSummaryView(logObjectDict)

    def getAllStories(self, comment):
        """
        This function is used to extract the story numbers from the comments and return them as an array.
        """
        storyArray = []

        comment = comment.upper()
        for prefix in VALID_STORY_PREFIXES:
            count = comment.count(prefix)
            for x in range(0, count):
                try:
                    match = re.search('(?<=' + prefix + ')\d+', comment).group(0)
                    storyArray.append(prefix + match)
                    comment = comment.replace(prefix + match, '')
                except AttributeError:
                    if prefix == 'NO-PB' or prefix == 'NO_PB':
                        storyArray.append(prefix)
                    continue
        return storyArray

    def extractStoryNumber(self, pbStory):
        """
        This function extracts and returns the numeric part of the story number
        """
        if pbStory != 'NO-PB' and pbStory != 'NO_PB':
            storyNumber = re.search('\d+', pbStory)
            return storyNumber.group(0)
        else:
            return "NO-PB"

if __name__ == '__main__':

    parseSVNLogObject = ParseSVNLog()
    if ((len(sys.argv) > 1) and (sys.argv[1] == "summary") or len(sys.argv)==1):
        parseSVNLogObject.parseLog()
    elif ((len(sys.argv) > 1) and (sys.argv[1] == "detailed")):
        parseSVNLogObject.parseLog(True)
    else:
        print sys.argv
        print len(sys.argv)
        print "Wrong Option. Please try again!"
