from enum import Enum
from typing import Tuple, Union
import carb
import carb.settings
import omni.ui as ui
from .settings_model import (
    SettingModel,
    SettingsComboItemModel,
    VectorFloatSettingsModel,
    VectorIntSettingsModel,
    AssetPathSettingsModel,
    RadioButtonSettingModel,
)
from .settings_widget_builder import SettingsWidgetBuilder
from omni.kit.widget.searchable_combobox import build_searchable_combo_widget
import omni.kit.app


class SettingType(Enum):
    """
    Supported setting types
    """
    FLOAT = 0
    INT = 1
    COLOR3 = 2
    BOOL = 3
    STRING = 4
    DOUBLE3 = 5
    INT2 = 6
    DOUBLE2 = 7
    ASSET = 8


class SettingWidgetType(Enum):
    """
    Supported setting UI widget types
    """
    FLOAT = 0
    INT = 1
    COLOR3 = 2
    BOOL = 3
    STRING = 4
    DOUBLE3 = 5
    INT2 = 6
    DOUBLE2 = 7
    ASSET = 8
    COMBOBOX = 9
    RADIOBUTTON = 10


# This is for backward compatibility - Using deprecated SettingType - omni.kit.widget.settings.deprecated.SettingType
INT_WIDGET_TPYE_MAP = {
    0: SettingWidgetType.FLOAT,
    1: SettingWidgetType.INT,
    2: SettingWidgetType.COLOR3,
    3: SettingWidgetType.BOOL,
    4: SettingWidgetType.STRING,
    5: SettingWidgetType.DOUBLE3,
    6: SettingWidgetType.INT2,
    7: SettingWidgetType.DOUBLE2,
    8: SettingWidgetType.ASSET,
    9: SettingWidgetType.COMBOBOX,
    10: SettingWidgetType.RADIOBUTTON,
}


#  TODO: Section will be moved to some location like omni.ui.settings
#  #############################################################################################
def create_setting_widget(
    setting_path: str, setting_type: SettingType, range_from=0, range_to=0, speed=1, **kwargs
) -> Tuple[ui.Widget, ui.AbstractValueModel]:
    """
    Create a UI widget connected with a setting.

    If ``range_from`` >= ``range_to`` there is no limit. Undo/redo operations are also supported, because changing setting
    goes through the :mod:`omni.kit.commands` module, using :class:`.ChangeSettingCommand`.

    Args:
        setting_path: Path to the setting to show and edit.
        setting_type: Type of the setting to expect.
        range_from: Limit setting value lower bound.
        range_to: Limit setting value upper bound.
        speed: Range speed

    Returns:
        :class:`ui.Widget` and :class:`ui.AbstractValueModel` connected with the setting on the path specified.
    """
    widget = None
    model = None
    if isinstance(setting_type, int):
        setting_type = INT_WIDGET_TPYE_MAP.get(setting_type)

    # The string "ASSET" is for backward compatibility
    if not isinstance(setting_type, Enum) and setting_type != "ASSET":
        carb.log_warn(f"Unsupported setting widget type {setting_type} for {setting_path}")
        return None, None

    # Create widget to be used for particular type.
    if setting_type == SettingWidgetType.COMBOBOX:
        setting_is_index = kwargs.pop("setting_is_index", True)
        widget, model = SettingsWidgetBuilder.createComboboxWidget(setting_path, setting_is_index=setting_is_index, additional_widget_kwargs=kwargs)
    elif setting_type == SettingWidgetType.RADIOBUTTON:
        model = RadioButtonSettingModel(setting_path)
        widget = SettingsWidgetBuilder.createRadiobuttonWidget(model, setting_path, additional_widget_kwargs=kwargs)

    elif setting_type in [SettingType.INT, SettingWidgetType.INT]:
        model = SettingModel(setting_path, draggable=True)
        if model.get_value_as_int() is not None:
            widget = SettingsWidgetBuilder.createIntWidget(model, range_from, range_to, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.FLOAT, SettingWidgetType.FLOAT]:
        model = SettingModel(setting_path, draggable=True)
        if model.get_value_as_float() is not None:
            widget = SettingsWidgetBuilder.createFloatWidget(model, range_from, range_to, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.BOOL, SettingWidgetType.BOOL]:
        model = SettingModel(setting_path)
        if model.get_value_as_bool() is not None:
            widget = SettingsWidgetBuilder.createBoolWidget(model, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.STRING, SettingWidgetType.STRING]:
        model = SettingModel(setting_path)
        if model.get_value_as_string() is not None:
            widget = ui.StringField(**kwargs)
    elif setting_type in [SettingType.COLOR3, SettingWidgetType.COLOR3]:
        model = VectorFloatSettingsModel(setting_path, 3)
        widget = SettingsWidgetBuilder.createColorWidget(model, comp_count=3, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.DOUBLE2, SettingWidgetType.DOUBLE2]:
        model = VectorFloatSettingsModel(setting_path, 2)
        widget = SettingsWidgetBuilder.createDoubleArrayWidget(model, range_from, range_to, comp_count=2, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.DOUBLE3, SettingWidgetType.DOUBLE3]:
        model = VectorFloatSettingsModel(setting_path, 3)
        widget = SettingsWidgetBuilder.createDoubleArrayWidget(model, range_from, range_to, comp_count=3, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.INT2, SettingWidgetType.INT2]:
        model = VectorIntSettingsModel(setting_path, 2)
        widget = SettingsWidgetBuilder.createIntArrayWidget(model, range_from, range_to, comp_count=2, additional_widget_kwargs=kwargs)
    elif setting_type in [SettingType.ASSET, SettingWidgetType.ASSET, "ASSET"]:  # The string "ASSET" is for backward compatibility
        model = AssetPathSettingsModel(setting_path)
        widget = SettingsWidgetBuilder.createAssetWidget(model, additional_widget_kwargs=kwargs)
    else:  # pragma: no cover
        # Convenient way to extend new types of widget creation.
        build_widget_func = getattr(SettingsWidgetBuilder, f"create{setting_type.name.capitalize()}Widget", None)
        if build_widget_func:
            widget, model = build_widget_func(setting_path, additional_widget_kwargs=kwargs)
        else:
            carb.log_warn(f"Couldn't find widget for {setting_type} - {setting_path}")  # Do we have any right now?
            return None, None

    if widget:
        try:
            widget.model = model
        except Exception:  # pragma: no cover
            print(widget, "doesn't have model")

    if isinstance(widget, ui.Widget) and not widget.identifier:
        widget.identifier = setting_path

    return widget, model


