# 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 asyncio
import sys
from typing import Callable, Dict

import omni.kit.app
import omni.ui as ui

from .common import ExtensionCommonInfo, ExtSource, build_doc_urls, pull_extension_async, toggle_extension
from .styles import get_style
from .utils import get_setting, open_url, run_process


class ExtensionToggle:
    def __init__(self, item: ExtensionCommonInfo, with_label=False, show_install_button=True, refresh_cb=None):
        with ui.HStack(width=0, style=get_style(self)):
            if item.is_app:

                def on_click(is_local=item.is_local, ext_id=item.id):
                    manager = omni.kit.app.get_app().get_extension_manager()
                    if not is_local:
                        manager.pull_extension(ext_id)

                    args = [sys.argv[0]]
                    ext_info = manager.get_extension_dict(ext_id)
                    if ext_info.get("isKitFile", False):
                        args.append(ext_info["path"])
                    else:
                        args.extend(["--enable", ext_id])

                    # Pass all exts folders
                    for folder in get_setting("/app/exts/folders", default=[]):
                        args.extend(["--ext-folder", folder])

                    run_process(args)

                with ui.VStack():
                    ui.Spacer()
                    ui.Button("LAUNCH", name="LaunchButton", width=60, height=20, clicked_fn=on_click)
                    ui.Spacer()
            elif not item.is_local:
                if not show_install_button:
                    return

                with ui.VStack():
                    ui.Spacer()

                    if item.is_pulled:
                        ui.Button("INSTALLING...", name="DownloadingButton", width=60, height=20)
                    else:

                        def on_click(item=item):
                            asyncio.ensure_future(pull_extension_async(item))

                        ui.Button("INSTALL", name="InstallButton", width=60, height=20, clicked_fn=on_click)

                    ui.Spacer()

            else:
                if item.failed:
                    with ui.VStack():
                        ui.Spacer()
                        # OM-90861: Add tooltip for extension depdency solve failure
                        tooltip = "Failed to solve extension dependency."
                        if item.solver_error:
                            tooltip += f"\nError: {item.solver_error}"
                        ui.Image(name="Failed", width=26, height=26, tooltip=tooltip)
                        ui.Spacer()
                with ui.VStack():
                    ui.Spacer()
                    name = "clickable"
                    tb = ui.ToolButton(image_width=39, image_height=18, height=0, name=name)

                    tb.model.as_bool = item.enabled

                    def toggle(model, ext_id=item.id, fullname=item.fullname):
                        enable = model.get_value_as_bool()
                        # If we about to enable, toggle other versions if enabled
                        if enable:
                            manager = omni.kit.app.get_app().get_extension_manager()
                            extensions = manager.fetch_extension_versions(fullname)
                            for e in extensions:
                                if e["enabled"] and e["id"] != ext_id:
                                    toggle_extension(e["id"], False)

                        if not toggle_extension(ext_id, enable=enable):
                            model.as_bool = not enable

                    if item.is_toggle_blocked:
                        tb.name = "nonclickable"

                        # TODO(anov): how to make ToolButton disabled?
                        def block_change(model, enabled=item.enabled):
                            model.as_bool = enabled

                        tb.model.add_value_changed_fn(block_change)
                    else:
                        tb.model.add_value_changed_fn(toggle)
                    ui.Spacer()

                if with_label:
                    ui.Spacer(width=8)
                    if item.enabled:
                        ui.Label("ENABLED", name="EnabledLabel")
                    else:
                        ui.Label("DISABLED", name="DisabledLabel")


class SimpleCheckBox:
    def __init__(self, checked: bool, on_checked_fn: Callable, text: str = None, model=None, enabled=True):
        with ui.HStack(width=0, style=get_style(self)):
            with ui.VStack(width=0):
                ui.Spacer()
                if enabled:
                    tb = ui.ToolButton(image_width=39, image_height=18, height=0, model=model)
                    tb.model.as_bool = checked
                    tb.model.add_value_changed_fn(lambda model: on_checked_fn(model.get_value_as_bool()))
                else:
                    name = "disabled_checked" if checked else "disabled_unchecked"
                    ui.Image(width=39, height=18, name=name)
                ui.Spacer()

            if text:
                ui.Label(text)


