from giara.constants import RESOURCE_PREFIX
from gettext import gettext as _
from functools import reduce
from operator import or_, and_
from gi.repository import Gtk, Adw, GtkSource, GObject, GLib, Pango
from giara.path_utils import is_image, is_video
from giara.image_utils import make_video_thumb
from giara.subreddits_list_view import SubredditsListbox
from giara.choice_picker import ChoicePickerButton
from giara.flair_label import FlairLabel, FlairTextColor
from giara.confManager import ConfManager
from praw.models import Submission
from threading import Thread
from giara.accel_manager import add_accelerators
from giara.ellipsized_label import make_ellipsized_label


class CommonNewEntityWindow(Adw.Window):
    __gsignals__ = {
        'file-chosen': (
            GObject.SignalFlags.RUN_LAST,
            None,
            (str,)
        )
    }

    def __init__(self, widgets_to_hide, widgets_to_show, **kwargs):
        super().__init__(**kwargs)
        self.set_modal(True)

        self.confman = ConfManager()

        self.set_default_size(
            self.confman.conf['newentity_windowsize']['width'],
            self.confman.conf['newentity_windowsize']['height']
        )

        self.builder = Gtk.Builder.new_from_resource(
            f'{RESOURCE_PREFIX}/ui/new_post_window.ui'
        )

        self.title_entry = self.builder.get_object('title_entry')
        self.link_entry = self.builder.get_object('link_entry')
        self.text_source_view_container = self.builder.get_object(
            'text_source_view_container'
        )
        self.choice_picker_container = self.builder.get_object(
            'choice_picker_container'
        )

        self.headerbar = self.builder.get_object('headerbar')
        self.set_content(self.builder.get_object('inner_box'))

        self.widgets_to_hide = widgets_to_hide
        self.widgets_to_show = widgets_to_show

        for w in self.widgets_to_hide:
            w = self.builder.get_object(w)
            w.set_visible(False)
        for w in self.widgets_to_show:
            w = self.builder.get_object(w)
            w.set_visible(True)

        self.source_buffer = None
        if 'text_source_view_container' in self.widgets_to_show:
            source_style_scheme_manager = \
                GtkSource.StyleSchemeManager.get_default()
            source_lang_manager = GtkSource.LanguageManager.get_default()
            source_lang_md = source_lang_manager.get_language('markdown')
            self.source_buffer = GtkSource.Buffer()
            color_scheme = 'oblivion'
            self.source_buffer.set_style_scheme(
                source_style_scheme_manager.get_scheme(color_scheme)
            )
            self.source_buffer.set_language(source_lang_md)
            source_view = self.builder.get_object('source_view')
            source_view.set_buffer(self.source_buffer)
            self.source_buffer.connect('changed', self.on_input)

        self.file_chooser_btn = self.builder.get_object('file_chooser_btn')
        self.file_chooser_label = self.builder.get_object('file_chooser_label')
        self.file_chooser_dialog = None
        self.media_listbox = self.builder.get_object('media_listbox')
        self.media_listbox_sw = self.builder.get_object('media_listbox_sw')
        self.media = list()
        if 'file_chooser_btn' in self.widgets_to_show:

            def on_file_chooser_deployed(*args):
                self.file_chooser_dialog = Gtk.FileChooserNative.new(
                    _('Choose an image or video to upload'),
                    self,
                    Gtk.FileChooserAction.OPEN,
                    None, None
                )
                self.file_chooser_dialog.set_transient_for(self)
                self.file_chooser_dialog.set_modal(True)
                self.file_chooser_dialog.set_filter(self.builder.get_object(
                    'filefilter1'
                ))

                def on_response(dialog, res):
                    if res == Gtk.ResponseType.ACCEPT:
                        filename = dialog.get_file().get_path()
                        if filename:
                            pic_filename = filename
                            if is_video(filename):
                                pic_filename = make_video_thumb(pic_filename)
                            pic = Gtk.Picture.new_for_filename(pic_filename)
                            pic.set_hexpand(False)
                            pic.set_halign(Gtk.Align.START)
                            rm_btn = Gtk.Button(
                                icon_name='list-remove-symbolic',
                                tooltip_text=_('Remove'),
                                hexpand=False, halign=Gtk.Align.END
                            )
                            rm_btn.get_style_context().add_class('circular')
                            box = Gtk.Box(
                                orientation=Gtk.Orientation.HORIZONTAL,
                                spacing=6, margin_top=6, margin_bottom=6,
                                margin_start=6, margin_end=6
                            )
                            label = Gtk.Label(
                                label=filename.split('/')[-1],
                                hexpand=True,
                                halign=Gtk.Align.START,
                                ellipsize=Pango.EllipsizeMode.END
                            )
                            box.append(pic)
                            box.append(label)
                            box.append(rm_btn)
                            lbr = Gtk.ListBoxRow()
                            lbr.set_child(box)

                            def remove_media(*args):
                                self.media_listbox.remove(lbr)
                                self.media.remove(filename)

                            rm_btn.connect('clicked', remove_media)
                            self.media_listbox.append(lbr)
                            self.media.append(filename)
                            self.emit('file-chosen', '')

                self.file_chooser_dialog.connect('response', on_response)
                self.file_chooser_dialog.show()

            self.file_chooser_btn.connect('clicked', on_file_chooser_deployed)

        self.cancel_btn = self.builder.get_object('cancel_btn')
        self.cancel_btn.connect('clicked', lambda *args: self.close())
        self.send_btn = self.builder.get_object('send_btn')
        self.send_btn.set_sensitive(False)
        self.send_btn.connect('clicked', self.on_send)

        for entry in (
                self.title_entry,
                self.link_entry
        ):
            entry.connect('changed', self.on_input)
        self.connect(
            'file-chosen',
            self.on_input
        )

        add_accelerators(self, [{
            'combo': 'Escape',
            'cb': lambda *args: self.close()
        }])

    def close(self, *args, **kwargs):
        self.on_destroy()
        super().close(*args, **kwargs)

    def on_destroy(self, *args):
        alloc = self.get_allocation()
        self.confman.conf['newentity_windowsize'] = {
            'width': alloc.width,
            'height': alloc.height
        }
        self.hide()
        self.confman.save_conf()

    def on_send(self, *args):
        raise NotImplementedError('on_send not implemented')

    def on_input(self, *args):
        raise NotImplementedError('on_input not implemented')

    def get_sourcebuffer_text(self):
        return self.source_buffer.get_text(
            self.source_buffer.get_start_iter(),
            self.source_buffer.get_end_iter(),
            True
        ).strip() if self.source_buffer is not None else ''


