from asyncio import Condition
import telethon
from telethon import events
import resources
import os
import curses
from subprocess import call
import drawtool
import emojis
import shlex
import sqlite3
from telethon.utils import get_display_name
import datetime
import re
from tttcutils import debug, show_stacktrace
import subprocess


class MainView():
    def __init__(self, client, stdscr):
        self.stdscr = stdscr
        self.client = client
        self.inputevent = Condition()
        self.client.add_event_handler(self.on_message, events.NewMessage)
        self.client.add_event_handler(self.on_user_update, events.UserUpdate)
        # TODO
        # self.client.add_event_handler(self.on_read, events.MessageRead)
        self.text_emojis = True

        self.macros = {}
        self.macro_recording = None
        self.macro_sequence = []

        self.inputs = ""
        self.inputs_cursor = 0

        self.edit_message = None

        self.popup = None

        self.drawtool = drawtool.Drawtool(self)
        self.fin = False
        from config import colors as colorconfig
        self.colors = colorconfig.get_colors()
        self.ready = False

        self.search_result = None
        self.search_index = None
        self.search_box = ""
        self.vimline_box = ""
        self.command_box = ""

        # index corresponds to the index in self.dialogs
        self.selected_chat = 0
        # index offset
        self.selected_chat_offset = 0
            
        self.selected_message = None

        self.mode = "normal"
        self.modestack = []

    async def quit(self):
        self.fin = True
        with await self.inputevent:
            self.inputevent.notify()

    async def on_user_update(self, event):
        user_id = event.user_id 
        if event.online != None:
            for dialog in self.dialogs:
                if event.online == True:
                    dialog["online_until"] = event.until
                elif dialog["online_until"]:
                    now = datetime.datetime.now().astimezone()
                    until = dialog["online_until"].astimezone()
                    if (now - until).seconds > 0:
                        dialog["online_until"] = None
                        dialog["online"] = False
                if dialog["dialog"].entity.id == user_id:
                    dialog["online"] = event.online

    async def on_message(self, event):
        # move chats with news up
        for idx, dialog in enumerate(self.dialogs):
            if dialog["dialog"].id == event.chat_id:
                # stuff to do upon arriving messages
                newmessage = await self.client.get_messages(dialog["dialog"], 1)
                dialog["messages"].insert(0, newmessage[0])
                if not event.out:
                    dialog["unread_count"] += 1
                    os.system(f"notify-send -i apps/telegram \"{dialog['dialog'].name}\" \"{newmessage[0].message}\"")
                front = self.dialogs.pop(idx)
                self.dialogs = [front] + self.dialogs
                break