class SearchWidget:
    """String field with a label overlay to type search string into."""

    def __init__(self, on_search_fn: Callable[[str], None]):
        def clear_text(widget):
            widget.model.set_value("")
            widget.focus_keyboard()

        def field_changed(field_string):
            self._clear_button.visible = len(field_string) > 0
            on_search_fn(field_string)

        self.clear_filters()
        with ui.ZStack(height=0, style=get_style(self)):
            # Search filed
            with ui.HStack():
                field_widget = ui.StringField(style_type_name_override="SearchField")
                self._search = field_widget.model
                ui.Rectangle(width=20, style={"background_color": 0xFF212121, "border_radius": 0.0})

            with ui.HStack():
                ui.Spacer()
                self._clear_button = ui.Button(
                    "",
                    name="ClearSearch",
                    width=16,
                    alignment=ui.Alignment.CENTER,
                    clicked_fn=lambda w=field_widget: clear_text(w),
                    visible=False,
                )

            # The label on the top of the search field
            with ui.HStack():
                ui.Spacer(width=5)
                with ui.VStack(width=0):
                    ui.Spacer()
                    self._search_icon = ui.Image(name="SearchIcon", width=12, height=13)
                    ui.Spacer()
                self._search_label = ui.Label("Search", style={"margin_width": 4}, name="Search")

        # The filtering logic
        self._begin_filter_sub = self._search.subscribe_begin_edit_fn(lambda _: self._toggle_visible(False))
        self._edit_filter_sub = self._search.subscribe_value_changed_fn(lambda m: field_changed(m.as_string))
        self._end_filter_sub = self._search.subscribe_end_edit_fn(lambda m: self._toggle_visible(not m.as_string))

    def _toggle_visible(self, visible):
        self._search_label.visible = visible
        self._search_icon.visible = visible

    def set_text(self, text):
        self._search.as_string = text
        self._toggle_visible(False)

    def get_text(self):
        return self._search.as_string

    def get_filters(self):
        return self._filters

    def toggle_filter(self, filter_to_toggle):
        if filter_to_toggle in self._filters:
            self._filters.remove(filter_to_toggle)
        else:
            self._filters.append(filter_to_toggle)

    def clear_filters(self):
        self._filters = []

    def destroy(self):
        self._begin_filter_sub = None
        self._edit_filter_sub = None
        self._end_filter_sub = None


class ExtSourceSelector:
    def __init__(self, on_selected_fn):

        self._on_selected_fn = None
        self._buttons = {}

        with ui.HStack(height=20):
            for index, source in enumerate(ExtSource):
                ui.Spacer()

                tab_btn = ui.Button(
                    source.get_ui_name(),
                    width=0,
                    style_type_name_override="ExtensionDescription.Tab",
                    clicked_fn=lambda s=source: self.set_tab(s),
                )
                self._buttons[source] = tab_btn
                if index < len(ExtSource) - 1:
                    ui.Spacer()
                    ui.Line(
                        style_type_name_override="ExtensionDescription.TabLine",
                        alignment=ui.Alignment.H_CENTER,
                        height=20,
                        width=40,
                    )

            ui.Spacer()

        self.set_tab(ExtSource.NVIDIA)
        self._on_selected_fn = on_selected_fn

    def set_badge_number(self, source, number):
        self._buttons[source].text = source.get_ui_name() + f" ({number})"

    def set_tab(self, source):
        for b in self._buttons.values():
            b.selected = False

        self._buttons[source].selected = True

        if self._on_selected_fn:
            self._on_selected_fn(source)


def add_icon_button(name, on_click):
    with ui.VStack(width=0):
        ui.Spacer()

        b = ui.Button(name=name, style_type_name_override="IconButton", width=23, height=18, clicked_fn=on_click)
        ui.Spacer()

        return b


def add_doc_link_button(ext_item: ExtensionCommonInfo, package_dict: Dict, docs_dict: Dict):
    # Create a button, but hide.
    button = None
    with ui.VStack(width=0):
        ui.Spacer()
        button = ui.Button(
            name="OpenDoc",
            style_type_name_override="IconButton",
            width=23,
            height=18,
            style={"Label": {"color": 0xFF444444}},
        )
        ui.Spacer()

    button.visible = False

    def check_url_sync(url):
        import urllib

        try:
            return urllib.request.urlopen(url).getcode() == 200
        except urllib.error.HTTPError:
            return False

    async def check_urls(doc_urls):
        for doc_url in doc_urls:
            # run sync code on other thread not to block
            if await asyncio.get_event_loop().run_in_executor(None, check_url_sync, doc_url):
                button.visible = True

                def on_click(url=doc_url):
                    open_url(url)

                button.set_clicked_fn(on_click)
                button.set_tooltip_fn(lambda url=doc_url: ui.Label(url))
                break

    # Async check for URLs and if valid show the button
    doc_urls = build_doc_urls(ext_item)
    if doc_urls:
        asyncio.ensure_future(check_urls(doc_urls))
