import os
from collections import namedtuple

from gi.repository import Gtk, Gdk, GLib

from .i18n import _
from .config import USER_COLORS_DIR, COLORS_DIR
from .settings import UI_SETTINGS
from .plugin_api import PLUGIN_PATH_PREFIX
from .plugin_loader import IMPORT_PLUGINS
from .theme_file import get_presets, group_presets_by_dir


Section = namedtuple('Section', ['id', 'display_name'])


class Sections:
    PRESETS = Section('presets', _("Presets"))
    PLUGINS = Section('plugins', _("Plugins"))
    USER = Section('user', _("User Presets"))


_SECTION_RESERVED_NAME = '<section>'


class Keys:
    RIGHT_ARROW = 65363
    LEFT_ARROW = 65361
    KEY_F5 = 65474


class ThemePresetList(Gtk.ScrolledWindow):

    _update_signal = None
    treestore = None
    treeview = None
    preset_select_callback = None

    DISPLAY_NAME = 0
    THEME_NAME = 1
    THEME_PATH = 2
    IS_SAVEABLE = 3

    def __init__(self, preset_select_callback):
        super().__init__()
        self.set_size_request(width=UI_SETTINGS.preset_list_minimal_width, height=-1)

        self.preset_select_callback = preset_select_callback

        self.treestore = Gtk.TreeStore(str, str, str, bool)
        self.treeview = Gtk.TreeView(
            model=self.treestore, headers_visible=False
        )
        self.treeview.connect(
            "key-press-event", self._on_keypress
        )
        self.treeview.connect(
            "row-collapsed", self._on_row_collapsed
        )
        self.treeview.connect(
            "row-expanded", self._on_row_expanded
        )
        column = Gtk.TreeViewColumn(
            cell_renderer=Gtk.CellRendererText(), markup=self.DISPLAY_NAME
        )
        self.treeview.append_column(column)
        self.load_presets()

        self.add(self.treeview)

        GLib.idle_add(
            self.focus_first_available,
            priority=GLib.PRIORITY_HIGH
        )

    ###########################################################################
    # Public interface:
    ###########################################################################

    def get_preset_path(self):
        return self._get_selected_value(self.THEME_PATH)

    def preset_is_saveable(self):
        return self._get_selected_value(self.IS_SAVEABLE)

    def focus_preset_by_filepath(self, filepath):
        treepath = self._find_treepath_by_filepath(
            self.treestore, filepath
        )
        if treepath:
            treepathcopy = treepath.copy()
            while treepath.up():
                self.treeview.expand_row(treepath, False)
            self.treeview.set_cursor(treepathcopy)

    def focus_first_available(self):
        init_iter = self.treestore.get_iter_first()
        while init_iter and not self.treeview.row_expanded(self.treestore.get_path(init_iter)):
            init_iter = self.treestore.iter_next(init_iter)
        init_iter = self.treestore.iter_children(init_iter)
        self.treeview.set_cursor(self.treestore.get_path(init_iter))

    def load_presets(self):
        if self._update_signal:
            self.treeview.disconnect(self._update_signal)

        self.treestore.clear()
        all_presets = get_presets()
        self._load_system_presets(all_presets)
        self._load_plugin_presets(all_presets)
        self._load_user_presets(all_presets)

        self._update_signal = self.treeview.connect(
            "cursor_changed", self._on_preset_select
        )

    def reload_presets(self):
        selected_path = self.get_preset_path()
        self.load_presets()
        self.focus_preset_by_filepath(selected_path)

    ###########################################################################
    # Private methods:
    ###########################################################################

    def _get_current_treepath(self):
        return self.treeview.get_cursor()[0]

    def _get_selected_value(self, value):
        treepath = self._get_current_treepath()
        if not treepath:
            return None
        treeiter = self.treestore.get_iter(treepath)
        return self.treestore.get_value(treeiter, value)

    def _find_treepath_by_filepath(
            self, store, target_filepath, treeiter=None
    ):
        if not treeiter:
            treeiter = self.treestore.get_iter_first()
        while treeiter:
            current_filepath = store[treeiter][self.THEME_PATH]
            if current_filepath == target_filepath:
                return store[treeiter].path
            if store.iter_has_child(treeiter):
                childiter = store.iter_children(treeiter)
                child_result = self._find_treepath_by_filepath(
                    store, target_filepath, childiter
                )
                if child_result:
                    return child_result
            treeiter = store.iter_next(treeiter)
        return None

    def _add_preset(self, display_name, name, path, saveable, parent=None):  # pylint: disable=too-many-arguments
        return self.treestore.append(
            parent, (display_name, name, path, saveable)
        )

    def _add_directory(self, name, tree_id=None, parent=None, template='{}'):
        tree_id = tree_id or name
        return self.treestore.append(parent, (
            template.format(name), _SECTION_RESERVED_NAME, tree_id, False
        ))

    def _add_section(self, section, parent=None):
        return self._add_directory(
            template='<b>{}</b>', name=section.display_name,
            parent=parent, tree_id=section.id,
        )

    @staticmethod
    def _format_dirname(preset, preset_dir, plugin_name=None):
        dir_template = '{}: {}'
        preset_relpath = (
            preset.name[len(preset_dir):]
            if plugin_name else preset.name
        )
        dir_display_name, _slash, item_display_name = preset_relpath.lstrip('/').partition('/')
        if item_display_name:
            dir_display_name = dir_template.format(
                dir_display_name.replace('_', ' '), item_display_name
            )
        if plugin_name:
            dir_display_name = dir_template.format(plugin_name, dir_display_name)
        return dir_display_name

    @staticmethod
    def _format_childname(preset_relpath):
        dir_template = '{}: {}'
        dir_display_name, _slash, item_display_name = preset_relpath.lstrip('/').partition('/')
        if item_display_name:
            dir_display_name = dir_template.format(
                dir_display_name.replace('_', ' '), item_display_name
            )
        return dir_display_name

    def _add_presets(  # pylint: disable=too-many-arguments
            self, preset_dir, preset_list,
            plugin_name=None, parent=None
    ):
        sorted_preset_list = sorted(preset_list, key=lambda x: x.name.lower())

        first_preset = sorted_preset_list[0]
        piter = self._add_preset(
            display_name=self._format_dirname(first_preset, preset_dir, plugin_name),
            name=first_preset.name,
            path=first_preset.path,
            saveable=first_preset.is_saveable,
            parent=parent
        )

        for preset in sorted_preset_list[1:]:
            display_name = self._format_childname((
                preset.name[len(preset_dir):]
                if preset_dir else preset.name
            ))
            self._add_preset(
                display_name=display_name,
                name=preset.name,
                path=preset.path,
                saveable=preset.is_saveable,
                parent=piter
            )

    def _load_system_presets(self, all_presets):
        featured_dirs = ('Featured', )
        presets_iter = self._add_section(Sections.PRESETS)
        sorted_system_presets = sorted(
            all_presets.get(COLORS_DIR, {}).items(),
            key=lambda x: (x[0] not in featured_dirs, x[0].lower())
        )
        for preset_dir, preset_list in sorted_system_presets:
            if preset_dir.startswith(PLUGIN_PATH_PREFIX):
                continue
            self._add_presets(
                preset_dir=preset_dir, preset_list=preset_list,
                parent=presets_iter
            )
        if UI_SETTINGS.preset_list_sections_expanded.get(Sections.PRESETS.id, True):
            self.treeview.expand_row(self.treestore.get_path(presets_iter), False)

    def _load_plugin_presets(self, all_presets):  # pylint: disable=too-many-locals
        plugins_iter = self._add_section(Sections.PLUGINS)
        for colors_dir, presets in all_presets.items():
            for preset_dir, preset_list in sorted(presets.items()):

                preset_plugin = None
                plugin_theme_dir = None
                for plugin in IMPORT_PLUGINS.values():
                    if plugin.plugin_theme_dir == colors_dir:
                        plugin_theme_dir = plugin.plugin_theme_dir
                    elif plugin.user_theme_dir and (
                            os.path.join(colors_dir, preset_dir).startswith(plugin.user_theme_dir)
                    ):
                        plugin_theme_dir = plugin.user_theme_dir
                    else:
                        continue
                    preset_plugin = plugin
                if not plugin_theme_dir:
                    continue
                plugin_name = preset_plugin.display_name or preset_plugin.name
                if plugin_theme_dir == preset_plugin.user_theme_dir:
                    plugin_name = preset_plugin.user_presets_display_name or plugin_name

                grouped_presets = group_presets_by_dir(
                    preset_list, os.path.join(colors_dir, preset_dir)
                )
                if len(grouped_presets) == 1 and grouped_presets[0][0] == '':
                    grouped_presets = [
                        (preset.name, [preset, ])
                        for preset in grouped_presets[0][1]
                    ]

                plugin_presets_iter = self._add_directory(
                    name=plugin_name,
                    parent=plugins_iter
                )
                for dir_name, group in grouped_presets:
                    self._add_presets(
                        preset_dir=dir_name,
                        preset_list=group,
                        parent=plugin_presets_iter
                    )

        if UI_SETTINGS.preset_list_sections_expanded.get(Sections.PLUGINS.id, True):
            self.treeview.expand_row(self.treestore.get_path(plugins_iter), False)

    def _load_user_presets(self, all_presets):
        user_presets_iter = self._add_section(Sections.USER)
        for preset_dir, preset_list in sorted(all_presets.get(USER_COLORS_DIR, {}).items()):
            if preset_dir.startswith(PLUGIN_PATH_PREFIX):
                continue
            self._add_presets(
                preset_dir=preset_dir, preset_list=preset_list,
                parent=user_presets_iter
            )
        if UI_SETTINGS.preset_list_sections_expanded.get(Sections.USER.id, True):
            self.treeview.expand_row(self.treestore.get_path(user_presets_iter), False)

    ###########################################################################
    # Signal handlers:
    ###########################################################################

    def _on_preset_select(self, _widget):
        treepath = self._get_current_treepath()
        if not treepath:
            return
        treeiter = self.treestore.get_iter(treepath)
        current_theme = self.treestore.get_value(treeiter, self.THEME_NAME)
        if current_theme == _SECTION_RESERVED_NAME:
            return
        current_preset_path = self.treestore.get_value(treeiter, self.THEME_PATH)
        self.preset_select_callback(
            current_theme, current_preset_path
        )

    def _on_keypress(self, _widget, event):
        key = event.keyval
        if event.type != Gdk.EventType.KEY_PRESS:
            return
        if key == Keys.KEY_F5:
            self.reload_presets()
        elif key in (Keys.LEFT_ARROW, Keys.RIGHT_ARROW):
            treepath = self._get_current_treepath()
            if not treepath:
                return
            if key == Keys.RIGHT_ARROW:
                self.treeview.expand_row(treepath, False)
            elif key == Keys.LEFT_ARROW:
                self.treeview.collapse_row(treepath)

    def _on_row_expanded(self, _treeview, treeiter, _treepath):
        if self.treestore.get_value(treeiter, self.THEME_NAME) == _SECTION_RESERVED_NAME:
            section_id = self.treestore.get_value(treeiter, self.THEME_PATH)
            UI_SETTINGS.preset_list_sections_expanded[section_id] = True

    def _on_row_collapsed(self, _treeview, treeiter, _treepath):
        if self.treestore.get_value(treeiter, self.THEME_NAME) == _SECTION_RESERVED_NAME:
            section_id = self.treestore.get_value(treeiter, self.THEME_PATH)
            UI_SETTINGS.preset_list_sections_expanded[section_id] = False