# Copyright (c) 2018-2020, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#
import os
import sys
import base64
import hashlib
from urllib.parse import urlparse, parse_qs, urljoin, urlunsplit

from enum import Enum
from typing import Tuple, List
from urllib import parse


import carb.dictionary
import carb.tokens
import carb.settings
import omni.kit.app
import omni.ext

from .utils import _info, _error, _print, _get_setting


def short_hash(name: str, length: int = 10) -> str:
    hasher = hashlib.sha1(name.encode("utf-8"))
    return base64.urlsafe_b64encode(hasher.digest()[:length]).decode("ascii").rstrip("=")


def _try_post_notification(message):
    try:
        import omni.kit.notification_manager
        omni.kit.notification_manager.post_notification(message)
        return True
    except:
        pass
    return False


def _call_git(args, cwd=None):
    import subprocess

    cmd = ["git"] + args
    _info("run process: {}".format(cmd))
    try:
        res = subprocess.run(cmd, cwd=cwd)
        if res.returncode != 0:
            _error(f"error running process: {cmd}. Result: {res}. Stderr: {res.stderr}")
            return False
        return True
    except FileNotFoundError:
        _error("Failed calling git. 'omni.kit.extpath.git' extension requires git installed and available.")
        _try_post_notification("Can't find git. Please install it to use git scheme for extension search path.")
        return False
    except PermissionError:
        _error("No permission execute git. 'omni.kit.extpath.git' extension requires git installed and available.")
        return False


def _remove_prefix(text, prefix):
    return text[text.startswith(prefix) and len(prefix) :]


class ExtPath:
    def __init__(self, url, cache_path):
        self.local_path = ""

        self.path_sh = short_hash(url)

        parsed = urlparse(url)

        q = parse_qs(parsed.query)
        self.branch = q.get("branch", [""])[0]
        self.tag = q.get("tag", [""])[0]
        self.sha = q.get("sha", [""])[0]
        self.dir = q.get("dir", [""])[0]

        scheme = parsed.scheme
        if scheme == "git":
            scheme = "git+https"
        scheme = _remove_prefix(scheme, "git+")

        self.url = urlunsplit([scheme, parsed.netloc, parsed.path, "", ""])

        self.dest = f"{cache_path}/{self.path_sh}"
        self.rev = self.sha
        if not self.rev:
            self.rev = "origin/" + (self.branch or self.tag or "master")

        if not os.path.exists(self.dest):
            _call_git(["clone", "-q", self.url, self.dest])
            _call_git(["checkout", "-q", self.rev], cwd=self.dest)
            self._update_submodules()

        self.local_path = os.path.join(self.dest, self.dir)
        _info(f"local_path: {self.local_path} for url: {url} is ready to use")

    def update(self):
        if self.sha:
            return
        _print(f"updating git path: {self.url}. rev: {self.rev} into '{self.dest}'...")
        _call_git(["fetch", "-q"], cwd=self.dest)
        _call_git(["reset", "--hard", "-q", self.rev], cwd=self.dest)
        self._update_submodules()

    def _update_submodules(self):
        if not os.path.exists(os.path.join(self.dest, ".gitmodules")):
            return
        _call_git(["submodule", "update", "--init", "--recursive", "-q"], cwd=self.dest)


SCHEMES = ["git", "git+https", "git+http", "git+ssh"]

_git_paths = {}
_cache_path = ""


def update_git_path(path: str):
    """Update checked out git repo branch/tag to latest (git pull) for specified extension path."""
    if path in _git_paths:
        _git_paths[path].update()


def update_all_git_paths():
    """Update checked out git repo branch/tag to latest (git pull) for all extension paths."""
    for p in _git_paths.values():
        p.update()


def get_local_path(path: str):
    if path in _git_paths:
        return _git_paths[path].local_path
    return None


def is_git_path(path: str):
    return path in _git_paths

def get_cache_path() -> str:
    return _cache_path


class ExtPathGitExt(omni.ext.IExt):
    def on_startup(self):
        self._ext_manager = omni.kit.app.get_app().get_extension_manager()

        if not _call_git(["--version"]):
            return

        global _cache_path
        _cache_path = _get_setting("/exts/omni.kit.extpath.git/cachePath", None)
        _cache_path = carb.tokens.get_tokens_interface().resolve(_cache_path)

        self._update = _get_setting("/exts/omni.kit.extpath.git/autoUpdate", False)

        for scheme in SCHEMES:
            self._ext_manager.add_path_protocol_provider(scheme, self._on_add_path, self._on_remove_path)

    def _on_add_path(self, path):
        _info(f"ExtPathGitExt::_on_add_path: {path}")
        p = ExtPath(path, cache_path=_cache_path)
        if self._update:
            p.update()
        global _git_paths
        _git_paths[path] = p
        return p.local_path

    def _on_remove_path(self, path):
        _info(f"ExtPathGitExt::_on_remove_path: {path}")
        _git_paths.pop(path, None)

    def on_shutdown(self):
        for scheme in SCHEMES:
            self._ext_manager.remove_path_protocol_provider(scheme)
        global _git_paths
        _git_paths.clear()