def create_setting_widget_combo(
    setting_path: str, items: Union[list, dict], setting_is_index: bool = True) -> Tuple[SettingsComboItemModel, ui.ComboBox]:
    """
    Creating a Combo Setting widget.


    This function creates a combo box that shows a provided list of names and it is connected with setting by path
    specified. Underlying setting values are used from values of `items` dict.

    Args:
        setting_path: Path to the setting to show and edit.
        items: Can be either :py:obj:`dict` or :py:obj:`list`. For :py:obj:`dict` keys are UI displayed names, values are
            actual values set into settings. If it is a :py:obj:`list` UI displayed names are equal to setting values.
        setting_is_index:
            True - setting_path value is index into items list (default)
            False - setting_path value is string in items list
    """
    return SettingsWidgetBuilder.createComboboxWidget(setting_path, items=items, setting_is_index=setting_is_index)


class SettingsSearchableCombo:
    def __init__(self, setting_path: str, key_value_pairs: dict, default_key: str):
        self._path = setting_path
        self._items = key_value_pairs
        key_index = -1
        key_list = sorted(list(self._items.keys()))
        self._set_by_ui = False

        def on_combo_click(model):
            item_key = model.get_value_as_string()
            item_value = self._items[item_key]
            self._set_by_ui = True
            carb.settings.get_settings().set_string(self._path, item_value)

        self._component_combo = build_searchable_combo_widget(key_list, key_index, on_combo_click, widget_height=18, default_value=default_key)
        self._update_setting = omni.kit.app.SettingChangeSubscription(self._path, self._on_setting_change)

    def destroy(self):
        self._component_combo.destroy()
        self._component_combo = None
        self._update_setting = None

    def get_key_from_value(self, value):
        idx = list(self._items.values()).index(value)
        key = list(self._items.keys())[idx]
        return key

    def get_current_key(self):
        current_value = carb.settings.get_settings().get_as_string(self._path)
        return self.get_key_from_value(current_value)

    def _on_setting_change(owner, item: carb.dictionary.Item, event_type):
        if owner._set_by_ui:
            owner._set_by_ui = False
        else:
            key = owner.get_current_key()
            owner._component_combo.set_text(new_text=key)
            #print(item)
