#### Jade Application Kit
# * https://codesardine.github.io/Jade-Application-Kit
# * Vitor Lopes Copyright (c) 2016 - 2020
# * https://vitorlopes.me
import os
from functools import lru_cache as cache
from JAK.Utils import check_url_rules, get_current_path, bindings
from JAK.Widgets import Dialog
from JAK.RequestInterceptor import Interceptor
if bindings() == "PyQt5":
    from PyQt5.QtCore import QUrl, Qt
    from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler
    from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings
else:
    from PySide2.QtCore import QUrl, Qt
    from PySide2.QtWebEngineCore import QWebEngineUrlSchemeHandler
    from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings


@cache(maxsize=5)
def validate_url(self, url: str) -> None:
    """
    * Check if is a URL or HTML and if is valid
    * :param self: QWebEnginePage
    * :param web_contents: URL or HTML
    """
    if "!doctype" in url.lower():
        # Inject HTML
        base_url = get_current_path()
        self.setHtml(url, QUrl(f"file://{base_url}/"))
        print("Loading local HTML")
    else:
        if url.endswith(".html"):
            # HTML file
            if not url.startswith("/"):
                url = f"/{url}"
            url = f"file://{url}"

        elif "://" not in url:
            # HTML URL
            url = f"https://{url}"

        url = QUrl(url)
        if url.isValid():
            self.load(url)
            print(f"Loading URL:{url.toString()}")


class IpcSchemeHandler(QWebEngineUrlSchemeHandler):
    def __init__(self):
        super().__init__()

    def requestStarted(self, request):
        url = request.requestUrl().toString()
        if url.startswith("ipc:"):
            # * Link's that starts with [ ipc:somefunction() ] trigger's the two way communication system between
            # HTML and Python, only if online is set to false
            from JAK.IPC import Communication
            Communication.send(url)
            return


class JWebPage(QWebEnginePage):
    """ #### Imports: from JAK.WebEngine import JWebPage """
    def __init__(self, profile, webview, config):
        self.config = config
        super(JWebPage, self).__init__(profile, webview)
        self.featurePermissionRequested.connect(self._on_feature_permission_requested)

    def _open_in_browser(self) -> None:
        """ Open url in a external browser """
        print("Open above^ tab in Browser")
        from webbrowser import open_new_tab
        open_new_tab(self.url)

    def _dialog_open_in_browser(self) -> None:
        """ Opens a dialog to confirm if user wants to open url in external browser """
        msg = "Open In Your Browser"
        Dialog.question(self.parent(), self.title(), msg, self._open_in_browser)

    @cache(maxsize=10)
    def acceptNavigationRequest(self, url, _type, is_main_frame) -> bool:
        """
        * Decide if we navigate to a URL
        * :param url: QtCore.QUrl
        * :param _type: QWebEnginePage.NavigationType
        * :param is_main_frame:bool
        """
        self.url = url.toString()
        self.page = JWebPage(self.profile(), self.view(), self.config)
        # Redirect new tabs to same window
        self.page.urlChanged.connect(self._on_url_changed)

        if self.config['webview']["online"]:
            if _type == QWebEnginePage.WebWindowType.WebBrowserTab:
                if self.config['webview']["urlRules"]:
                    # Check for URL rules on new tabs
                    if self.url.startswith(self.config['webview']["urlRules"]["WebBrowserTab"]):
                        self.open_window(self.url)
                        return False
                    elif check_url_rules("WebBrowserTab", self.url, self.config['webview']["urlRules"]):
                        print(f"Redirecting WebBrowserTab^ to same window")
                        return True
                    else:
                        print(f"Deny WebBrowserTab:{self.url}")
                        # check against WebBrowserWindow list to avoid duplicate dialogs
                        if not check_url_rules("WebBrowserWindow", self.url, self.config['webview']["urlRules"]):
                            self._dialog_open_in_browser()
                        return False
                else:
                    return True

            elif _type == QWebEnginePage.WebBrowserBackgroundTab:
                print(f"WebBrowserBackgroundTab request:{self.url}")
                return True

            elif _type == QWebEnginePage.WebBrowserWindow:
                if self.config['webview']["urlRules"] and self.config['webview']["online"]:
                    # Check URL rules on new windows
                    if check_url_rules("WebBrowserWindow", self.url, self.config['webview']["urlRules"]):
                        print(f"Deny WebBrowserWindow:{self.url}")
                        self._dialog_open_in_browser()
                        return False
                    else:
                        print(f"Allow WebBrowserWindow:{self.url}")
                        return True
                else:
                    return True

            elif _type == QWebEnginePage.WebDialog:
                return True
        return True

    def _on_feature_permission_requested(self, security_origin, feature):

        def grant_permission():
            self.setFeaturePermission(security_origin, feature, self.PermissionGrantedByUser)

        def deny_permission():
            self.setFeaturePermission(security_origin, feature, self.PermissionDeniedByUser)

        if feature == self.Notifications:
            grant_permission()
        elif feature == self.MediaAudioVideoCapture and self.config['webview']["MediaAudioVideoCapture"]:
            grant_permission()
        elif feature == self.MediaVideoCapture and self.config['webview']["MediaVideoCapture"]:
            grant_permission()
        elif feature == self.MediaAudioCapture and self.config['webview']["MediaAudioCapture"]:
            grant_permission()
        elif feature == self.Geolocation and self.config['webview']["Geolocation"]:
            grant_permission()
        elif feature == self.MouseLock and self.config['webview']["MouseLock"]:
            grant_permission()
        elif feature == self.DesktopVideoCapture and self.config['webview']["DesktopVideoCapture"]:
            grant_permission()
        elif feature == self.DesktopAudioVideoCapture and self.config['webview']["DesktopAudioVideoCapture"]:
            grant_permission()
        else:
            deny_permission()

    def open_window(self, url):
        """ Open a New Window"""
        # FIXME cookies path needs to be declared for this to work
        self.popup = JWebView(self.config)
        self.popup.page().windowCloseRequested.connect(self.popup.close)
        self.popup.show()
        print(f"Opening New Window^")

    @cache(maxsize=2)
    def createWindow(self, _type: object) -> QWebEnginePage:
        """
        * Redirect new window's or tab's to same window
        * :param _type: QWebEnginePage.WebWindowType
        """
        return self.page

    def _on_url_changed(self, url: str) -> None:
        url = url.toString()
        if url == "about:blank":
            return False
        else:
            validate_url(self, url)