#old dead code
#        # auto adjust relative replys to match shifted message offsets
#        if event.chat_id == self.dialogs[self.selected_chat]["dialog"].id:
#            if self.inputs.startswith("r"):
#                num = self.inputs[1:].split()[0]
#                try:
##                    num = int(s[1:].split()[0])
#                    number = int(num)
#                    msg = self.inputs.replace("r" + num, "r" + str(number+1))
#                    self.inputs = msg
#                except:
#                    pass
        # dont switch the dialoge upon arriving messages
        if idx == self.selected_chat:
            self.selected_chat = 0
        elif idx > self.selected_chat:
            self.selected_chat += 1
        elif idx < self.selected_chat:
            pass
        await self.drawtool.redraw()

    async def textinput(self):
        self.inputs = ""
        with await self.inputevent:
            await self.inputevent.wait()
        if self.fin:
            return ""
        out = self.inputs
        self.inputs = ""
        return out

    async def run(self):
        try:
            chats = await self.client.get_dialogs()
        except sqlite3.OperationalError:
            self.stdscr.addstr("Database is locked. Cannot connect with this session. Aborting")
            self.stdscr.refresh()
            await self.textinput()
            await self.quit()
        self.dialogs = [
                {
                    "dialog": dialog,
                    "unread_count": dialog.unread_count,
                    "online": dialog.entity.status.to_dict()["_"] == "UserStatusOnline" if hasattr(dialog.entity, "status") and dialog.entity.status else None,
                    "online_until": None,
                #    "last_seen": dialog.entity.status.to_dict()["was_online"] if online == False  else None,
                } for dialog in chats ]
        await self.drawtool.redraw()
        self.ready = True
        while True:
            s = await self.textinput()
            if self.fin:
                return
            if s.startswith("r"):
                try:
                    num = int(s[1:].split()[0])
                except:
                    continue
                s = s.replace("r" + str(num) + " ", "")
                reply_msg = self.dialogs[self.selected_chat]["messages"][num]
                s = emojis.encode(s)
                reply = await reply_msg.reply(s)
                await self.on_message(reply)
            elif s.startswith("media"):
                try:
                    num = int(s[5:].split()[0])
                except:
                    continue
                message = self.dialogs[self.selected_chat]["messages"][num]
                if message.media:
                    os.makedirs("/tmp/tttc/", exist_ok=True)
                    path = await self.client.download_media(message.media, "/tmp/tttc/")
                    # TODO mute calls
                    if message.media.photo:
                        sizes = message.media.photo.sizes
                        w, h = sizes[0].w, sizes[0].h
                        # w, h
                        basesize = 1500
                        w3m_command=f"0;1;0;0;{basesize};{int(basesize*h/w)};;;;;{path}\n4;\n3;"
                        W3MIMGDISPLAY="/usr/lib/w3m/w3mimgdisplay"
                        os.system(f"echo -e '{w3m_command}' | {W3MIMGDISPLAY} & disown")
                        await self.textinput()
                    else:
                        subprocess.call(["xdg-open", "{shlex.quote(path)}"], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
                        #os.system(f"(xdg-open {shlex.quote(path)} 2>&1 > /dev/null) & disown")
            else:
                s = emojis.encode(s)
                outgoing_message = await self.dialogs[self.selected_chat]["dialog"].send_message(s)
                await self.on_message(outgoing_message)
            await self.drawtool.redraw()

    def select_next_chat(self):
        # if wrapping not allowed:
        # self.selected_chat = min(self.selected_chat + 1, len(self.dialogs) - 1)
        self.selected_chat = (self.selected_chat + 1) % (len(self.dialogs))
        self.center_selected_chat()

    def select_prev_chat(self):
        # if wrapping not allowed:
        # self.selected_chat = max(self.selected_chat - 1, 0)
        self.selected_chat = (self.selected_chat - 1) % (len(self.dialogs))
        self.center_selected_chat()

    def center_selected_chat(self):
        if self.selected_chat < self.drawtool.chats_num // 2:
            self.selected_chat_offset = 0
        elif self.selected_chat > len(self.dialogs) - self.drawtool.chats_num // 2:
            self.selected_chat_offset = len(self.dialogs) - self.drawtool.chats_num
        else:
            self.selected_chat_offset = self.selected_chat - self.drawtool.chats_num // 2

    def select_chat(self, index):
        if index < -1 or index >= len(self.dialogs):
            return
        if index == -1:
            index = len(self.dialogs) - 1
        while index < self.selected_chat:
            self.select_prev_chat()
        else:
            while index > self.selected_chat:
                self.select_next_chat()


    def is_subsequence(self, xs, ys):
        xs = list(xs)
        for y in ys:
            if xs and xs[0] == y:
                xs.pop(0)
        return not xs
            
    def search_chats(self, query = None):
        if query is None:
            query = self.search_box
        if query is None:
            return # we dont search for ""
        filter_function = self.is_subsequence
        filter_function = lambda x, y: x in y
        self.search_result = [ idx for (idx, dialog) in enumerate(self.dialogs) 
                if filter_function(query.lower(), get_display_name(dialog["dialog"].entity).lower())]
        self.search_index = -1
    
    def search_next(self):
        if not self.search_result:
            return
        if self.search_index == -1:
            import bisect
            self.search_index = bisect.bisect_left(self.search_result, self.selected_chat)
            self.select_chat(self.search_result[self.search_index % len(self.search_result)])
            self.center_selected_chat()
            return
        self.search_index = (self.search_index + 1) % len(self.search_result)
        index = self.search_result[self.search_index]
        self.select_chat(index)
        self.center_selected_chat()

    def search_prev(self):
        if not self.search_result:
           return
        if self.search_index == -1:
            import bisect
            self.search_index = bisect.bisect_right(self.search_result, self.selected_chat)
            self.select_chat(self.search_result[self.search_index])
            self.center_selected_chat()
            return
        self.search_index = (self.search_index - 1) % len(self.search_result)
        self.select_chat(self.search_result[self.search_index % len(self.search_result)])
        self.center_selected_chat()

    async def call_command(self):
        command = self.vimline_box
        if command == "q":
            await self.quit()
        elif command == "pfd":
            m = ""
            for i in range(len(self.inputs)):
                m += self.inputs[i].lower() if i%2==0 else self.inputs[i].lower().swapcase()
            self.inputs = m

    async def send_message(self):
        if not self.inputs:
            return
        s = self.inputs
        s = emojis.encode(s)
        outgoing_message = await self.dialogs[self.selected_chat]["dialog"].send_message(s)
        await self.on_message(outgoing_message)
        await self.mark_read()
        self.center_selected_chat()
        self.inputs = ""

    async def mark_read(self):
        chat = self.dialogs[self.selected_chat]
        dialog = chat["dialog"]
        lastmessage = chat["messages"][0]
        await self.client.send_read_acknowledge(dialog, lastmessage)
        self.dialogs[self.selected_chat]["unread_count"] = 0

    async def show_media(self, num = None):
        if not num:
            return
        message = self.dialogs[self.selected_chat]["messages"][num]
        if message.media:
            os.makedirs("/tmp/tttc/", exist_ok=True)
            # TODO test if file exists, ask for confirmation to replace or download again
            path = await self.client.download_media(message.media, "/tmp/tttc/")
            if hasattr(message.media, "photo") and False:
                sizes = message.media.photo.sizes
                w, h = sizes[0].w, sizes[0].h
                # w, h
                basesize = 300
                w3m_command=f"0;1;0;0;{basesize};{int(basesize*h/w)};;;;;{path}\n4;\n3;"
                W3MIMGDISPLAY="/usr/lib/w3m/w3mimgdisplay"
                echo_sp = subprocess.Popen(["echo", "-e", f"{w3m_command}"], stdout = subprocess.PIPE)
                w3m_sp = subprocess.Popen([f"{W3MIMGDISPLAY}"], stdin = echo_sp.stdout)
            else:
                subprocess.Popen(["xdg-open", f"{path}"], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)

    def popup_message(self, question):
        self.modestack.append(self.mode)
        self.mode = "popupmessage"
        async def action_handler(self, key):
            pass
        self.popup = (action_handler, question)

    def spawn_popup(self, action_handler, question):
        # on q press
        self.modestack.append(self.mode)
        self.mode = "popup"
        self.popup = (action_handler, question)

    async def handle_key(self, key, redraw = True):
        if self.mode == "popupmessage":
            self.mode = self.modestack.pop()
        if not self.ready:
            return
        if key == "RESIZE":
            await self.drawtool.resize()
            return
        if self.macro_recording:
            if key != "q":
                self.macro_sequence.append(key)
        if self.mode == "search":
            if key == "ESCAPE" or key == "RETURN":
                self.mode = "normal"
            elif key == "BACKSPACE":
                if self.search_box == "":
                    self.mode = "normal"
                else:
                    self.search_box = self.search_box[0:-1]
                    self.search_chats()
                    self.search_next()
            else:
                self.search_box += key
                self.search_chats()
                self.search_next()
        elif self.mode == "vimmode":
            if key == "ESCAPE":
                self.mode = "normal"
            elif key == "RETURN":
                await self.call_command()
                self.vimline_box = ""
                self.mode = "normal"
            elif key == "BACKSPACE":
                if self.vimline_box == "":
                    self.mode = "normal"
                else:
                    self.vimline_box = self.vimline_box[0:-1]
            else:
                self.vimline_box += key
        elif self.mode == "normal":
            num = None
            try:
                num = int(key)
            except:
                pass
            if num is not None:
                self.command_box += str(num)
                await self.drawtool.redraw()
                return
            elif key == ":":
                self.mode = "vimmode"
                self.vimline_box = ""
            elif key == "RETURN" or key == "y":
                await self.send_message()
            elif key == "Q":
                await self.quit()
            elif key == "q":
                if self.macro_recording == None:
                    # start macro recording
                    async def record_macro(self, key):
                        if "a" < key.lower() < "z":
                            self.macro_recording = key
                            self.popup_message(f"recording into {key}")
                        else:
                            self.popup_message(f"Register must be [a-zA-Z]")

                    self.spawn_popup(record_macro, "Record into which register?")
                else:
                    # end macro recording
                    self.macros[self.macro_recording] = self.macro_sequence
                    self.macro_recording = None
                    self.macro_sequence = []
            elif key == "@":
                # execute macro
                async def ask_macro(self, key):
                    if key in self.macros.keys():
                        macro = self.macros[key]
                        debug(macro)
                        for k in macro:
                            await self.handle_key(k, redraw = False)
                    else:
                        self.popup_message(f"No such macro @{key}")

                self.spawn_popup(ask_macro, "Execute which macro?")
            elif key == "C":
                self.select_prev_chat()
            elif key == "c":
                self.select_next_chat()
            elif key == "E":
                self.text_emojis ^= True
            elif key == "R":
                await self.mark_read()
            elif key == "d":
                if self.command_box:
                    try:
                        n = int(self.command_box)
                    except:
                        return
                    if n >= len(self.dialogs[self.selected_chat]["messages"]):
                        #TODO: alert user
                        self.popup_message("No message by that id.")
                        await self.drawtool.redraw()
                        return
                    async def action_handler(self, key):
                        if key in ["y","Y"]:
                            to_delete = self.dialogs[self.selected_chat]["messages"][n]
                            await to_delete.delete()
                            self.dialogs[self.selected_chat]["messages"].pop(n)
                            self.command_box = ""
                        self.mode = "normal"
                    question = f"Are you really sure you want to delete message {n}? [y/N]"
                    self.spawn_popup(action_handler, question)

                    await self.drawtool.redraw()
            elif key == "e":
                if self.command_box:
                    try:
                        n = int(self.command_box)
                    except:
                        return
                    self.edit_message = self.dialogs[self.selected_chat]["messages"][n]
                    self.mode = "edit"
                    self.inputs = emojis.decode(self.edit_message.text)
                    self.command_box = ""
            elif key == "r":
                if self.command_box:
                    try:
                        n = int(self.command_box)
                    except:
                        return
                    reply_to = self.dialogs[self.selected_chat]["messages"][n]
                    s = emojis.encode(self.inputs)
                    reply = await reply_to.reply(s)
                    await self.on_message(reply)
                    self.command_box = ""
                    self.inputs = ""
            elif key == "m":
                if self.command_box:
                    try:
                        n = int(self.command_box)
                    except:
                        return
                    self.command_box = ""
                    await self.show_media(n)
            elif key == "M":
                self.center_selected_chat()
            elif key == "HOME" or key == "g":
                self.select_chat(0)
            elif key == "END" or key == "G":
                self.select_chat(-1)
            elif key == "i":
                self.mode = "insert"
            elif key == "n":
                self.search_next()
            elif key == "N":
                self.search_prev()
            elif key == "/":
                self.mode = "search"
                self.search_box = ""
            elif key == " ":
                self.drawtool.show_indices ^= True
        elif self.mode == "popup":
            action, _ = self.popup
            # I think this could break
            self.mode = self.modestack.pop()
            await action(self, key)
        elif self.mode == "edit":
            if key == "ESCAPE":
                async def ah(self, key):
                    if key in ["Y", "y", "RETURN"]:
                        edit = await self.edit_message.edit(self.inputs)
                        await self.on_message(edit)
                        # TODO: update message in chat
                        # this on_message call does not work reliably
                        self.mode = "normal"
                    else:
                        self.popup_message("Edit discarded.")
                        self.mode = "normal"
                self.spawn_popup(ah, "Do you want to save the edit? [Y/n]")
            elif key == "LEFT":
                self.insert_move_left()
            elif key == "RIGHT":
                self.insert_move_right()
            elif key == "BACKSPACE":
                self.inputs = self.inputs[0:-1]
            elif key == "RETURN":
                self.inputs += "\n"
            else:
                self.inputs += key
        elif self.mode == "insert":
            if key == "ESCAPE":
                self.mode = "normal"
            elif key == "LEFT":
                self.insert_move_left()
            elif key == "RIGHT":
                self.insert_move_right()
            elif key == "BACKSPACE":
                self.inputs = self.inputs[0:-1]
            elif key == "RETURN":
                self.inputs += "\n"
            else:
                self.inputs += key
        self.command_box = ""
        if redraw:
            await self.drawtool.redraw()

    def insert_move_left(self):
        self.inputs_cursor = max(0, self.cursor - 1)

    def insert_move_right(self):
        self.inputs_cursor = min(len(self.inputs), self.cursor + 1)

    async def handle_key_old(self, key):
        if key == "RETURN":
            with await self.inputevent:
                self.inputevent.notify()
        elif key == "":
            chat =  self.dialogs[self.selected_chat]["dialog"]
            last_message = self.dialogs[self.selected_chat]["messages"][0]
            await self.client.send_read_acknowledge(chat, max_id=last_message.id)
            self.dialogs[self.selected_chat]["unread_count"] = 0
        else:
            self.inputs += key
        self.drawtool.redraw()