class SubredditChoiceListbox(SubredditsListbox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, load_now=True, sort=True, **kwargs)
        self.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.set_filter_func(self.subs_only_filter_func, None, False)

    def subs_only_filter_func(self, row, data, notify_destroy):
        return 'u/' not in row.subreddit.display_name_prefixed


class FlairChoiceListboxRow(Gtk.ListBoxRow):
    def __init__(self, text, bg, fg, flair_id, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.flair_id = flair_id
        fl = FlairLabel(text, bg, fg)
        fl.set_halign(Gtk.Align.CENTER)
        fl.set_margin_top(12)
        fl.set_margin_bottom(12)
        fl.set_margin_start(12)
        fl.set_margin_end(12)
        self.set_child(fl)
        self.title = text

    def get_key(self):
        return self.title.lower()


class FlairChoiceListbox(Gtk.ListBox):
    def __init__(self, subreddit, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.set_header_func(self.separator_header_func)
        self.set_sort_func(self.flair_sort_func, None, False)
        self.subreddit = subreddit
        self.populate()

    def flair_sort_func(self, row1, row2, data, notify_destroy):
        if row1.flair_id is None:
            return False
        if row2.flair_id is None:
            return True
        return row1.title.lower() > row2.title.lower()

    def populate(self):
        nonerow = Gtk.ListBoxRow()
        nonerow.get_key = lambda *args: _('None').lower()
        nonelbl = Gtk.Label.new(_('None'))
        nonelbl.set_margin_top(12)
        nonelbl.set_margin_bottom(12)
        nonelbl.set_margin_start(12)
        nonelbl.set_margin_end(12)
        nonerow.set_child(nonelbl)
        nonerow.title = _('None')
        nonerow.flair_id = None
        self.append(nonerow)
        for lt in self.subreddit.flair.link_templates:
            self.append(FlairChoiceListboxRow(
                lt['text'],
                lt['background_color'],
                FlairTextColor.LIGHT if lt['text_color'].lower() == 'light'
                else FlairTextColor.DARK,
                lt['id']
            ))

    def separator_header_func(self, row, prev_row):
        if (
            prev_row is not None and
            row.get_header() is None
        ):
            row.set_header(
                Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
            )


class NewPostWindow(CommonNewEntityWindow):
    def __init__(self, reddit, post_type, callback=None, **kwargs):
        hide = []
        show = []
        self.post_type = post_type
        if self.post_type == 'text':
            hide = ['link_entry', 'file_chooser_btn', 'media_listbox_sw']
            show = ['text_source_view_container']
        elif self.post_type == 'link':
            hide = ['file_chooser_btn', 'text_source_view_container',
                    'media_listbox_sw']
            show = ['link_entry']
        else:  # media
            hide = ['text_source_view_container', 'link_entry']
            show = ['file_chooser_btn', 'media_listbox_sw']
        show.append('choice_picker_container')
        super().__init__(hide, show, **kwargs)
        self.callback = callback
        assert (
            self.post_type in ('text', 'link', 'media')
        ), 'Post type can only be text, link or media'

        self.subreddits_picker_listbox = SubredditChoiceListbox(
            reddit.user.subreddits
        )
        self.picker = ChoicePickerButton(
            self.subreddits_picker_listbox, _('Select a subreddit…')
        )
        self.selected_subreddit = None
        self.flair_picker = None
        self.picker.connect('changed', self.on_picker_changed)
        self.choice_picker_container.append(self.picker)

    def on_send(self, *args):
        self.set_sensitive(False)
        subreddit = self.get_selected_subreddit()
        title = self.title_entry.get_text().strip()

        def af():
            if self.post_type in ('text', 'link'):
                subreddit.submit(
                    title=title,
                    selftext=(
                        self.get_sourcebuffer_text()
                        if self.post_type == 'text'
                        else None
                    ),
                    url=(
                        self.link_entry.get_text().strip()
                        if self.post_type == 'link'
                        else None
                    ),
                    flair_id=self.get_selected_flair_id()
                )
            else:
                if is_video(self.media[0]) and len(self.media) == 1:
                    subreddit.submit_video(
                        title=title,
                        video_path=self.media[0],
                        thumbnail_path=make_video_thumb(self.media[0]),
                        flair_id=self.get_selected_flair_id()
                    )
                elif is_image(self.media[0]) and len(self.media) == 1:
                    subreddit.submit_image(
                        title=title,
                        image_path=self.media[0],
                        flair_id=self.get_selected_flair_id()
                    )
                elif (
                        reduce(and_, [is_image(m) for m in self.media], True)
                        and len(self.media) > 1
                ):
                    subreddit.submit_gallery(
                        title=title,
                        images=[{'image_path': m} for m in self.media],
                        flair_id=self.get_selected_flair_id()
                    )
            GLib.idle_add(cb)

        def cb():
            # TODO: verify that post has been sent before closing
            # else show error
            if self.callback is not None:
                self.callback()
            self.destroy()

        Thread(target=af, daemon=True).start()

    def on_picker_changed(self, *args):
        n_sub = self.get_selected_subreddit()
        if n_sub != self.selected_subreddit and n_sub is not None:
            if self.flair_picker is not None:
                self.choice_picker_container.remove(self.flair_picker)
            self.selected_subreddit = n_sub
            try:
                flair_picker_listbox = FlairChoiceListbox(
                    self.selected_subreddit
                )
                self.flair_picker = ChoicePickerButton(
                    flair_picker_listbox, default_title=_('Select a flair…')
                )
                self.choice_picker_container.append(self.flair_picker)
                self.flair_picker.show()
            except Exception:
                print(
                    'Error getting flairs for subreddit',
                    f'`{self.selected_subreddit}`'
                )
        self.on_input()

    def on_input(self, *args):
        self.send_btn.set_sensitive(
            self.get_selected_subreddit() is not None and
            len(self.title_entry.get_text().strip()) > 0 and
            (
                self.post_type != 'text' or
                len(self.get_sourcebuffer_text()) > 0
            ) and
            (
                self.post_type != 'link' or
                len(self.link_entry.get_text().strip()) > 0
            ) and
            (
                self.post_type != 'media' or
                (self.validate_selected_media())
            )
        )

    def validate_selected_media(self):
        has_videos = reduce(or_, [is_video(m) for m in self.media], False)
        return (
            (has_videos and len(self.media) == 1) or
            (
                not has_videos and
                reduce(and_, [is_image(m) for m in self.media], True) and
                len(self.media) >= 1
            )
        )

    def get_selected_subreddit(self):
        choice = self.picker.get_choice()
        return choice.subreddit if choice is not None else None

    def get_selected_flair_id(self):
        choice = self.flair_picker.get_choice()
        return (
            choice.flair_id
            if self.flair_picker is not None else None
        ) if choice is not None else None


class NewCommentWindow(CommonNewEntityWindow):
    def __init__(self, parent, callback=None, **kwargs):
        super().__init__(
            [
                'link_entry', 'file_chooser_btn', 'choice_picker_container',
                'title_entry', 'media_listbox_sw'
            ],
            ['text_source_view_container'],
            **kwargs
        )
        self.parent = parent
        self.callback = callback
        self.headerbar.set_title_widget(
            make_ellipsized_label(_('New comment'))
        )

    def on_send(self, *args):
        self.set_sensitive(False)

        def af():
            self.parent.reply(body=self.get_sourcebuffer_text())
            GLib.idle_add(cb)

        def cb():
            if self.callback is not None:
                self.callback()
            self.destroy()

        Thread(target=af, daemon=True).start()

    def on_input(self, *args):
        self.send_btn.set_sensitive(
            len(self.get_sourcebuffer_text()) > 0
        )


class EditWindow(NewCommentWindow):
    def __init__(self, entity, callback=None, **kwargs):
        super().__init__(
            entity,
            callback,
            **kwargs
        )
        self.headerbar.set_title_widget(make_ellipsized_label(_('Editing')))
        self.send_btn.set_label(_('Edit'))
        self.source_buffer.set_text(
            entity.selftext if isinstance(entity, Submission)
            else entity.body
        )

    def on_send(self, *args):
        # NOTE: parent == entity
        self.set_sensitive(False)

        def af():
            self.parent.edit(body=self.get_sourcebuffer_text())
            GLib.idle_add(cb)

        def cb():
            if self.callback is not None:
                self.callback()
            self.destroy()

        Thread(target=af, daemon=True).start()
