import RNS
import os
import time
import nomadnet
import LXMF

import urwid

from datetime import datetime
from nomadnet.Directory import DirectoryEntry
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox

class ConversationListDisplayShortcuts():
    def __init__(self, app):
        self.app = app

        self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info  [C-x] Delete  [C-r] Sync  [C-n] New  [C-u] Ingest URI  [C-g] Fullscreen"), "shortcutbar")

class ConversationDisplayShortcuts():
    def __init__(self, app):
        self.app = app

        self.widget = urwid.AttrMap(urwid.Text("[C-d] Send  [C-p] Paper Msg  [C-t] Title  [C-k] Clear  [C-w] Close  [C-u] Purge  [C-x] Clear History  [C-o] Sort"), "shortcutbar")

class ConversationsArea(urwid.LineBox):
    def keypress(self, size, key):
        if key == "ctrl e":
            self.delegate.edit_selected_in_directory()
        elif key == "ctrl x":
            self.delegate.delete_selected_conversation()
        elif key == "ctrl n":
            self.delegate.new_conversation()
        elif key == "ctrl u":
            self.delegate.ingest_lxm_uri()
        elif key == "ctrl r":
            self.delegate.sync_conversations()
        elif key == "ctrl g":
            self.delegate.toggle_fullscreen()
        elif key == "tab":
            self.delegate.app.ui.main_display.frame.focus_position = "header"
        elif key == "up" and (self.delegate.ilb.first_item_is_selected() or self.delegate.ilb.body_is_empty()):
            self.delegate.app.ui.main_display.frame.focus_position = "header"
        else:
            return super(ConversationsArea, self).keypress(size, key)

class DialogLineBox(urwid.LineBox):
    def keypress(self, size, key):
        if key == "esc":
            self.delegate.update_conversation_list()
        else:
            return super(DialogLineBox, self).keypress(size, key)

