import json
import logging
import sys
import webbrowser
from ctypes import windll
from threading import Semaphore

import clr

from webview import _state
from webview.util import (
    DEFAULT_HTML,
    inject_base_uri,
    inject_pywebview,
    interop_dll_path,
    js_bridge_call,
)

clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Collections')
clr.AddReference('System.Threading')

import System.Windows.Forms as WinForms  # noqa: E402
from System import Func, Type  # noqa: E402

clr.AddReference(interop_dll_path('WebBrowserInterop.dll'))
from WebBrowserInterop import IWebBrowserInterop, WebBrowserEx  # noqa: E402

logger = logging.getLogger('pywebview')
settings = {}

renderer = 'mshtml'


def _set_ie_mode():
    """
    By default hosted IE control emulates IE7 regardless which version of IE is installed. To fix this, a proper value
    must be set for the executable.
    See http://msdn.microsoft.com/en-us/library/ee330730%28v=vs.85%29.aspx#browser_emulation for details on this
    behaviour.
    """

    import winreg

    def get_ie_mode():
        """
        Get the installed version of IE
        :return:
        """
        ie_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Internet Explorer')
        try:
            version, _ = winreg.QueryValueEx(ie_key, 'svcVersion')
        except Exception:
            version, _ = winreg.QueryValueEx(ie_key, 'Version')

        winreg.CloseKey(ie_key)

        if version.startswith('11'):
            value = 0x2AF9
        elif version.startswith('10'):
            value = 0x2711
        elif version.startswith('9'):
            value = 0x270F
        elif version.startswith('8'):
            value = 0x22B8
        else:
            value = 0x2AF9  # Set IE11 as default

        return value

    try:
        browser_emulation = winreg.OpenKey(
            winreg.HKEY_CURRENT_USER,
            r'Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION',
            0,
            winreg.KEY_ALL_ACCESS,
        )
    except OSError:
        browser_emulation = winreg.CreateKeyEx(
            winreg.HKEY_CURRENT_USER,
            r'Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION',
            0,
            winreg.KEY_ALL_ACCESS,
        )

    try:
        dpi_support = winreg.OpenKey(
            winreg.HKEY_CURRENT_USER,
            r'Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_96DPI_PIXEL',
            0,
            winreg.KEY_ALL_ACCESS,
        )
    except OSError:
        dpi_support = winreg.CreateKeyEx(
            winreg.HKEY_CURRENT_USER,
            r'Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_96DPI_PIXEL',
            0,
            winreg.KEY_ALL_ACCESS,
        )

    mode = get_ie_mode()
    executable_name = sys.executable.split('\\')[-1]
    winreg.SetValueEx(browser_emulation, executable_name, 0, winreg.REG_DWORD, mode)
    winreg.CloseKey(browser_emulation)

    winreg.SetValueEx(dpi_support, executable_name, 0, winreg.REG_DWORD, 1)
    winreg.CloseKey(dpi_support)


