import datetime

import discord
import urwid

import discurses.processing
import discurses.keymaps as keymaps
import logging

logger = logging.getLogger(__name__)


class MessageListWidget(urwid.WidgetWrap):
    """The Listbox of MessageWidgets"""

    def __init__(self, discord_client, chat_widget):
        self.discord = discord_client
        self.ui = self.discord.ui
        self.chat_widget = chat_widget
        self.list_walker = MessageListWalker(self)
        self.listbox = urwid.ListBox(self.list_walker)
        self.discord.add_event_handler('on_message', self._on_message)
        self.discord.add_event_handler('on_message_edit',
                                       self._on_message_edit)
        self.discord.add_event_handler('on_message_delete',
                                       self._on_message_delete)
        self.scroll_to_bottom()
        self.__super.__init__(self.listbox)

    def add_message(self, message):
        self.list_walker.append(
            MessageWidget(self.discord, self.chat_widget, message))
        focus_status, focus = self.list_walker.get_focus()
        if not focus > len(self.list_walker) - 2:
            self.scroll_to_bottom()
        self.discord.ui.draw_screen()

    def _on_message(self, message):
        if len(self.list_walker) == 0:
            return  # No message to handle
        if message.timestamp.date() != \
                self.list_walker[-1].message.timestamp.date():
            self.list_walker.append(
                DatelineWidget(self.chat_widget, message.timestamp.date()))
        if message.channel in self.chat_widget.channels:
            self.add_message(message)

    def _on_message_edit(self, before, after):
        if before.channel in self.chat_widget.channels:
            for mw in self.list_walker:
                if before.id == mw.message.id:
                    index = self.list_walker.index(mw)
                    self.list_walker[index] = MessageWidget(
                        self.discord, self.chat_widget, after)
                    break

    def _on_message_delete(self, m):
        if m.channel in self.chat_widget.channels:
            for mw in self.list_walker:
                if m.id == mw.message.id:
                    self.list_walker.remove(mw)
                    logger.info("Removed message from listview")
                    break

    def scroll_to_bottom(self):
        if len(self.list_walker) > 0:
            self.listbox.set_focus(len(self.list_walker) - 1)

    @keymaps.MESSAGE_LIST.keypress
    def keypress(self, size, key):
        return self._w.keypress(size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        if event == 'mouse press':
            if button == 4:
                return self.listbox.keypress(size, "up") is not None
            if button == 5:
                return self.listbox.keypress(size, "down") is not None
        return self.listbox.mouse_event(size, event, button, col, row, focus)

    @keymaps.MESSAGE_LIST.command
    def update_all_columns(self):
        for mw in self.list_walker:
            mw.update_columns()

    @keymaps.MESSAGE_LIST.command
    def focus_message_textbox(self):
        self.chat_widget.set_focus('MESSAGE_EDIT')


class MessageListWalker(urwid.MonitoredFocusList, urwid.ListWalker):
    def __init__(self, list_widget):
        self.list_widget = list_widget
        self.is_polling = False
        self.top_reached = False
        urwid.MonitoredFocusList.__init__(self, [])
        self.get_logs(callback=list_widget.scroll_to_bottom)

    def get_logs(self, before=None, callback=lambda: None):
        if before is None and len(self) > 0:
            before = self[0].message.timestamp
        if self.is_polling or self.top_reached:
            return
        self.is_polling = True

        async def _callback():
            messages = []
            for channel in self.list_widget.chat_widget.channels:
                try:
                    async for m in self.list_widget.discord.logs_from(
                            channel, before=before):
                        messages.append(
                            MessageWidget(self.list_widget.discord,
                                          self.list_widget.chat_widget, m))
                except discord.errors.Forbidden:
                    messages.append(
                                ForbiddenWidget(self.list_widget.chat_widget))
            if messages == [] and len(
                    self.list_widget.chat_widget.channels) > 0:
                self.top_reached = True
                messages = [TopReachedWidget(self.list_widget.chat_widget)]

            self[0:0] = messages
            self.sort_messages()
            self._modified()
            self.is_polling = False
            callback()

        self.list_widget.discord.async_do(_callback())

    def sort_messages(self):
        st = []
        dates = []
        for mw in self:
            if mw.message.id in st or isinstance(mw, DatelineWidget):
                self.remove(mw)
            else:
                st.append(mw.message.id)
            if isinstance(mw, MessageWidget):
                if mw.message.timestamp.date() not in dates:
                    dates.append(mw.message.timestamp.date())

        for d in dates:
            self.append(DatelineWidget(self.list_widget.chat_widget, d))
            logger.debug("Added dateline")
        self.sort(key=lambda mw: mw.message.timestamp)

    def invalidate(self):
        self[:] = []
        self.get_logs(callback=self.list_widget.scroll_to_bottom)

    def _modified(self):
        if self.focus is not None:
            if self.focus >= len(self):
                self.focus = max(0, len(self) - 1)
        urwid.ListWalker._modified(self)

    def set_modified_callback(self, callback):
        """
        This function inherited from MonitoredList is not
        implemented in SimpleFocusListWalker.

        Use connect_signal(list_walker, "modified", ...) instead.
        """
        raise NotImplementedError('Use connect_signal('
                                  'list_walker, "modified", ...) instead.')

    def set_focus(self, position):
        """Set focus position."""
        try:
            if position < 0:
                self.get_logs()
                return
            if position >= len(self):
                raise ValueError
        except (TypeError, ValueError):
            raise IndexError("No widget at position %s" % (position, ))
        self.focus = position
        self._modified()

    def next_position(self, position):
        """
        Return position after start_from.
        """
        if len(self) - 1 <= position:
            raise IndexError
        return position + 1

    def prev_position(self, position):
        """
        Return position before start_from.
        """
        if position <= 0:
            self.get_logs()
            raise IndexError
        return position - 1

    def positions(self, reverse=False):
        """
        Optional method for returning an iterable of positions.
        """
        if reverse:
            return range(len(self) - 1, -1, -1)
        return range(len(self))


class MessageWidget(urwid.WidgetWrap):
    """A view of a message in the MessageListWidget"""

    def __init__(self, discord_client, chat_widget, m):
        self.discord = discord_client
        self.ui = self.discord.ui
        self.chat_widget = chat_widget
        self.message = m
        self.processed = discurses.processing.format_incomming(
            m, self.chat_widget)
        for at in m.attachments:
            self.processed += "\n" + at.get('url')
        self.columns_w = urwid.Columns([])
        w = urwid.AttrMap(self.columns_w, None, discurses.ui.MainUI.focus_attr)
        self.update_columns()
        self.__super.__init__(w)

    @keymaps.MESSAGE_LIST_ITEM.keypress
    def keypress(self, size, key):
        return key

    @keymaps.MESSAGE_LIST_ITEM.command
    def update_columns(self):
        channel_visible = len(self.chat_widget.channels) > 1
        channel_name = self.chat_widget.channel_names[self.message.channel]
        channel_width = min(len(channel_name) + 1, 20)
        author_nickname = self.message.author.display_name
        author_width_extra = 1 if \
            len(author_nickname.encode("utf-8")) > len(author_nickname) else 0
        author_width = 30 - channel_width + author_width_extra
        channel_attr_map = "message_channel" \
            if len(self.chat_widget.channels) > 1 and \
            self.message.channel == self.chat_widget.send_channel \
            else "message_channel_cur"
        self.columns = [
            self.Column(
                'timestamp',
                True, ('given', 7),
                self.message.timestamp.replace(
                    tzinfo=datetime.timezone.utc).astimezone(
                        tz=None).strftime("%H:%M"),
                attr_map="message_timestamp",
                padding=(1, 1)
            ),
            self.Column(
                'channel',
                channel_visible, ('given', channel_width),
                channel_name[:channel_width - 1],
                attr_map=channel_attr_map,
                padding=(0, 1)
            ),
            self.Column(
                'author',
                True, ('given', author_width),
                "{0}:".format(author_nickname.encode("utf-8")[:author_width - 2].
                              decode("utf-8", "ignore")),
                attr_map="message_author",
                padding=(0, 1),
                align="right"
            ),
            self.Column(
                'content',
                True, ('weight', 1),
                self.processed,
                attr_map="message_content",
                padding=(0, 1)
            )
        ]
        visible_cols = []
        for col in self.columns:
            if col.visible:
                visible_cols.append(col)
                self.columns_w.contents = [(c.get_widget(),
                                            self.columns_w.options(
                                                width_type=c.width[0],
                                                width_amount=c.width[1]))
                                           for c in visible_cols]

    def selectable(self) -> bool:
        return True

    @keymaps.MESSAGE_LIST_ITEM.command
    def edit_message(self):
        self.chat_widget.w_message_edit.edit_message(self.message)
        self.chat_widget.set_focus('MESSAGE_EDIT')

    @keymaps.MESSAGE_LIST_ITEM.command
    def delete_message(self):
        if self.message.author == self.discord.user or \
                self.message.channel.permissions_for(self.discord.user).\
                manage_messages:
            self.discord.async_do(self.discord.delete_message(self.message))

    @keymaps.MESSAGE_LIST_ITEM.command
    def ask_delete_message(self):
        def callback(bool):
            if bool:
                self.delete_message()
        self.chat_widget.open_confirm_prompt(
            callback, "Delete message?",
            [self.message.author.display_name + ":\n   ",
             discurses.processing.format_incomming(self.message,
                                                   self.chat_widget)])

    @keymaps.MESSAGE_LIST_ITEM.command
    def quote_message(self):
        self.chat_widget.w_edit_message.reply_to(self.message)
        self.chat_widget.set_focus('MESSAGE_EDIT')

    @keymaps.MESSAGE_LIST_ITEM.command
    def mention_author(self):
        self.chat_widget.w_edit_message.edit.insert_text("<@!{0}>".format(
            self.message.author.id))
        self.chat_widget.set_focus('MESSAGE_EDIT')

    @keymaps.MESSAGE_LIST_ITEM.command
    def yank_message(self):
        discurses.config.to_clipboard(self.message.clean_content)
        return

    @keymaps.MESSAGE_LIST_ITEM.command
    def select_channel(self):
        self.chat_widget.set_send_channel(self.message.channel)
        self.chat_widget.w_message_list.focus_message_textbox()

    class Column:
        def __init__(self,
                     name,
                     visible,
                     width,
                     content,
                     attr_map="body",
                     padding=(0, 0),
                     align="left"):
            self.name = name
            self.visible = visible
            self.width = width
            self.content = content
            self.attr_map = attr_map
            self.padding = padding
            self.align = align

        def get_widget(self):
            txt = urwid.Text(self.content, align=self.align)
            if self.padding[0] > 0 or self.padding[1] > 0:
                txt = urwid.Padding(
                    txt, left=self.padding[0], right=self.padding[1])
            return urwid.AttrMap(txt, self.attr_map)


class TopReachedWidget(urwid.WidgetWrap):
    """This widget will be displayed at the top of the channel history"""

    def __init__(self, chat_widget):
        self.chat_widget = chat_widget
        self.message = FakeMessage(datetime.datetime.min)
        self._selectable = False
        txt = urwid.Text(
            "                                                               \n"
            "                                                               \n"
            "                                                               \n"
            "                                                               \n"
            "< moo >                                                        \n"
            " -----                                                         \n"
            "        \   ^__^                                               \n"
            "         \  (oo)\_______                                       \n"
            "            (__)\       )\/\                                   \n"
            "                ||----w |                                      \n"
            "                ||     ||                                      \n"
            "                                                               \n"
            "                                                               \n"
            "Congratulations! You have reached the top, Thats awesome! Unless "
            "the channel is empty, in which case, meh... big deal.\n\n",
            align=urwid.CENTER)
        w = urwid.Padding(txt, left=5, right=5)
        self.__super.__init__(w)

    def update_columns(*args, **kwargs):
        pass


class ForbiddenWidget(urwid.WidgetWrap):
    """This widget will be displayed in channels you can't access"""

    def __init__(self, chat_widget):
        self.chat_widget = chat_widget
        self.message = FakeMessage(datetime.datetime.min)
        self._selectable = False
        txt = urwid.Text(
            "                                                               \n"
            "                                                               \n"
            "                                                               \n"
            "                                                               \n"
            "< moo >                                                        \n"
            " -----                                                         \n"
            "        \   ^__^                                               \n"
            "         \  (oo)\_______                                       \n"
            "            (__)\       )\/\                                   \n"
            "                ||----w |                                      \n"
            "                ||     ||                                      \n"
            "                                                               \n"
            "                                                               \n"
            "Oops! This channel is Forbidden. You don't have access. Sorry!\n",
            align=urwid.CENTER)
        w = urwid.Padding(txt, left=5, right=5)
        self.__super.__init__(w)

    def update_columns(*args, **kwargs):
        pass


class DatelineWidget(urwid.WidgetWrap):
    """Displays a date separator in the listwidget"""

    def __init__(self, chat_widget, date):
        self.chat_widget = chat_widget
        self.message = FakeMessage(
            datetime.datetime.combine(date,
                                      datetime.datetime.min.time()))
        self._selectable = False
        txt = urwid.Text(("dateline", date.strftime("%d.%m.%Y")),
                         align=urwid.LEFT)
        self.__super.__init__(txt)

    def update_columns(*args, **kwargs):
        pass


class FakeMessage:
    """Very much a temporary thing"""

    def __init__(self, timestamp):
        self.timestamp = timestamp
        self.id = "0"