import urllib2

from reviewboard.diffviewer.parser import DiffParser, DiffParserError
from reviewboard.scmtools.core import \
    FileNotFoundError, SCMTool, HEAD, PRE_CREATION

class HgTool(SCMTool):
    def __init__(self, repository):
        SCMTool.__init__(self, repository)
        if repository.path.startswith('http'):
            self.client = HgWebClient(repository.path,
                                      repository.username,
                                      repository.password)
        else:
            self.client = HgClient(repository.path)

    def get_file(self, path, revision=HEAD):
        return self.client.cat_file(path, str(revision))

    def parse_diff_revision(self, file_str, revision_str):
        revision = revision_str
        if file_str == "/dev/null":
            revision = PRE_CREATION
        return file_str, revision

    def get_diffs_use_absolute_paths(self):
        return True

    def get_fields(self):
        return ['diff_path']

    def get_parser(self, data):
        return HgDiffParser(data)


class HgDiffParser(DiffParser):
    """
    This class is able to extract Mercurial changeset ids, and
    replaces /dev/null with a useful name
    """

    def parse_special_header(self, linenum, info):
        if self.lines[linenum].startswith("diff -r"):
            # should contain "diff -r aaa [-r bbb] filename"
            diffLine = self.lines[linenum].split()
            try:
                # hg is file based, so new file always == old file
                info['newFile'] = info['origFile'] = diffLine[-1]
                info['origInfo'] = diffLine[2]
                if len(diffLine) <= 4:
                    info['newInfo'] = "Uncommitted"
                else:
                    info['newInfo'] = diffLine[4]
            except ValueError:
                raise DiffParserError("The diff file is missing revision information",
                                      linenum)
            linenum += 1;

        return linenum

    def parse_diff_header(self, linenum, info):
        if linenum <= len(self.lines) and \
           self.lines[linenum].startswith("Binary file "):
            info['binary'] = True
            linenum += 1

        if linenum + 1 < len(self.lines) and \
           (self.lines[linenum].startswith('--- ') and \
             self.lines[linenum + 1].startswith('+++ ')):
            # check if we're a new file
            if self.lines[linenum].split()[1] == "/dev/null":
                info['origInfo'] = PRE_CREATION
            linenum += 2;
        return linenum


class HgWebClient:
    def __init__(self, repoPath, username, password):
        self.url = repoPath
        self.username = username
        self.password = password

    def cat_file(self, path, rev="tip"):
        if rev == HEAD:
            rev = "tip"
        elif rev == PRE_CREATION:
            rev = ""
        try:
            passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
            passman.add_password(None, self.url, self.username, self.password)
            authhandler = urllib2.HTTPBasicAuthHandler(passman)
            opener = urllib2.build_opener(authhandler)
            f = opener.open('%s/raw/%s/%s' % (self.url, rev, path))
            return f.read()
        except Exception, e:
            raise FileNotFoundError(path, rev, str(e))

    def get_filenames(self, rev):
        raise NotImplemented


class HgClient:
    def __init__(self, repoPath):
        from mercurial import hg, ui
        self.repo = hg.repository(ui.ui(interactive=False), path=repoPath)

    def cat_file(self, path, rev="tip"):
        if rev == HEAD:
            rev = "tip"
        elif rev == PRE_CREATION:
            rev = ""
        try:
            return self.repo.changectx(rev).filectx(path).data()
        except Exception, e:
            # LookupError moves from repo to revlog in hg v0.9.4, so we
            # catch the more general Exception to avoid the dependency.
            raise FileNotFoundError(path, rev, str(e))

    def get_filenames(self, rev):
        return self.repo.changectx(rev).TODO

@@ -1247,32 +1247,79 @@
 linenum, info):%0A
+        # XXX: does not handle git style diffs%0A
         if self.
@@ -1374,44 +1374,230 @@
   # 
-should contain %22diff -r aaa %5B-r bbb%5D
+diff between two revisions are in the following form:%0A            #  %22diff -r abcdef123456 -r 123456abcdef filename%22%0A            # diff between a revision and the working copy are like:%0A            #  %22diff -r abcdef123456
  fil
@@ -1759,57 +1759,138 @@
    i
-nfo%5B'newFile'%5D = info%5B'origFile'%5D = diffLine%5B-1%5D%0A
+sCommitted = len(diffLine) %3E 4 and diffLine%5B3%5D == '-r'%0A                if isCommitted:%0A                    nameStartIndex = 5%0A    
     
@@ -1899,36 +1899,35 @@
           info%5B'
-orig
+new
 Info'%5D = diffLin
@@ -1928,17 +1928,17 @@
 iffLine%5B
-2
+4
 %5D%0A      
@@ -1951,30 +1951,52 @@
     
-if len(diffLine) %3C= 4:
+else:%0A                    nameStartIndex = 3
 %0A   
@@ -2064,43 +2064,129 @@
     
-else:%0A                    info%5B'new
+info%5B'newFile'%5D = info%5B'origFile'%5D = %5C%0A                    ' '.join(diffLine%5BnameStartIndex:%5D)%0A                info%5B'orig
 Info
@@ -2191,33 +2191,33 @@
 fo'%5D = diffLine%5B
-4
+2
 %5D%0A            ex
@@ -3614,16 +3614,21 @@
 ('%25s/raw
+-file
 /%25s/%25s' 
@@ -3628,16 +3628,44 @@
 %25s/%25s' %25
+%0A                           
  (self.u
@@ -3677,14 +3677,28 @@
 ev, 
+urllib.quote(
 path))
+)
 %0A   

Enable support for paths with spaces in Mercurial (hg) diff parser