class ConversationsDisplay():
    list_width = 0.33
    given_list_width = 52
    cached_conversation_widgets = {}

    def __init__(self, app):
        self.app = app
        self.dialog_open = False
        self.sync_dialog = None
        self.currently_displayed_conversation = None

        def disp_list_shortcuts(sender, arg1, arg2):
            self.shortcuts_display = self.list_shortcuts
            self.app.ui.main_display.update_active_shortcuts()

        self.update_listbox()

        self.columns_widget = urwid.Columns(
            [
                # (urwid.WEIGHT, ConversationsDisplay.list_width, self.listbox),
                # (urwid.WEIGHT, 1-ConversationsDisplay.list_width, self.make_conversation_widget(None))
                (ConversationsDisplay.given_list_width, self.listbox),
                (urwid.WEIGHT, 1, self.make_conversation_widget(None))
            ],
            dividechars=0, focus_column=0, box_columns=[0]
        )

        self.list_shortcuts = ConversationListDisplayShortcuts(self.app)
        self.editor_shortcuts = ConversationDisplayShortcuts(self.app)

        self.shortcuts_display = self.list_shortcuts
        self.widget = self.columns_widget
        nomadnet.Conversation.created_callback = self.update_conversation_list

    def focus_change_event(self):
        # This hack corrects buggy styling behaviour in IndicativeListBox
        if not self.dialog_open:
            ilb_position = self.ilb.get_selected_position()
            self.update_conversation_list()
            if ilb_position != None:
                self.ilb.select_item(ilb_position)

    def update_listbox(self):
        conversation_list_widgets = []
        for conversation in self.app.conversations():
            conversation_list_widgets.append(self.conversation_list_widget(conversation))

        self.list_widgets = conversation_list_widgets
        self.ilb = IndicativeListBox(
            self.list_widgets,
            on_selection_change=self.conversation_list_selection,
            initialization_is_selection_change=False,
            highlight_offFocus="list_off_focus"
        )

        self.listbox = ConversationsArea(urwid.Filler(self.ilb, height=urwid.RELATIVE_100), title="Conversations")
        self.listbox.delegate = self

    def delete_selected_conversation(self):
        self.dialog_open = True
        item = self.ilb.get_selected_item()
        if item == None:
            return
        source_hash = item.source_hash

        def dismiss_dialog(sender):
            self.update_conversation_list()
            self.dialog_open = False

        def confirmed(sender):
            self.dialog_open = False
            self.delete_conversation(source_hash)
            nomadnet.Conversation.delete_conversation(source_hash, self.app)
            self.update_conversation_list()

        dialog = DialogLineBox(
            urwid.Pile([
                urwid.Text(
                    "Delete conversation with\n"+self.app.directory.simplest_display_str(bytes.fromhex(source_hash))+"\n",
                    align=urwid.CENTER,
                ),
                urwid.Columns([
                    (urwid.WEIGHT, 0.45, urwid.Button("Yes", on_press=confirmed)),
                    (urwid.WEIGHT, 0.1, urwid.Text("")),
                    (urwid.WEIGHT, 0.45, urwid.Button("No", on_press=dismiss_dialog)),
                ])
            ]), title="?"
        )
        dialog.delegate = self
        bottom = self.listbox

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=urwid.RELATIVE_100,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        # options = self.columns_widget.options(urwid.WEIGHT, ConversationsDisplay.list_width)
        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        self.columns_widget.contents[0] = (overlay, options)

    def edit_selected_in_directory(self):
        g = self.app.ui.glyphs
        self.dialog_open = True
        item = self.ilb.get_selected_item()
        if item == None:
            return
        source_hash_text = item.source_hash
        display_name = self.ilb.get_selected_item().display_name
        if display_name == None:
            display_name = ""

        e_id = urwid.Edit(caption="Addr : ",edit_text=source_hash_text)
        t_id = urwid.Text("Addr : "+source_hash_text)
        e_name = urwid.Edit(caption="Name : ",edit_text=display_name)

        selected_id_widget = t_id

        untrusted_selected  = False
        unknown_selected    = True
        trusted_selected    = False

        direct_selected     = True
        propagated_selected = False

        try:
            if self.app.directory.find(bytes.fromhex(source_hash_text)):
                trust_level = self.app.directory.trust_level(bytes.fromhex(source_hash_text))
                if trust_level == DirectoryEntry.UNTRUSTED:
                    untrusted_selected = True
                    unknown_selected   = False
                    trusted_selected   = False
                elif trust_level == DirectoryEntry.UNKNOWN:
                    untrusted_selected = False
                    unknown_selected   = True
                    trusted_selected   = False
                elif trust_level == DirectoryEntry.TRUSTED:
                    untrusted_selected = False
                    unknown_selected   = False
                    trusted_selected   = True

                if self.app.directory.preferred_delivery(bytes.fromhex(source_hash_text)) == DirectoryEntry.PROPAGATED:
                    direct_selected = False
                    propagated_selected = True
                    
        except Exception as e:
            pass

        trust_button_group = []
        r_untrusted = urwid.RadioButton(trust_button_group, "Untrusted", state=untrusted_selected)
        r_unknown   = urwid.RadioButton(trust_button_group, "Unknown", state=unknown_selected)
        r_trusted   = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_selected)

        method_button_group = []
        r_direct     = urwid.RadioButton(method_button_group, "Deliver directly", state=direct_selected)
        r_propagated = urwid.RadioButton(method_button_group, "Use propagation nodes", state=propagated_selected)

        def dismiss_dialog(sender):
            self.update_conversation_list()
            self.dialog_open = False

        def confirmed(sender):
            try:
                display_name = e_name.get_edit_text()
                source_hash = bytes.fromhex(e_id.get_edit_text())
                trust_level = DirectoryEntry.UNTRUSTED
                if r_unknown.state == True:
                    trust_level = DirectoryEntry.UNKNOWN
                elif r_trusted.state == True:
                    trust_level = DirectoryEntry.TRUSTED

                delivery = DirectoryEntry.DIRECT
                if r_propagated.state == True:
                    delivery = DirectoryEntry.PROPAGATED

                entry = DirectoryEntry(source_hash, display_name, trust_level, preferred_delivery=delivery)
                self.app.directory.remember(entry)
                self.update_conversation_list()
                self.dialog_open = False
                self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
            except Exception as e:
                RNS.log("Could not save directory entry. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
                if not dialog_pile.error_display:
                    dialog_pile.error_display = True
                    options = dialog_pile.options(height_type=urwid.PACK)
                    dialog_pile.contents.append((urwid.Text(""), options))
                    dialog_pile.contents.append((
                        urwid.Text(("error_text", "Could not save entry. Check your input."), align=urwid.CENTER),
                        options,)
                    )

        source_is_known = self.app.directory.is_known(bytes.fromhex(source_hash_text))
        if source_is_known:
            known_section = urwid.Divider(g["divider1"])
        else:
            def query_action(sender, user_data):
                self.close_conversation_by_hash(user_data)
                nomadnet.Conversation.query_for_peer(user_data)
                options = dialog_pile.options(height_type=urwid.PACK)
                dialog_pile.contents = [
                    (urwid.Text("Query sent"), options),
                    (urwid.Button("OK", on_press=dismiss_dialog), options)
                ]
            query_button = urwid.Button("Query network for keys", on_press=query_action, user_data=source_hash_text)
            known_section = urwid.Pile([
                urwid.Divider(g["divider1"]),
                urwid.Text(g["info"]+"\n", align=urwid.CENTER),
                urwid.Text(
                    "The identity of this peer is not known, and you cannot currently send messages to it. "
                    "You can query the network to obtain the identity.\n",
                    align=urwid.CENTER,
                ),
                query_button,
                urwid.Divider(g["divider1"]),
            ])

        dialog_pile = urwid.Pile([
            selected_id_widget,
            e_name,
            urwid.Divider(g["divider1"]),
            r_untrusted,
            r_unknown,
            r_trusted,
            urwid.Divider(g["divider1"]),
            r_direct,
            r_propagated,
            known_section,
            urwid.Columns([
                (urwid.WEIGHT, 0.45, urwid.Button("Save", on_press=confirmed)),
                (urwid.WEIGHT, 0.1, urwid.Text("")),
                (urwid.WEIGHT, 0.45, urwid.Button("Back", on_press=dismiss_dialog)),
            ])
        ])
        dialog_pile.error_display = False

        dialog = DialogLineBox(dialog_pile, title="Peer Info")
        dialog.delegate = self
        bottom = self.listbox

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=urwid.RELATIVE_100,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        # options = self.columns_widget.options(urwid.WEIGHT, ConversationsDisplay.list_width)
        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        self.columns_widget.contents[0] = (overlay, options)

    def new_conversation(self):
        self.dialog_open = True
        source_hash = ""
        display_name = ""

        e_id = urwid.Edit(caption="Addr : ",edit_text=source_hash)
        e_name = urwid.Edit(caption="Name : ",edit_text=display_name)

        trust_button_group = []
        r_untrusted = urwid.RadioButton(trust_button_group, "Untrusted")
        r_unknown   = urwid.RadioButton(trust_button_group, "Unknown", state=True)
        r_trusted   = urwid.RadioButton(trust_button_group, "Trusted")

        def dismiss_dialog(sender):
            self.update_conversation_list()
            self.dialog_open = False

        def confirmed(sender):
            try:
                existing_conversations = nomadnet.Conversation.conversation_list(self.app)
                
                display_name = e_name.get_edit_text()
                source_hash_text = e_id.get_edit_text().strip()
                source_hash = bytes.fromhex(source_hash_text)
                trust_level = DirectoryEntry.UNTRUSTED
                if r_unknown.state == True:
                    trust_level = DirectoryEntry.UNKNOWN
                elif r_trusted.state == True:
                    trust_level = DirectoryEntry.TRUSTED

                if not source_hash in [c[0] for c in existing_conversations]:
                    entry = DirectoryEntry(source_hash, display_name, trust_level)
                    self.app.directory.remember(entry)

                    new_conversation = nomadnet.Conversation(source_hash_text, nomadnet.NomadNetworkApp.get_shared_instance(), initiator=True)

                    self.update_conversation_list()

                self.display_conversation(source_hash_text)
                self.dialog_open = False

            except Exception as e:
                RNS.log("Could not start conversation. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
                if not dialog_pile.error_display:
                    dialog_pile.error_display = True
                    options = dialog_pile.options(height_type=urwid.PACK)
                    dialog_pile.contents.append((urwid.Text(""), options))
                    dialog_pile.contents.append((
                        urwid.Text(
                            ("error_text", "Could not start conversation. Check your input."),
                            align=urwid.CENTER,
                        ),
                        options,
                    ))

        dialog_pile = urwid.Pile([
            e_id,
            e_name,
            urwid.Text(""),
            r_untrusted,
            r_unknown,
            r_trusted,
            urwid.Text(""),
            urwid.Columns([
                (urwid.WEIGHT, 0.45, urwid.Button("Create", on_press=confirmed)),
                (urwid.WEIGHT, 0.1, urwid.Text("")),
                (urwid.WEIGHT, 0.45, urwid.Button("Back", on_press=dismiss_dialog)),
            ])
        ])
        dialog_pile.error_display = False

        dialog = DialogLineBox(dialog_pile, title="New Conversation")
        dialog.delegate = self
        bottom = self.listbox

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=urwid.RELATIVE_100,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        # options = self.columns_widget.options(urwid.WEIGHT, ConversationsDisplay.list_width)
        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        self.columns_widget.contents[0] = (overlay, options)

    def ingest_lxm_uri(self):
        self.dialog_open = True
        lxm_uri = ""
        e_uri = urwid.Edit(caption="URI : ",edit_text=lxm_uri)

        def dismiss_dialog(sender):
            self.update_conversation_list()
            self.dialog_open = False

        def confirmed(sender):
            try:
                local_delivery_signal = "local_delivery_occurred"
                duplicate_signal = "duplicate_lxm"
                lxm_uri = e_uri.get_edit_text().strip()

                ingest_result = self.app.message_router.ingest_lxm_uri(
                    lxm_uri,
                    signal_local_delivery=local_delivery_signal,
                    signal_duplicate=duplicate_signal
                )

                if ingest_result == False:
                    raise ValueError("The URI contained no decodable messages")
                
                elif ingest_result == local_delivery_signal:
                    rdialog_pile = urwid.Pile([
                        urwid.Text("Message was decoded, decrypted successfully, and added to your conversation list."),
                        urwid.Text(""),
                        urwid.Columns([
                            (urwid.WEIGHT, 0.6, urwid.Text("")),
                            (urwid.WEIGHT, 0.4, urwid.Button("OK", on_press=dismiss_dialog)),
                        ])
                    ])
                    rdialog_pile.error_display = False

                    rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
                    rdialog.delegate = self
                    bottom = self.listbox

                    roverlay = urwid.Overlay(
                        rdialog,
                        bottom,
                        align=urwid.CENTER,
                        width=urwid.RELATIVE_100,
                        valign=urwid.MIDDLE,
                        height=urwid.PACK,
                        left=2,
                        right=2,
                    )

                    options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
                    self.columns_widget.contents[0] = (roverlay, options)
                
                elif ingest_result == duplicate_signal:
                    rdialog_pile = urwid.Pile([
                        urwid.Text("The decoded message has already been processed by the LXMF Router, and will not be ingested again."),
                        urwid.Text(""),
                        urwid.Columns([
                            (urwid.WEIGHT, 0.6, urwid.Text("")),
                            (urwid.WEIGHT, 0.4, urwid.Button("OK", on_press=dismiss_dialog)),
                        ])
                    ])
                    rdialog_pile.error_display = False

                    rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
                    rdialog.delegate = self
                    bottom = self.listbox

                    roverlay = urwid.Overlay(
                        rdialog,
                        bottom,
                        align=urwid.CENTER,
                        width=urwid.RELATIVE_100,
                        valign=urwid.MIDDLE,
                        height=urwid.PACK,
                        left=2,
                        right=2,
                    )

                    options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
                    self.columns_widget.contents[0] = (roverlay, options)
                
                else:
                    if self.app.enable_node:
                        propagation_text = "The decoded message was not addressed to this LXMF address, but has been added to the propagation node queues, and will be distributed on the propagation network."
                    else:
                        propagation_text = "The decoded message was not addressed to this LXMF address, and has been discarded."

                    rdialog_pile = urwid.Pile([
                        urwid.Text(propagation_text),
                        urwid.Text(""),
                        urwid.Columns([
                            (urwid.WEIGHT, 0.6, urwid.Text("")),
                            (urwid.WEIGHT, 0.4, urwid.Button("OK", on_press=dismiss_dialog)),
                        ])
                    ])
                    rdialog_pile.error_display = False

                    rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
                    rdialog.delegate = self
                    bottom = self.listbox

                    roverlay = urwid.Overlay(
                        rdialog,
                        bottom,
                        align=urwid.CENTER,
                        width=urwid.RELATIVE_100,
                        valign=urwid.MIDDLE,
                        height=urwid.PACK,
                        left=2,
                        right=2,
                    )

                    options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
                    self.columns_widget.contents[0] = (roverlay, options)

            except Exception as e:
                RNS.log("Could not ingest LXM URI. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
                if not dialog_pile.error_display:
                    dialog_pile.error_display = True
                    options = dialog_pile.options(height_type=urwid.PACK)
                    dialog_pile.contents.append((urwid.Text(""), options))
                    dialog_pile.contents.append((urwid.Text(("error_text", "Could ingest LXM from URI data. Check your input."), align=urwid.CENTER), options))

        dialog_pile = urwid.Pile([
            e_uri,
            urwid.Text(""),
            urwid.Columns([
                (urwid.WEIGHT, 0.45, urwid.Button("Ingest", on_press=confirmed)),
                (urwid.WEIGHT, 0.1, urwid.Text("")),
                (urwid.WEIGHT, 0.45, urwid.Button("Back", on_press=dismiss_dialog)),
            ])
        ])
        dialog_pile.error_display = False

        dialog = DialogLineBox(dialog_pile, title="Ingest message URI")
        dialog.delegate = self
        bottom = self.listbox

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=urwid.RELATIVE_100,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        self.columns_widget.contents[0] = (overlay, options)

    def delete_conversation(self, source_hash):
        if source_hash in ConversationsDisplay.cached_conversation_widgets:
            conversation = ConversationsDisplay.cached_conversation_widgets[source_hash]
            self.close_conversation(conversation)

    def toggle_fullscreen(self):
        if ConversationsDisplay.given_list_width != 0:
            self.saved_list_width = ConversationsDisplay.given_list_width
            ConversationsDisplay.given_list_width = 0
        else:
            ConversationsDisplay.given_list_width = self.saved_list_width

        self.update_conversation_list()

    def sync_conversations(self):
        g = self.app.ui.glyphs
        self.dialog_open = True
        
        def dismiss_dialog(sender):
            self.dialog_open = False
            self.sync_dialog = None
            self.update_conversation_list()
            if self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
                self.app.cancel_lxmf_sync()

        max_messages_group = []
        r_mall = urwid.RadioButton(max_messages_group, "Download all", state=True)
        r_mlim = urwid.RadioButton(max_messages_group, "Limit to", state=False)
        ie_lim = urwid.IntEdit("", 5)
        rbs = urwid.GridFlow([r_mlim, ie_lim], 12, 1, 0, align=urwid.LEFT)

        def sync_now(sender):
            limit = None
            if r_mlim.get_state():
                limit = ie_lim.value()
            self.app.request_lxmf_sync(limit)
            self.update_sync_dialog()

        def cancel_sync(sender):
            self.app.cancel_lxmf_sync()
            self.update_sync_dialog()

        cancel_button = urwid.Button("Close", on_press=dismiss_dialog)
        sync_progress = SyncProgressBar("progress_empty" , "progress_full", current=self.app.get_sync_progress(), done=1.0, satt=None)

        real_sync_button = urwid.Button("Sync Now", on_press=sync_now)
        hidden_sync_button = urwid.Button("Cancel Sync", on_press=cancel_sync)

        if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
            sync_button = real_sync_button
        else:
            sync_button = hidden_sync_button

        button_columns = urwid.Columns([
            (urwid.WEIGHT, 0.45, sync_button),
            (urwid.WEIGHT, 0.1, urwid.Text("")),
            (urwid.WEIGHT, 0.45, cancel_button),
        ])
        real_sync_button.bc = button_columns

        pn_ident = None
        if self.app.get_default_propagation_node() != None:
            pn_hash = self.app.get_default_propagation_node()
            pn_ident = RNS.Identity.recall(pn_hash)

            if pn_ident == None:
                RNS.log("Propagation node identity is unknown, requesting from network...", RNS.LOG_DEBUG)
                RNS.Transport.request_path(pn_hash)

        if pn_ident != None:
            node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", pn_ident)
            pn_entry = self.app.directory.find(node_hash)
            pn_display_str = " "
            if pn_entry != None:
                pn_display_str += " "+str(pn_entry.display_name)
            else:
                pn_display_str += " "+RNS.prettyhexrep(pn_hash)

            dialog = DialogLineBox(
                urwid.Pile([
                    urwid.Text(""+g["node"]+pn_display_str, align=urwid.CENTER),
                    urwid.Divider(g["divider1"]),
                    sync_progress,
                    urwid.Divider(g["divider1"]),
                    r_mall,
                    rbs,
                    urwid.Text(""),
                    button_columns
                ]), title="Message Sync"
            )
        else:
            button_columns = urwid.Columns([
                (urwid.WEIGHT, 0.45, urwid.Text("" )),
                (urwid.WEIGHT, 0.1, urwid.Text("")),
                (urwid.WEIGHT, 0.45, cancel_button),
            ])
            dialog = DialogLineBox(
                urwid.Pile([
                    urwid.Text(""),
                    urwid.Text("No trusted nodes found, cannot sync!\n", align=urwid.CENTER),
                    urwid.Text(
                        "To synchronise messages from the network, "
                        "one or more nodes must be marked as trusted in the Known Nodes list, "
                        "or a node must manually be selected as the default propagation node. "
                        "Nomad Network will then automatically sync from the nearest trusted node, "
                        "or the manually selected one.",
                        align=urwid.LEFT,
                    ),
                    urwid.Text(""),
                    button_columns
                ]), title="Message Sync"
            )

        dialog.delegate = self
        dialog.sync_progress = sync_progress
        dialog.cancel_button = cancel_button
        dialog.real_sync_button = real_sync_button
        dialog.hidden_sync_button = hidden_sync_button
        dialog.bc = button_columns

        self.sync_dialog = dialog
        bottom = self.listbox

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=urwid.RELATIVE_100,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        # options = self.columns_widget.options(urwid.WEIGHT, ConversationsDisplay.list_width)
        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        self.columns_widget.contents[0] = (overlay, options)

    def update_sync_dialog(self, loop = None, sender = None):
        if self.dialog_open and self.sync_dialog != None:
            self.sync_dialog.sync_progress.set_completion(self.app.get_sync_progress())

            if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
                self.sync_dialog.bc.contents[0] = (self.sync_dialog.real_sync_button, self.sync_dialog.bc.options(urwid.WEIGHT, 0.45))
            else:
                self.sync_dialog.bc.contents[0] = (self.sync_dialog.hidden_sync_button, self.sync_dialog.bc.options(urwid.WEIGHT, 0.45))

            self.app.ui.loop.set_alarm_in(0.2, self.update_sync_dialog)


    def conversation_list_selection(self, arg1, arg2):
        pass

    def update_conversation_list(self):
        ilb_position = self.ilb.get_selected_position()
        self.update_listbox()
        # options = self.columns_widget.options(urwid.WEIGHT, ConversationsDisplay.list_width)
        options = self.columns_widget.options(urwid.GIVEN, ConversationsDisplay.given_list_width)
        if not (self.dialog_open and self.sync_dialog != None):
            self.columns_widget.contents[0] = (self.listbox, options)
        else:
            bottom = self.listbox
            overlay = urwid.Overlay(
                self.sync_dialog,
                bottom,
                align=urwid.CENTER,
                width=urwid.RELATIVE_100,
                valign=urwid.MIDDLE,
                height=urwid.PACK,
                left=2,
                right=2,
            )
            self.columns_widget.contents[0] = (overlay, options)

        if ilb_position != None:
            self.ilb.select_item(ilb_position)
        nomadnet.NomadNetworkApp.get_shared_instance().ui.loop.draw_screen()

        if self.app.ui.main_display.sub_displays.active_display == self.app.ui.main_display.sub_displays.conversations_display:
            if self.currently_displayed_conversation != None:
                if self.app.conversation_is_unread(self.currently_displayed_conversation):
                    self.app.mark_conversation_read(self.currently_displayed_conversation)
                    try:
                        if os.path.isfile(self.app.conversationpath + "/" + self.currently_displayed_conversation + "/unread"):
                            os.unlink(self.app.conversationpath + "/" + self.currently_displayed_conversation + "/unread")
                    except Exception as e:
                        raise e




    def display_conversation(self, sender=None, source_hash=None):
        if self.currently_displayed_conversation != None:
            if self.app.conversation_is_unread(self.currently_displayed_conversation):
                self.app.mark_conversation_read(self.currently_displayed_conversation)

        self.currently_displayed_conversation = source_hash
        # options = self.widget.options(urwid.WEIGHT, 1-ConversationsDisplay.list_width)
        options = self.widget.options(urwid.WEIGHT, 1)
        self.widget.contents[1] = (self.make_conversation_widget(source_hash), options)
        if source_hash == None:
            self.widget.focus_position = 0
        else:
            if self.app.conversation_is_unread(source_hash):
                self.app.mark_conversation_read(source_hash)
                self.update_conversation_list()

            self.widget.focus_position = 1
            conversation_position = None
            index = 0
            for widget in self.list_widgets:
                if widget.source_hash == source_hash:
                    conversation_position = index
                index += 1

            if conversation_position != None:
                self.ilb.select_item(conversation_position)
        

    def make_conversation_widget(self, source_hash):
        if source_hash in ConversationsDisplay.cached_conversation_widgets:
            conversation_widget = ConversationsDisplay.cached_conversation_widgets[source_hash]
            if source_hash != None:
                conversation_widget.update_message_widgets(replace=True)

            conversation_widget.check_editor_allowed()
            return conversation_widget
        else:
            widget = ConversationWidget(source_hash)
            widget.delegate = self
            ConversationsDisplay.cached_conversation_widgets[source_hash] = widget

            widget.check_editor_allowed()
            return widget

    def close_conversation_by_hash(self, conversation_hash):
        if conversation_hash in ConversationsDisplay.cached_conversation_widgets:
            ConversationsDisplay.cached_conversation_widgets.pop(conversation_hash)

        if self.currently_displayed_conversation == conversation_hash:
            self.display_conversation(sender=None, source_hash=None)

    def close_conversation(self, conversation):
        if conversation.source_hash in ConversationsDisplay.cached_conversation_widgets:
            ConversationsDisplay.cached_conversation_widgets.pop(conversation.source_hash)

        if self.currently_displayed_conversation == conversation.source_hash:
            self.display_conversation(sender=None, source_hash=None)


    def conversation_list_widget(self, conversation):
        trust_level  = conversation[2]
        display_name = conversation[1]
        source_hash  = conversation[0]
        unread       = conversation[4]

        g = self.app.ui.glyphs

        if trust_level == DirectoryEntry.UNTRUSTED:
            symbol        = g["cross"]
            style         = "list_untrusted"
            focus_style   = "list_focus_untrusted"
        elif trust_level == DirectoryEntry.UNKNOWN:
            symbol        = "?"
            style         = "list_unknown"
            focus_style   = "list_focus"
        elif trust_level == DirectoryEntry.TRUSTED:
            symbol        = g["check"]
            style         = "list_trusted"
            focus_style   = "list_focus_trusted"
        elif trust_level == DirectoryEntry.WARNING:
            symbol        = g["warning"]
            style         = "list_warning"
            focus_style   = "list_focus"
        else:
            symbol        = g["warning"]
            style         = "list_untrusted"
            focus_style   = "list_focus_untrusted"

        display_text = symbol

        if display_name != None and display_name != "":
            display_text += " "+display_name

        if trust_level != DirectoryEntry.TRUSTED:
            display_text += " <"+source_hash+">"
        
        if trust_level != DirectoryEntry.UNTRUSTED:
            if unread:
                if source_hash != self.currently_displayed_conversation:
                    display_text += " "+g["unread"]


        widget = ListEntry(display_text)
        urwid.connect_signal(widget, "click", self.display_conversation, conversation[0])
        display_widget = urwid.AttrMap(widget, style, focus_style)
        display_widget.source_hash = source_hash
        display_widget.display_name = display_name

        return display_widget


    def shortcuts(self):
        focus_path = self.widget.get_focus_path()
        if focus_path[0] == 0:
            return self.list_shortcuts
        elif focus_path[0] == 1:
            return self.editor_shortcuts
        else:
            return self.list_shortcuts

class ListEntry(urwid.Text):
    _selectable = True

    signals = ["click"]

    def keypress(self, size, key):
        """
        Send 'click' signal on 'activate' command.
        """
        if self._command_map[key] != urwid.ACTIVATE:
            return key

        self._emit('click')

    def mouse_event(self, size, event, button, x, y, focus):
        """
        Send 'click' signal on button 1 press.
        """
        if button != 1 or not urwid.util.is_mouse_press(event):
            return False

        self._emit('click')
        return True

class MessageEdit(urwid.Edit):
    def keypress(self, size, key):
        if key == "ctrl d":
            self.delegate.send_message()
        elif key == "ctrl p":
            self.delegate.paper_message()
        elif key == "ctrl k":
            self.delegate.clear_editor()
        elif key == "up":
            y = self.get_cursor_coords(size)[1]
            if y == 0:
                if self.delegate.full_editor_active and self.name == "title_editor":
                    self.delegate.frame.focus_position = "body"
                elif not self.delegate.full_editor_active and self.name == "content_editor":
                    self.delegate.frame.focus_position = "body"
                else:
                    return super(MessageEdit, self).keypress(size, key)
            else:
                return super(MessageEdit, self).keypress(size, key)
        else:
            return super(MessageEdit, self).keypress(size, key)


class ConversationFrame(urwid.Frame):
    def keypress(self, size, key):
        if self.focus_position == "body":
            if key == "up" and self.delegate.messagelist.top_is_visible:
                nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
            elif key == "down" and self.delegate.messagelist.bottom_is_visible:
                self.focus_position = "footer"
            else:
                return super(ConversationFrame, self).keypress(size, key)
        elif key == "ctrl k":
            self.delegate.clear_editor()
        else:
            return super(ConversationFrame, self).keypress(size, key)

class ConversationWidget(urwid.WidgetWrap):
    def __init__(self, source_hash):
        self.app = nomadnet.NomadNetworkApp.get_shared_instance()
        g = self.app.ui.glyphs
        if source_hash == None:
            self.frame = None
            display_widget = urwid.LineBox(urwid.Filler(urwid.Text("\n  No conversation selected"), "top"))
            super().__init__(display_widget)
        else:
            if source_hash in ConversationsDisplay.cached_conversation_widgets:
                return ConversationsDisplay.cached_conversation_widgets[source_hash]
            else:
                self.source_hash = source_hash
                self.conversation = nomadnet.Conversation(source_hash, nomadnet.NomadNetworkApp.get_shared_instance())
                self.message_widgets = []
                self.sort_by_timestamp = False
                self.updating_message_widgets = False

                self.update_message_widgets()

                self.conversation.register_changed_callback(self.conversation_changed)

                #title_editor  = MessageEdit(caption="\u270E", edit_text="", multiline=False)
                title_editor  = MessageEdit(caption="", edit_text="", multiline=False)
                title_editor.delegate = self
                title_editor.name = "title_editor"

                #msg_editor  = MessageEdit(caption="\u270E", edit_text="", multiline=True)
                msg_editor  = MessageEdit(caption="", edit_text="", multiline=True)
                msg_editor.delegate = self
                msg_editor.name = "content_editor"

                header = None
                if self.conversation.trust_level == DirectoryEntry.UNTRUSTED:
                    header = urwid.AttrMap(
                        urwid.Padding(
                            urwid.Text(g["warning"]+" Warning: Conversation with untrusted peer "+g["warning"], align=urwid.CENTER)),
                        "msg_warning_untrusted",
                    )

                self.minimal_editor = urwid.AttrMap(msg_editor, "msg_editor")
                self.minimal_editor.name = "minimal_editor"

                title_columns = urwid.Columns([
                    (8, urwid.Text("Title")),
                    urwid.AttrMap(title_editor, "msg_editor"),
                ])

                content_columns = urwid.Columns([
                    (8, urwid.Text("Content")),
                    urwid.AttrMap(msg_editor, "msg_editor")
                ])

                self.full_editor = urwid.Pile([
                    title_columns,
                    content_columns
                ])
                self.full_editor.name = "full_editor"

                self.content_editor = msg_editor
                self.title_editor = title_editor
                self.full_editor_active = False

                self.frame = ConversationFrame(
                    self.messagelist,
                    header=header,
                    footer=self.minimal_editor,
                    focus_part="footer"
                )
                self.frame.delegate = self

                self.display_widget = urwid.LineBox(
                    self.frame
                )
                
                super().__init__(self.display_widget)

    def clear_history_dialog(self):
        def dismiss_dialog(sender):
            self.dialog_open = False
            self.conversation_changed(None)

        def confirmed(sender):
            self.dialog_open = False
            self.conversation.clear_history()
            self.conversation_changed(None)


        dialog = DialogLineBox(
            urwid.Pile([
                urwid.Text("Clear conversation history\n", align=urwid.CENTER),
                urwid.Columns([
                    (urwid.WEIGHT, 0.45, urwid.Button("Yes", on_press=confirmed)),
                    (urwid.WEIGHT, 0.1, urwid.Text("")),
                    (urwid.WEIGHT, 0.45, urwid.Button("No", on_press=dismiss_dialog)),
                ])
            ]), title="?"
        )
        dialog.delegate = self
        bottom = self.messagelist

        overlay = urwid.Overlay(
            dialog,
            bottom,
            align=urwid.CENTER,
            width=34,
            valign=urwid.MIDDLE,
            height=urwid.PACK,
            left=2,
            right=2,
        )

        self.frame.contents["body"] = (overlay, self.frame.options())
        self.frame.focus_position = "body"
    
    def toggle_editor(self):
        if self.full_editor_active:
            self.frame.contents["footer"] = (self.minimal_editor, None)
            self.full_editor_active = False
        else:
            self.frame.contents["footer"] = (self.full_editor, None)
            self.full_editor_active = True

    def check_editor_allowed(self):
        g = self.app.ui.glyphs
        if self.frame:
            allowed = nomadnet.NomadNetworkApp.get_shared_instance().directory.is_known(bytes.fromhex(self.source_hash))
            if allowed:
                self.frame.contents["footer"] = (self.minimal_editor, None)
            else:
                warning = urwid.AttrMap(
                    urwid.Padding(urwid.Text(
                        "\n"+g["info"]+"\n\nYou cannot currently message this peer, since its identity keys are not known. "
                                       "The keys have been requested from the network and should arrive shortly, if available. "
                                       "Close this conversation and reopen it to try again.\n\n"
                                       "To query the network manually, select this conversation in the conversation list, "
                                       "press Ctrl-E, and use the query button.\n",
                        align=urwid.CENTER,
                    )),
                    "msg_header_caution",
                )
                self.frame.contents["footer"] = (warning, None)

    def toggle_focus_area(self):
        name = ""
        try:
            name = self.frame.get_focus_widgets()[0].name
        except Exception as e:
            pass

        if name == "messagelist":
            self.frame.focus_position = "footer"
        elif name == "minimal_editor" or name == "full_editor":
            self.frame.focus_position = "body"

    def keypress(self, size, key):
        if key == "tab":
            self.toggle_focus_area()
        elif key == "ctrl w":
            self.close()
        elif key == "ctrl u":
            self.conversation.purge_failed()
            self.conversation_changed(None)
        elif key == "ctrl t":
            self.toggle_editor()
        elif key == "ctrl x":
            self.clear_history_dialog()
        elif key == "ctrl g":
            nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.sub_displays.conversations_display.toggle_fullscreen()
        elif key == "ctrl o":
            self.sort_by_timestamp ^= True
            self.conversation_changed(None)
        else:
            return super(ConversationWidget, self).keypress(size, key)

    def conversation_changed(self, conversation):
        self.update_message_widgets(replace = True)

    def update_message_widgets(self, replace = False):
        while self.updating_message_widgets:
            time.sleep(0.5)

        self.updating_message_widgets = True
        self.message_widgets = []
        added_hashes = []
        for message in self.conversation.messages:
            message_hash = message.get_hash()
            if not message_hash in added_hashes:
                added_hashes.append(message_hash)
                message_widget = LXMessageWidget(message)
                self.message_widgets.append(message_widget)
        
        if self.sort_by_timestamp:
            self.message_widgets.sort(key=lambda m: m.timestamp, reverse=False)
        else:
            self.message_widgets.sort(key=lambda m: m.sort_timestamp, reverse=False)

        from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox
        self.messagelist = IndicativeListBox(self.message_widgets, position = len(self.message_widgets)-1)
        self.messagelist.name = "messagelist"
        if replace:
            self.frame.contents["body"] = (self.messagelist, None)
            nomadnet.NomadNetworkApp.get_shared_instance().ui.loop.draw_screen()

        self.updating_message_widgets = False


    def clear_editor(self):
        self.content_editor.set_edit_text("")
        self.title_editor.set_edit_text("")

    def send_message(self):
        content = self.content_editor.get_edit_text()
        title = self.title_editor.get_edit_text()
        if not content == "":
            if self.conversation.send(content, title):
                self.clear_editor()
            else:
                pass

    def paper_message_saved(self, path):
        g = self.app.ui.glyphs
        def dismiss_dialog(sender):
            self.dialog_open = False
            self.conversation_changed(None)

        dialog = DialogLineBox(
            urwid.Pile([
                urwid.Text("The paper message was saved to:\n\n"+str(path)+"\n", align=urwid.CENTER),
                urwid.Columns([
                    (urwid.WEIGHT, 0.6, urwid.Text("")),
                    (urwid.WEIGHT, 0.4, urwid.Button("OK", on_press=dismiss_dialog)),
                ])
            ]), title=g["papermsg"].replace(" ", "")
        )
        dialog.delegate = self
        bottom = self.messagelist

        overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=60, valign=urwid.MIDDLE, height=urwid.PACK, left=2, right=2)

        self.frame.contents["body"] = (overlay, self.frame.options())
        self.frame.focus_position = "body"

    def print_paper_message_qr(self):
        content = self.content_editor.get_edit_text()
        title = self.title_editor.get_edit_text()
        if not content == "":
            if self.conversation.paper_output(content, title):
                self.clear_editor()
            else:
                self.paper_message_failed()

    def save_paper_message_qr(self):
        content = self.content_editor.get_edit_text()
        title = self.title_editor.get_edit_text()
        if not content == "":
            output_result = self.conversation.paper_output(content, title, mode="save_qr")
            if output_result != False:
                self.clear_editor()
                self.paper_message_saved(output_result)
            else:
                self.paper_message_failed()

    def save_paper_message_uri(self):
        content = self.content_editor.get_edit_text()
        title = self.title_editor.get_edit_text()
        if not content == "":
            output_result = self.conversation.paper_output(content, title, mode="save_uri")
            if output_result != False:
                self.clear_editor()
                self.paper_message_saved(output_result)
            else:
                self.paper_message_failed()

    def paper_message(self):
        def dismiss_dialog(sender):
            self.dialog_open = False
            self.conversation_changed(None)

        def print_qr(sender):
            dismiss_dialog(self)
            self.print_paper_message_qr()

        def save_qr(sender):
            dismiss_dialog(self)
            self.save_paper_message_qr()

        def save_uri(sender):
            dismiss_dialog(self)
            self.save_paper_message_uri()

        dialog = DialogLineBox(
            urwid.Pile([
                urwid.Text(
                    "Select the desired paper message output method.\nSaved files will be written to:\n\n"+str(self.app.downloads_path)+"\n",
                    align=urwid.CENTER,
                ),
                urwid.Columns([
                    (urwid.WEIGHT, 0.5, urwid.Button("Print QR", on_press=print_qr)),
                    (urwid.WEIGHT, 0.1, urwid.Text("")),
                    (urwid.WEIGHT, 0.5, urwid.Button("Save QR", on_press=save_qr)),
                    (urwid.WEIGHT, 0.1, urwid.Text("")),
                    (urwid.WEIGHT, 0.5, urwid.Button("Save URI", on_press=save_uri)),
                    (urwid.WEIGHT, 0.1, urwid.Text("")),
                    (urwid.WEIGHT, 0.5, urwid.Button("Cancel", on_press=dismiss_dialog))
                ])
            ]), title="Create Paper Message"
        )
        dialog.delegate = self
        bottom = self.messagelist

        overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=60, valign=urwid.MIDDLE, height=urwid.PACK, left=2, right=2)

        self.frame.contents["body"] = (overlay, self.frame.options())
        self.frame.focus_position = "body"

    def paper_message_failed(self):
        def dismiss_dialog(sender):
            self.dialog_open = False
            self.conversation_changed(None)

        dialog = DialogLineBox(
            urwid.Pile([
                urwid.Text(
                    "Could not output paper message,\ncheck your settings. See the log\nfile for any error messages.\n",
                    align=urwid.CENTER,
                ),
                urwid.Columns([
                    (urwid.WEIGHT, 0.6, urwid.Text("")),
                    (urwid.WEIGHT, 0.4, urwid.Button("OK", on_press=dismiss_dialog)),
                ])
            ]), title="!"
        )
        dialog.delegate = self
        bottom = self.messagelist

        overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=34, valign=urwid.MIDDLE, height=urwid.PACK, left=2, right=2)

        self.frame.contents["body"] = (overlay, self.frame.options())
        self.frame.focus_position = "body"

    def close(self):
        self.delegate.close_conversation(self)