class JWebView(QWebEngineView):
    """ #### Imports: from JAK.WebEngine import JWebView """
    def __init__(self, config):
        self.config = config
        super(JWebView, self).__init__()
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.profile = QWebEngineProfile.defaultProfile()
        self.webpage = JWebPage(self.profile, self, config)
        self.setPage(self.webpage)
        if config['webview']["injectJavaScript"]["JavaScript"]:
            self._inject_script(config['webview']["injectJavaScript"])
        self.interceptor = Interceptor(config)

        if config['webview']["userAgent"]:
            # Set user agent
            self.profile.setHttpUserAgent(config['webview']["userAgent"])

        if config["debug"]:
            self.settings().setAttribute(QWebEngineSettings.XSSAuditingEnabled, True)
        else:
            self.setContextMenuPolicy(Qt.PreventContextMenu)

        if config['window']["transparent"]:
            # Activates background transparency
            self.setAttribute(Qt.WA_TranslucentBackground)
            self.page().setBackgroundColor(Qt.transparent)
            print("Transparency detected")

        # * Set Engine options
        self.settings().setAttribute(self.config['webview']['disabledSettings'], False)
        for setting in self.config['webview']['enabledSettings']:
            self.settings().setAttribute(setting, True)

        if config['webview']["online"]:
            self.settings().setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
            print("Engine online IPC and Bridge Disabled")
            self.page().profile().downloadRequested.connect(self._download_requested)

            # Set persistent cookies
            self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies)

            # set cookies on user folder
            if config['webview']["cookiesPath"]:
                # allow specific path per application.
                _cookies_path = f"{os.getenv('HOME')}/.jak/{config['webview']['cookiesPath']}"
            else:
                # use separate cookies database per application
                title = config['window']["title"].lower().replace(" ", "-")
                _cookies_path = f"{os.getenv('HOME')}/.jak/{title}"

            self.profile.setPersistentStoragePath(_cookies_path)
            print(f"Cookies PATH:{_cookies_path}")
        else:
            self.settings().setAttribute(QWebEngineSettings.ShowScrollBars, False)
            application_script = "const JAK = {};"

            if config['webview']["IPC"]:
                print("IPC Active:")
                self._ipc_scheme_handler = IpcSchemeHandler()
                self.profile.installUrlSchemeHandler('ipc'.encode(), self._ipc_scheme_handler)
                application_script += """JAK.IPC = function(backendFunction) {
                            window.location.href = "ipc:" + backendFunction;
                        };"""

            if config['webview']["webChannel"]["active"]:
                if bindings() == "PyQt5":
                    from PyQt5.QtCore import QFile, QIODevice
                    from PyQt5.QtWebChannel import QWebChannel
                else:
                    from PySide2.QtCore import QFile, QIODevice
                    from PySide2.QtWebChannel import QWebChannel

                webchannel_js = QFile(':/qtwebchannel/qwebchannel.js')
                webchannel_js.open(QIODevice.ReadOnly)
                webchannel_js = bytes(webchannel_js.readAll()).decode('utf-8')
                webchannel_js += """new QWebChannel(qt.webChannelTransport, function (channel) {
                                        JAK.Bridge = channel.objects.Bridge;
                                    });"""

                application_script += webchannel_js
                self._inject_script({"JavaScript": application_script, "name": "JAK"})
                channel = QWebChannel(self.page())
                if config['webview']["webChannel"]["sharedOBJ"]:
                    bridge_obj = config['webview']["webChannel"]["sharedOBJ"]
                else:
                    raise NotImplementedError("QWebChannel shared QObject")

                channel.registerObject("Bridge", bridge_obj)
                self.page().setWebChannel(channel)
                print("WebChannel Active:")
            else:
                self._inject_script({"JavaScript": application_script, "name": "JAK"})

        self.profile.setRequestInterceptor(self.interceptor)
        print(self.profile.httpUserAgent())
        validate_url(self, config['webview']["webContents"])

    def _inject_script(self, script: dict):
        from JAK.Utils import JavaScript
        JavaScript.inject(self.page(), script)

    def dropEvent(self, *args):
        # disable drop event
        pass

    def _download_requested(self, download_item) -> None:
        """
        * If a download is requested call a save file dialog
        * :param download_item: file to be downloaded
        """
        if bindings() == "PyQt5":
            from PyQt5.QtWidgets import QFileDialog
        else:
            from PySide2.QtWidgets import QFileDialog
        dialog = QFileDialog(self)
        path = dialog.getSaveFileName(dialog, "Save File", download_item.path())

        if path[0]:
            download_item.setPath(path[0])
            print(f"downloading file to:( {download_item.path()} )")
            download_item.accept()
            self.download_item = download_item
            download_item.finished.connect(self._download_finished)
        else:
            print("Download canceled")

    def _download_finished(self) -> None:
        """
        Goes to previous page and pops an alert informing the user that the download is finish and were to find it
        """
        file_path = self.download_item.path()
        msg = f"File Downloaded to: {file_path}"
        Dialog.information(self, "Download Complete", msg)
