import thread
import os
import uuid
import tempfile
import pygit2
import sys

reload(sys)
sys.setdefaultencoding('utf8')

class Source :
    def __init__(self) :
        self.__repo = None
        self.__branch = None
        self.__credentials = None
        self.__revisions = []

    def __del__(self) :
        if self.__repo :
            self.__repo = None

    def __tree_iterate(self, revision_tree, level, status, subtree) :
        revision_tree_struct = ""
        for revision_tree_entry in revision_tree :
            for level_index in range(0, level) :
                revision_tree_struct += "\t"
            revision_tree_struct += revision_tree_entry.name
            revision_tree_struct += "\t"
            if status :
                revision_tree_struct += status
                revision_tree_struct += "\t"
            try :
                revision_tree_item = self.__repo[revision_tree_entry.id]
            except Exception, e :
                revision_tree_struct += "\n"
                continue
            if revision_tree_item.type == pygit2.GIT_OBJ_TREE :
                revision_tree_struct += "dir"
            revision_tree_struct += "\n"
            if subtree and revision_tree_item.type == pygit2.GIT_OBJ_TREE :
                revision_tree_struct += self.__tree_iterate(revision_tree_item, level + 1, status, subtree)
        return revision_tree_struct

    def __find_treeitem(self, revision_tree, file_path) :
        if not file_path :
            return revision_tree
        this_sub_path = file_path
        if file_path.find('/') >= 0 :
            this_sub_path = file_path[:file_path.find('/')]
            file_path = file_path[file_path.find('/') + 1:]
        else :
            file_path = ""
        for revision_tree_entry in revision_tree :
            if revision_tree_entry.name == this_sub_path :
                revision_tree_item = self.__repo[revision_tree_entry.id]
                if file_path and revision_tree_item.type == pygit2.GIT_OBJ_TREE :
                    return self.__find_treeitem(revision_tree_item, file_path)
                return revision_tree_item

    def information(self) :
        return { "name" : "git source", "version" : "1.1" }

    def initialize(self, git_repo_url, branch, username, password, offline) :
        try :
            if self.__repo :
                raise Exception("Already initialized")
            git_repo_url_with_branch = git_repo_url
            if not branch :
                self.__branch = "master"
            else :
                self.__branch = branch
            git_repo_url_with_branch += "/" + self.__branch
            repo_uuid = str(uuid.uuid3(uuid.NAMESPACE_URL, git_repo_url_with_branch))
            git_file_path = os.path.join(tempfile.gettempdir(), repo_uuid)
            try :
                if offline :
                    raise Exception("Offline repository")
                self.__credentials = pygit2.UserPass(username, password)
                self.__repo = pygit2.clone_repository(git_repo_url, git_file_path, bare=True, checkout_branch=self.__branch, credentials=self.__credentials)
            except :
                try :
                    self.__repo = pygit2.Repository(git_file_path)
                    remote = self.__repo.remotes[0]
                    if remote.url != git_repo_url :
                        self.__repo = None
                        raise Exception("Invalid repository")
                    if not offline :
                        remote.credentials = self.__credentials
                        remote.fetch()
                        if len(self.__repo.listall_branches()) <= 0 :
                            self.__repo = None
                            raise Exception("Invalid repository")
                        branch = self.__branch
                        if not branch :
                            branch = "master"
                        remote_reference = self.__repo.lookup_reference('refs/remotes/origin/%s' % branch)
                        local_reference = self.__repo.lookup_reference('refs/heads/%s' % branch)
                        local_reference.target = remote_reference.target.hex
                except :
                    can_use_offline = False
                    if not offline and self.__repo :
                        can_use_offline = True
                    self.__repo = None
                    if not can_use_offline :
                        if os.path.exists(git_file_path) :
                            raise Exception("Clone repository failed, please remove %s and try again" % git_file_path)
                        raise Exception("Clone repository failed")
                    raise Exception("Clone repository failed, please re-config and use offline mode")
            self.__revisions = []
            for revision in self.__repo.walk(self.__repo.head.target, pygit2.GIT_SORT_TOPOLOGICAL) :
                self.__revisions.append(revision)
            return len(self.__revisions)
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def repository_uuid(self) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            remote = self.__repo.remotes[0]
            git_repo_url = str(remote.url)
            git_repo_url_with_branch = git_repo_url
            if self.__branch :
                git_repo_url_with_branch += "/" + self.__branch
            repo_uuid = str(uuid.uuid3(uuid.NAMESPACE_URL, git_repo_url_with_branch))
            return repo_uuid
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def pull_revisions(self) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            remote = self.__repo.remotes[0]
            remote.credentials = self.__credentials
            remote.fetch()
            branch = self.__branch
            if not branch :
                branch = "master"
            remote_reference = self.__repo.lookup_reference('refs/remotes/origin/%s' % branch)
            local_reference = self.__repo.lookup_reference('refs/heads/%s' % branch)
            local_reference.target = remote_reference.target.hex
            self.__revisions = []
            for revision in self.__repo.walk(self.__repo.head.target, pygit2.GIT_SORT_TOPOLOGICAL) :
                self.__revisions.append(revision)
            return len(self.__revisions)
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def revision_information(self, revision_index) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            if not self.__revisions or revision_index <= 0 or revision_index > len(self.__revisions) :
                raise Exception("Revision %d not found" % revision_index)
            revision = self.__revisions[len(self.__revisions) - revision_index]
            if not revision :
                raise Exception("Revision %d Invalid" % revision_index)
            revision_author = ""
            if revision.author :
                revision_author = revision.author.name
            revision_date = revision.commit_time
            revision_message = revision.message
            return { "author" : revision_author.encode("utf-8"), "date" : str(revision_date), "message" : revision_message.encode("utf-8") }
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def revision_file(self, revision_index, file_path) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            if not self.__revisions or revision_index <= 0 or revision_index > len(self.__revisions) :
                raise Exception("Revision %d not found" % revision_index)
            revision = self.__revisions[len(self.__revisions) - revision_index]
            if not revision :
                raise Exception("Revision %d Invalid" % revision_index)
            if not file_path :
                raise Exception("No filename specified")
            revision_tree_item = self.__find_treeitem(revision.tree, file_path)
            if not revision_tree_item :
                raise Exception("Couldn't find %s in revision %d" % (file_path, revision_index))
            if revision_tree_item.type == pygit2.GIT_OBJ_TREE :
                raise Exception("%s in revision %d is a directory" % (file_path, revision_index))
            return revision_tree_item.data
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def revision_tree(self, revision_index, file_path, subtree) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            if not self.__revisions or revision_index <= 0 or revision_index > len(self.__revisions) :
                raise Exception("Revision %d not found" % revision_index)
            revision = self.__revisions[len(self.__revisions) - revision_index]
            if not revision :
                raise Exception("Revision %d Invalid" % revision_index)
            remote = self.__repo.remotes[0]
            if not file_path :
                return (str(remote.url) + "\tnone\tdir\n").encode("utf-8") + self.__tree_iterate(revision.tree, 1, "none", subtree)
            revision_tree_item = self.__find_treeitem(revision.tree, file_path)
            if not revision_tree_item :
                raise Exception("Couldn't find %s in revision %d" % (file_path, revision_index))
            if revision_tree_item.type == pygit2.GIT_OBJ_TREE :
                return (str(remote.url) + "/" + file_path + "\tnone\tdir\n").encode("utf-8") + self.__tree_iterate(revision_tree_item, 1, "none", subtree)
            raise Exception("%s in revision %d is a file" % (file_path, revision_index))
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)

    def revision_difftree(self, revision_index1, revision_index2, file_path, subtree) :
        try :
            if not self.__repo :
                raise Exception("Not initialized yet")
            if revision_index1 == revision_index2 :
                return self.revision_content(revision_index1, file_path)
            if not self.__revisions or revision_index1 < 0 or revision_index1 > len(self.__revisions) :
                raise Exception("Revision %d not found" % revision_index1)
            if not self.__revisions or revision_index2 < 0 or revision_index2 > len(self.__revisions) :
                raise Exception("Revision %d not found" % revision_index2)
            revision1 = None
            revision2 = None
            if revision_index1 != 0 :
                revision1 = self.__revisions[len(self.__revisions) - revision_index1]
            if revision_index2 != 0 :
                revision2 = self.__revisions[len(self.__revisions) - revision_index2]
            remote = self.__repo.remotes[0]
            if revision_index1 == 0 and revision2 :
                if not file_path :
                    return (str(remote.url) + "\tadded\tdir\n").encode("utf-8") + self.__tree_iterate(revision2.tree, 1, "added", subtree)
                return (file_path + "\tadded\tdir\n").encode("utf-8") + self.__tree_iterate(self.__find_treeitem(revision2.tree, file_path), 1, "added", subtree)
            if revision_index2 == 0 and revision1 :
                if not file_path :
                    return (str(remote.url) + "\tdeleted\tdir\n").encode("utf-8") + self.__tree_iterate(revision1.tree, 1, "deleted", subtree)
                return (file_path + "\tdeleted\tdir\n").encode("utf-8") + self.__tree_iterate(self.__find_treeitem(revision1.tree, file_path), 1, "deleted", subtree)
            if not revision1 :
                raise Exception("Revision %d Invalid" % revision_index1)
            if not revision2 :
                raise Exception("Revision %d Invalid" % revision_index2)
            if not file_path :
                revision_tree_item1 = revision1.tree
                revision_tree_item2 = revision2.tree
                remote = self.__repo.remotes[0]
                file_path = str(remote.url)
            else :
                revision_tree_item1 = self.__find_treeitem(revision1.tree, file_path)
                revision_tree_item2 = self.__find_treeitem(revision2.tree, file_path)
                if revision_tree_item1 and revision_tree_item1.type != pygit2.GIT_OBJ_TREE :
                    raise Exception("%s in revision %d is a file" % (file_path, revision_index1))
                if revision_tree_item2 and revision_tree_item2.type != pygit2.GIT_OBJ_TREE :
                    raise Exception("%s in revision %d is a file" % (file_path, revision_index2))
            if not revision_tree_item1 or len(revision_tree_item1) == 0 :
                return (str(remote.url) + "/" + file_path + "\tadded\tdir\n").encode("utf-8") + self.__tree_iterate(revision_tree_item2, 1, "added", subtree)
            if not revision_tree_item2 or len(revision_tree_item2) == 0 :
                return (str(remote.url) + "/" + file_path + "\tdeleted\tdir\n").encode("utf-8") + self.__tree_iterate(revision_tree_item1, 1, "deleted", subtree)
            revision_tree_diff = revision_tree_item1.diff_to_tree(revision_tree_item2)
            if not revision_tree_diff or len(revision_tree_diff) == 0 :
                return ""
            revision_difftree_struct = file_path + "\tmodified\tdir\n"
            parsed_file_paths = set()
            for revision_diff_patch in revision_tree_diff :
                file_path = revision_diff_patch.old_file_path
                level = 1
                parsed_file_path = ""
                while file_path.find('/') >= 0 :
                    this_sub_path = file_path[:file_path.find('/')]
                    parsed_file_path += this_sub_path
                    if parsed_file_path in parsed_file_paths :
                        file_path = file_path[file_path.find('/') + 1:]
                        level += 1
                        parsed_file_path += "/"
                        continue
                    if subtree or level == 1 :
                        for level_index in range(0, level) :
                            revision_difftree_struct += "\t"
                        revision_difftree_struct += this_sub_path
                        revision_difftree_struct += "\t"
                        if revision_diff_patch.status == 'A' :
                            revision_tree_path1 = self.__find_treeitem(revision_tree_item1, parsed_file_path)
                            if not revision_tree_path1 or revision_tree_path1.type != pygit2.GIT_OBJ_TREE :
                                revision_difftree_struct += "added"
                            else :
                                revision_difftree_struct += "modified"
                        elif revision_diff_patch.status == 'D' :
                            revision_tree_path2 = self.__find_treeitem(revision_tree_item2, parsed_file_path)
                            if not revision_tree_path2 or revision_tree_path2.type != pygit2.GIT_OBJ_TREE :
                                revision_difftree_struct += "deleted"
                            else :
                                revision_difftree_struct += "modified"
                        else :
                            revision_difftree_struct += "modified"
                        revision_difftree_struct += "\t"
                        revision_difftree_struct += "dir"
                        revision_difftree_struct += "\n"
                    file_path = file_path[file_path.find('/') + 1:]
                    level += 1
                    parsed_file_paths.add(parsed_file_path)
                    parsed_file_path += "/"
                if subtree or level == 1 :
                    for level_index in range(0, level) :
                        revision_difftree_struct += "\t"
                    revision_difftree_struct += file_path
                    revision_difftree_struct += "\t"
                    if revision_diff_patch.status == 'A' :
                        revision_difftree_struct += "added"
                    elif revision_diff_patch.status == 'D' :
                        revision_difftree_struct += "deleted"
                    else :
                        revision_difftree_struct += "modified"
                    revision_difftree_struct += "\n"
            return revision_difftree_struct.encode("utf-8")
        except Exception, e :
            error_string = str(e)
            return (-1, error_string)