class MSHTML:
    alert = None

    class JSBridge(IWebBrowserInterop):
        __namespace__ = 'MSHTML.JSBridge'
        window = None

        def call(self, func_name, param, value_id):
            print(func_name, param)
            return js_bridge_call(self.window, func_name, json.loads(param), value_id)

        def alert(self, message):
            MSHTML.alert(str(message))

        def console(self, message):
            print(message)

    def __init__(self, form, window, alert):
        self.pywebview_window = window
        self.webview = WebBrowserEx()
        self.webview.Dock = WinForms.DockStyle.Fill
        self.webview.ScriptErrorsSuppressed = not _state['debug']
        self.webview.IsWebBrowserContextMenuEnabled = _state['debug']
        self.webview.WebBrowserShortcutsEnabled = False
        self.webview.DpiAware = True
        MSHTML.alert = alert

        user_agent = _state['user_agent'] or settings.get('user_agent')
        if user_agent:
            self.webview.ChangeUserAgent(user_agent)

        self.webview.ScriptErrorsSuppressed = not _state['debug']
        self.webview.IsWebBrowserContextMenuEnabled = _state['debug']

        self.js_result_semaphore = Semaphore(0)
        self.js_bridge = MSHTML.JSBridge()
        self.js_bridge.window = window

        self.webview.ObjectForScripting = self.js_bridge

        # HACK. Hiding the WebBrowser is needed in order to show a non-default background color. Tweaking the Visible property
        # results in showing a non-responsive control, until it is loaded fully. To avoid this, we need to disable this behaviour
        # for the default background color.
        if window.background_color != '#FFFFFF':
            self.webview.Visible = False
            self.first_load = True
        else:
            self.first_load = False

        self.cancel_back = False
        self.webview.PreviewKeyDown += self.on_preview_keydown
        self.webview.Navigating += self.on_navigating
        self.webview.NewWindow3 += self.on_new_window
        self.webview.DownloadComplete += self.on_download_complete
        self.webview.DocumentCompleted += self.on_document_completed

        if window.real_url:
            self.webview.Navigate(window.real_url)
        elif window.html:
            self.webview.DocumentText = window.html
        else:
            self.webview.DocumentText = DEFAULT_HTML

        self.form = form
        form.Controls.Add(self.webview)

    def evaluate_js(self, script, parse_json=True):
        def _evaluate_js():
            nonlocal result
            res = self.webview.Document.InvokeScript('eval', (script,))

            if parse_json and res is not None:
                try:
                    result = json.loads(res)
                except Exception:
                    result = res
            else:
                result = res
            lock.release()

        result = None
        lock = Semaphore(0)
        self.form.Invoke(Func[Type](_evaluate_js))
        lock.acquire()
        return result

    def load_html(self, content, base_uri):
        self.webview.DocumentText = inject_base_uri(content, base_uri)

    def load_url(self, url):
        self.webview.Navigate(url)

    def on_preview_keydown(self, _, args):
        if args.KeyCode == WinForms.Keys.Back:
            self.cancel_back = True
        elif args.KeyCode == WinForms.Keys.Delete:
            self.webview.Document.ExecCommand('Delete', False, None)
        elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.C:
            self.webview.Document.ExecCommand('Copy', False, None)
        elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.X:
            self.webview.Document.ExecCommand('Cut', False, None)
        elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.V:
            self.webview.Document.ExecCommand('Paste', False, None)
        elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.Z:
            self.webview.Document.ExecCommand('Undo', False, None)
        elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.A:
            self.webview.Document.ExecCommand('selectAll', False, None)

    def on_new_window(self, sender, args):
        args.Cancel = True
        webbrowser.open(args.Url)

    def on_download_complete(self, *_):
        pass

    def on_navigating(self, _, args):
        if self.cancel_back:
            args.Cancel = True
            self.cancel_back = False

    def on_document_completed(self, _, args):
        document = self.webview.Document

        if _state['debug']:
            document.InvokeScript(
                'eval',
                """
                    window.console = {
                        log: function(msg) { window.external.console(JSON.stringify(msg)) },
                        error: function(msg) { window.external.console(JSON.stringify(msg)) }
                    }',
                """,
            )

        if self.first_load:
            self.webview.Visible = True
            self.first_load = False

        self.url = None if args.Url.AbsoluteUri == 'about:blank' else str(args.Url.AbsoluteUri)
        inject_pywebview(renderer, self.pywebview_window)

        if self.pywebview_window.easy_drag:
            document.MouseMove += self.on_mouse_move

    def on_mouse_move(self, _, e):
        if e.MouseButtonsPressed == WinForms.MouseButtons.Left:
            WebBrowserEx.ReleaseCapture()
            windll.user32.SendMessageW(
                self.form.Handle.ToInt32(),
                WebBrowserEx.WM_NCLBUTTONDOWN,
                WebBrowserEx.HT_CAPTION,
                6,
            )