class LXMessageWidget(urwid.WidgetWrap):
    def __init__(self, message):
        app = nomadnet.NomadNetworkApp.get_shared_instance()
        g = app.ui.glyphs
        self.timestamp = message.get_timestamp()
        self.sort_timestamp = message.sort_timestamp
        time_format = app.time_format
        message_time = datetime.fromtimestamp(self.timestamp)
        encryption_string = ""
        if message.get_transport_encrypted():
            encryption_string = " ["+g["encrypted"]+" "+str(message.get_transport_encryption())+"]"
        else:
            encryption_string = " ["+g["plaintext"]+" "+str(message.get_transport_encryption())+"]"
        
        title_string = message_time.strftime(time_format)+encryption_string

        if app.lxmf_destination.hash == message.lxm.source_hash:
            if message.lxm.state == LXMF.LXMessage.DELIVERED:
                header_style = "msg_header_delivered"
                title_string = g["check"]+" "+title_string
            elif message.lxm.state == LXMF.LXMessage.FAILED:
                header_style = "msg_header_failed"
                title_string = g["cross"]+" "+title_string
            elif message.lxm.method == LXMF.LXMessage.PROPAGATED and message.lxm.state == LXMF.LXMessage.SENT:
                header_style = "msg_header_propagated"
                title_string = g["sent"]+" "+title_string
            elif message.lxm.method == LXMF.LXMessage.PAPER and message.lxm.state == LXMF.LXMessage.PAPER:
                header_style = "msg_header_propagated"
                title_string = g["papermsg"]+" "+title_string
            elif message.lxm.state == LXMF.LXMessage.SENT:
                header_style = "msg_header_sent"
                title_string = g["sent"]+" "+title_string
            else:
                header_style = "msg_header_sent"
                title_string = g["arrow_r"]+" "+title_string
        else:
            if message.signature_validated():
                header_style = "msg_header_ok"
                title_string = g["check"]+" "+title_string
            else:
                header_style = "msg_header_caution"
                title_string = g["warning"]+" "+message.get_signature_description() + "\n  " + title_string

        if message.get_title() != "":
            title_string += " | " + message.get_title()

        title = urwid.AttrMap(urwid.Text(title_string), header_style)

        display_widget = urwid.Pile([
            title,
            urwid.Text(message.get_content()),
            urwid.Text("")
        ])

        super().__init__(display_widget)

class SyncProgressBar(urwid.ProgressBar):
    def get_text(self):
        status = nomadnet.NomadNetworkApp.get_shared_instance().get_sync_status()
        show_percent = nomadnet.NomadNetworkApp.get_shared_instance().sync_status_show_percent()
        if show_percent:
            return status+" "+super().get_text()
        else:
            return status
