use cli_clipboard::ClipboardProvider as _;
use client_shared::message::{self, MessageSanitized};
use client_shared::utils::ConnectionError;
use client_shared::{DEFAULT_PASSWORD, MAX_MESSAGE_LENGTH, MAX_USERNAME_LENGTH};
use client_shared::ui::Language;
use cursive::view::{Nameable as _, Resizable as _};
use cursive::views::{
    Button, Dialog, DummyView, EditView, LinearLayout, NamedView, Panel, ScrollView, SelectView,
    TextArea, TextView,
};
use cursive::{event, Cursive};
use utils::hash::Hashable as _;

use crate::actions;
use crate::{
    get_appdata, save_appdata, Appdata
};

pub const USERS_PANEL_ID: &str = "users_view_id";
pub const MESSAGE_PANEL_ID: &str = "message_view_id";
pub const CHANNEL_VIEW_ID: &str = "channel_view_id";
pub const CHANNEL_NEW_FIELD_ID: &str = "channel_new_field_id";
pub const CHANNEL_NEW_BUTTON_ID: &str = "CHANNEL_NEW_BUTTON_ID";
pub const CHANNEL_CURRENT_PANEL_ID: &str = "CHANNEL_CURRENT_PANEL_ID";
pub const INPUT_FIELD_ID: &str = "input_field_id";
pub const INPUT_PANEL_ID: &str = "input_panel_id";
pub const INPUT_BUTTON_ID: &str = "input_button_id";
pub const INPUT_CLEAR_BUTTON_ID: &str = "input_clear_button_id";
pub const LANGUAGE_BUTTON_ID: &str = "language_button_id";
pub const CURRENT_USERNAME_PANEL_ID: &str = "current_username_view_id";
pub const USERNAME_FIELD_ID: &str = "username_field_id";
pub const USERNAME_BUTTON_ID: &str = "username_button_id";
pub const REFRESH_BUTTON_ID: &str = "refresh_button_id";
pub const QUIT_BUTTON_ID: &str = "quit_button_id";
pub const BLOCKED_WORDS_BUTTON_ID: &str = "blocked_words_view_id";
pub const SERVER_SETTINGS_ADDRESS_FIELD_ID: &str = "server_settings_address_field_id";
pub const SERVER_SETTINGS_REFRESH_FIELD_ID: &str = "server_settings_refresh_field_id";
pub const SERVER_SETTINGS_PASSWORD_FIELD_ID: &str = "SERVER_SETTINGS_PASSWORD_FIELD_ID";
pub const SERVER_SETTINGS_BUTTON_ID: &str = "server_settings_button_id";
pub const INFO_BUTTON_ID: &str = "INFO_BUTTON_ID";

pub const DIALOGUE_MIN_SIZE: (u16, u16) = (20, 5);
pub const SERVER_SETTINGS_FIELD_SIZE: (usize, usize) = (60, 1);
pub const USER_PANEL_SIZE: usize = MAX_USERNAME_LENGTH + 2;

#[allow(unused)]
pub enum Labels {
    Ok,
    Cancel,
    Close,
    Copy,
    Submit,
    Send,
    New,
    Clear,
    Error,
    User,
    Users,
    Message,
    Messages,
    Channel,
    Channels,
    NewChannel,
    InvalidChannelNameExplination,
    TypeAMessage,
    SetLanguage,
    SetUsername,
    InvalidUsernameExplination,
    Username,
    InvalidMessage,
    Success,
    SuccessfullySentMessage,
    FailedToSendMessage(ConnectionError),
    FailedToLoadMessages(ConnectionError),
    RefreshButton,
    BlockedWords,
    ServerSettings,
    ServerAddress,
    ServerRefreshRate,
    Password,
    Information,
    Time,
    Sender,
    Content,
    QuitButton,
}

impl Labels {
    // TODO (low): Double check the translations
    pub fn localize<'a>(&self, language: Language) -> String {
        let buf: [String; 3];

        let set: [&str; 3] = match self {
            Labels::Ok => ["OK", "OK", "OK"],
            Labels::Cancel => ["Cancel", "Annuleren", "キャンセル"],
            Labels::Close => ["Close", "Sluiten", "閉じる"],
            Labels::Copy => ["Copy", "Kopiëren", "コピー"],
            Labels::Error => ["Error", "Fout", "エラー"],
            Labels::User => ["User", "Gebruiker", "ユーザー"],
            Labels::Messages => ["Messages", "Berichten", "メッセージ"],
            Labels::Message => ["Message", "Bericht", "メッセージ"],
            Labels::Users => ["Users", "Gebruikers", "ユーザー"],
            Labels::TypeAMessage => [
                "Type a message",
                "Typ een bericht",
                "メッセージを入力してください",
            ],
            Labels::SetUsername => [
                "Set username",
                "Stel gebruikersnaam in",
                "ユーザー名を設定する",
            ],
            Labels::Send => ["Send", "Verzenden", "送信する"],
            Labels::Clear => ["Clear", "Wissen", "クリア"],
            Labels::Username => ["Username", "Gebruikersnaam", "ユーザー名"],
            Labels::SetLanguage => ["Set language", "Stel taal in", "言語を設定する"],
            Labels::FailedToSendMessage(e) => {
                let reason_buf: [String; 3];

                match e {
                    ConnectionError::ReqwestError(_) => {
                        reason_buf = [
                            "Connection error.".to_string(),
                            "Verbindingsfout.".to_string(),
                            "接続エラー。".to_string(),
                        ];
                    }
                    ConnectionError::StatusCodeError(code) => {
                        // NOTE: Needs to match the server
                        if *code == 400 {
                            reason_buf = [
                                "Message too long.".to_string(),
                                "Bericht te lang.".to_string(),
                                "メッセージが長すぎます。".to_string(),
                            ];
                        } else {
                            reason_buf = [
                                format!("Status code: {code}"),
                                format!("Statuscode: {code}"),
                                format!("ステータスコード: {code}"),
                            ]
                        }
                    }
                    ConnectionError::UrlError(_) => {
                        reason_buf = [
                            "Invalid URL.".to_string(),
                            "Ongeldig URL.".to_string(),
                            "無効なURL。".to_string(),
                        ];
                    }
                    // TODO: Check if it's safe to remove this.
                    // ConnectionError::EncryptionError(_) => {
                    //     reason_buf = [
                    //         "Encryption error.".to_string(),
                    //         "Encryptiefout.".to_string(),
                    //         "暗号化エラー。".to_string(),
                    //     ];
                    // }
                };

                buf = [
                    format!("Failed to send message. {}", reason_buf[0]),
                    format!("Bericht verzenden mislukt. {}", reason_buf[1]),
                    format!("メッセージの送信に失敗しました。 {}", reason_buf[2]),
                ];

                [buf[0].as_str(), buf[1].as_str(), buf[2].as_str()]
            }
            Labels::FailedToLoadMessages(error) => {
                let reason_buf: [String; 3];

                match error {
                    ConnectionError::ReqwestError(_) => {
                        reason_buf = [
                            "Connection error.".to_string(),
                            "Verbindingsfout.".to_string(),
                            "接続エラー。".to_string(),
                        ];
                    }
                    ConnectionError::StatusCodeError(code) => {
                        reason_buf = [
                            format!("Status code: {code}"),
                            format!("Statuscode: {code}"),
                            format!("ステータスコード: {code}"),
                        ];
                    }
                    ConnectionError::UrlError(_) => {
                        reason_buf = [
                            "Invalid URL.".to_string(),
                            "Ongeldig URL.".to_string(),
                            "無効なURL。".to_string(),
                        ];
                    }
                    // TODO: Check if it's safe to remove this.
                    // ConnectionError::EncryptionError(_) => {
                    //     reason_buf = [
                    //         "Decryption error.".to_string(),
                    //         "Fout bij het ontcijferen.".to_string(),
                    //         "復号エラー".to_string(),
                    //     ];
                    // }
                };

                buf = [
                    format!("Failed to load messages. {}", reason_buf[0]),
                    format!("Berichten laden mislukt. {}", reason_buf[1]),
                    format!("メッセージの読み込みに失敗しました。 {}", reason_buf[2]),
                ];

                [buf[0].as_str(), buf[1].as_str(), buf[2].as_str()]
            }
            Labels::QuitButton => ["Quit", "Verlaten", "終了する"],
            Labels::RefreshButton => ["Refresh", "Vernieuwen", "更新する"],
            Labels::InvalidUsernameExplination => {
                buf = [
                    r"Invalid username. Must match ^[a-zA-Z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                    r"Ongeldige gebruikersnaam. Moet overeenkomen met ^[a-zA-Z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                    r"無効なユーザー名。 ^[a-zA-Z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$ に一致する必要があります"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                ];

                [buf[0].as_str(), buf[1].as_str(), buf[2].as_str()]
            }
            Labels::InvalidMessage => {
                buf = [
                    "Invalid message. Must contain fewer than ${MAX_MESSAGE_LENGTH} characters"
                        .replace("${MAX_MESSAGE_LENGTH}", &MAX_MESSAGE_LENGTH.to_string()),
                    "Ongeldig bericht. Moet minder dan ${MAX_MESSAGE_LENGTH} tekens bevatten"
                        .replace("${MAX_MESSAGE_LENGTH}", &MAX_MESSAGE_LENGTH.to_string()),
                    "無効なメッセージ。 ${MAX_MESSAGE_LENGTH}文字未満である必要があります"
                        .replace("${MAX_MESSAGE_LENGTH}", &MAX_MESSAGE_LENGTH.to_string()),
                ];

                [buf[0].as_str(), buf[1].as_str(), buf[2].as_str()]
            }
            Labels::BlockedWords => [
                "Blocked words",
                "Geblokkeerde woorden",
                "ブロックされた単語",
            ],
            Labels::Submit => ["Submit", "Indienen", "提出する"],
            Labels::ServerSettings => ["Server settings", "Serverinstellingen", "サーバー設定"],
            Labels::ServerAddress => [
                "Server address (IP:Port)",
                "Serveradres (IP:poort)",
                "サーバーアドレス（IP:ポート）",
            ],
            Labels::ServerRefreshRate => [
                "Update rate (seconds)",
                "Updatefrequentie (seconden)",
                "更新率 (秒)",
            ],
            Labels::Channels => ["Channels", "Kanalen", "チャンネル"],
            Labels::NewChannel => ["New Channel", "Nieuw kanaal", "新しいチャンネル"],
            Labels::InvalidChannelNameExplination => {
                buf = [
                    r"Invalid channel name. Must match ^[a-z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                    r"Ongeldige kanaalnaam. Moet overeenkomen met ^[a-z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                    r"無効なチャンネル名。 ^[a-z0-9#_\-\.]{2,${MAX_USERNAME_LENGTH}}$ に一致する必要があります"
                        .replace("${MAX_USERNAME_LENGTH}", &MAX_USERNAME_LENGTH.to_string()),
                ];

                [buf[0].as_str(), buf[1].as_str(), buf[2].as_str()]
            }
            Labels::New => ["New", "Nieuw", "新"],
            Labels::Channel => ["Channel", "Kanaal", "チャネル"],
            Labels::Password => ["Password", "Wachtwoord", "パスワード"],
            Self::Success => ["Success", "Succes", "成功"],
            Self::SuccessfullySentMessage => [
                "Message sent!",
                "Bericht verzonden!",
                "メッセージを送信しました!",
            ],
            Self::Information => [
                "Info",
                "Informatie",
                "情報",
            ],
            Self::Time => ["Time", "Tijd", "時間"],
            Self::Sender => ["Sender", "Afzender", "送信者"],
            Self::Content => ["Content", "Inhoud", "コンテンツ"],
        };

        let idx = match language {
            Language::English => 0,
            Language::Dutch => 1,
            Language::Japanese => 2,
        };

        set[idx].to_string()
    }
}

pub fn alert<S>(siv: &mut Cursive, title: S, text: S)
where
    S: Into<String>,
{
    let language = get_appdata(siv).persistent_data.language;

    keybind_setup_close_once(siv);

    siv.add_layer(
        Dialog::text(text)
            .title(title)
            .button(Labels::Close.localize(language), |siv| {
                keybind_close_manual_end(siv, false);
            })
            .min_size((20, 5)),
    );
}

pub fn copyable<S>(siv: &mut Cursive, title: S, text: S)
where
    S: Into<String> + Clone,
{
    let language = get_appdata(siv).persistent_data.language;

    keybind_setup_close_once(siv);

    let text_s = text.clone().into();
    siv.add_layer(
        Dialog::text(text)
            .title(title)
            .button(Labels::Copy.localize(language), move |siv| {

                let ctx = cli_clipboard::ClipboardContext::new();
                if ctx.and_then(|mut x| x.set_contents(text_s.clone())).is_err() {
                    logging::error("Failed to copy text to clipboard.");
                }

                keybind_close_manual_end(siv, false);
            })
            .button(Labels::Close.localize(language), |siv| {
                keybind_close_manual_end(siv, false);
            })
            .min_size((20, 5)),
    );
}

pub fn error<S>(siv: &mut Cursive, text: S)
where
    S: Into<String>,
{
    let language = get_appdata(siv).persistent_data.language;
    alert(siv, Labels::Error.localize(language), text.into());
}

/// Sets up hotkey to close the most recent view
pub fn keybind_setup_close_once(siv: &mut Cursive) {
    siv.with_user_data(|appdata: &mut Appdata| {
        appdata.quick_close_window_count += 1;
    });

    siv.add_global_callback(event::Key::Esc, |siv| {
        keybind_close_manual_end(siv, false);
    });
}

// `close_all` can be removed, it isn't used.
/// Manually close the most recent view and removes the hotkey
pub fn keybind_close_manual_end(siv: &mut Cursive, close_all: bool) {
    siv.with_user_data(|appdata: &mut Appdata| {
        if close_all {
            appdata.quick_close_window_count = 0;
        } else {
            appdata.quick_close_window_count = appdata.quick_close_window_count.saturating_sub(1);
        }
    });

    if get_appdata(siv).quick_close_window_count == 0 {
        siv.clear_global_callbacks(event::Key::Esc);
    }

    siv.pop_layer();
}

pub fn visual_update(siv: &mut Cursive) {
    let appdata = get_appdata(siv);
    let current_channel = appdata.persistent_data.current_channel;
    let language = get_appdata(siv).persistent_data.language;

    // --- Messages ---

    let mut messages: Vec<MessageSanitized> = appdata
        .messages
        .clone()
        .values()
        .cloned()
        .map(MessageSanitized::from)
        .collect::<Vec<_>>();
    messages.sort_by(|a, b| b.time.cmp(&a.time));

    // Remove blocked phrases
    for message in messages.iter_mut() {
        message.remove_blocked_phrases(&appdata.persistent_data.blocked_phrases, appdata.persistent_data.blocked_phrases_censor_char);
    }

    siv.call_on_name(
        MESSAGE_PANEL_ID,
        |panel: &mut Panel<ScrollView<SelectView>>| {
            let view = panel.get_inner_mut().get_inner_mut();

            let selected = view.selected_id();

            view.clear();
            for message in messages
                .iter()
                .filter(|x| channel_matches(&x.channel, &current_channel))
            {
                view.add_item(
                    // TODO: Localized timestamps
                    format!(
                        "{:>6} | {}: {}\n",
                        utils::time::timestamp_relative(message.time),
                        message.sender,
                        message.content,
                    ),
                    message.hash().to_string(),
                );
            }

            if let Some(selected) = selected {
                view.set_selection(selected);
            }
        },
    );

    // --- Members list ---

    siv.call_on_name(
        USERS_PANEL_ID,
        |panel: &mut Panel<ScrollView<SelectView>>| {
            let view = panel.get_inner_mut().get_inner_mut();

            let mut senders: Vec<String> = vec![];
            for message in messages
                .iter()
                .filter(|x| channel_matches(&x.channel, &current_channel))
            {
                if senders.contains(&message.sender) {
                    continue;
                }
                senders.push(message.sender.clone());
            }
            senders.sort();

            let selected = view.selected_id();
            view.clear();
            for sender in senders {
                view.add_item(sender.clone(), sender.clone());
            }

            if let Some(selected) = selected {
                view.set_selection(selected);
            }
        },
    );

    // --- Channels ---

    siv.call_on_name(
        CHANNEL_VIEW_ID,
        |panel: &mut Panel<ScrollView<SelectView>>| {
            let view = panel.get_inner_mut().get_inner_mut();

            let mut channels = appdata.local_channels.clone();
            for message in messages.iter() {
                if !channels.contains(&message.channel) {
                    channels.push(message.channel.clone());
                }
            }
            channels.sort();

            let selected = view.selected_id();
            view.clear();

            for channel in channels {
                view.add_item(channel.clone(), channel.clone());
            }

            if let Some(selected) = selected {
                view.set_selection(selected);
            }
        },
    );

    // --- Current channel ---

    let appdata_c = get_appdata(siv);
    siv.call_on_name(
        CHANNEL_CURRENT_PANEL_ID,
        |view: &mut NamedView<Panel<TextView>>| {
            view.get_mut()
                .get_inner_mut()
                .set_content(appdata_c.persistent_data.current_channel);

            view.get_mut().set_title(Labels::Channel.localize(language));
        },
    );

    // --- Current username ---

    siv.call_on_name(
        CURRENT_USERNAME_PANEL_ID,
        |view: &mut NamedView<Panel<TextView>>| {
            view.get_mut().get_inner_mut().set_content(appdata.persistent_data.username);
        },
    );

    // --- Localize buttons ---

    for (name, label) in [
        (USERS_PANEL_ID, Labels::Users.localize(language)),
        (MESSAGE_PANEL_ID, Labels::Messages.localize(language)),
        (CHANNEL_VIEW_ID, Labels::Channels.localize(language)),
    ] {
        siv.call_on_name(name, |panel: &mut Panel<ScrollView<SelectView>>| {
            panel.set_title(label);
        });
    }

    siv.call_on_name(CHANNEL_NEW_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::NewChannel.localize(language));
    });

    siv.call_on_name(INPUT_PANEL_ID, |panel: &mut Panel<LinearLayout>| {
        panel.set_title(Labels::TypeAMessage.localize(language));
    });

    siv.call_on_name(INPUT_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut().set_label(Labels::Send.localize(language));
    });

    siv.call_on_name(INPUT_CLEAR_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut().set_label(Labels::Clear.localize(language));
    });

    siv.call_on_name(LANGUAGE_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::SetLanguage.localize(language));
    });

    siv.call_on_name(USERNAME_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::SetUsername.localize(language));
    });

    siv.call_on_name(QUIT_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::QuitButton.localize(language));
    });

    siv.call_on_name(REFRESH_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::RefreshButton.localize(language));
    });

    siv.call_on_name(BLOCKED_WORDS_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::BlockedWords.localize(language));
    });

    siv.call_on_name(INFO_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::Information.localize(language));
    });

    siv.call_on_name(
        CURRENT_USERNAME_PANEL_ID,
        |view: &mut NamedView<Panel<TextView>>| {
            view.get_mut()
                .set_title(Labels::Username.localize(language));
        },
    );

    siv.call_on_name(SERVER_SETTINGS_BUTTON_ID, |view: &mut NamedView<Button>| {
        view.get_mut()
            .set_label(Labels::ServerSettings.localize(language));
    });
}

pub fn change_language(siv: &mut Cursive, language: Language) {
    siv.with_user_data(|appdata: &mut Appdata| {
        appdata.persistent_data.language = language;
    })
    .expect("Failed to set language.");

    visual_update(siv);
}

pub fn channel_matches(message_channel: &str, reference_channel: &str) -> bool {
    message_channel == reference_channel
}

pub fn setup_ui(siv: &mut Cursive) -> LinearLayout {
    // Channels
    let channels_panel = {
        let mut view: SelectView<String> = SelectView::new();
        view.set_on_submit(move |siv, channel_id: &str| {
            siv.with_user_data(|appdata: &mut Appdata| {
                appdata.persistent_data.current_channel = channel_id.to_string();
            });

            visual_update(siv);
        });
        Panel::new(ScrollView::new(view)).with_name(CHANNEL_VIEW_ID)
    };

    let channel_current_panel = {
        let view = TextView::new("");
        Panel::new(view).with_name(CHANNEL_CURRENT_PANEL_ID)
    };

    let channels_new_button = Button::new("", move |siv| {
        keybind_setup_close_once(siv);

        let language = get_appdata(siv).persistent_data.language;

        siv.add_layer(
            cursive::views::Dialog::new()
                .title(Labels::NewChannel.localize(language))
                .content(
                    LinearLayout::vertical().child(
                        EditView::new()
                            .with_name(CHANNEL_NEW_FIELD_ID)
                            .fixed_width(DIALOGUE_MIN_SIZE.0.into()),
                    ),
                )
                .button(Labels::Ok.localize(language), move |siv| {
                    let input = siv
                        .call_on_name(CHANNEL_NEW_FIELD_ID, |view: &mut EditView| {
                            view.get_content()
                        })
                        .expect("Failed to retrieve channel creation input.");

                    if message::is_valid_channel_name(&input) {
                        // Valid
                        siv.with_user_data(|appdata: &mut Appdata| {
                            appdata.local_channels.push(input.to_string());
                            appdata.persistent_data.current_channel = input.to_string();
                        });

                        save_appdata(get_appdata(siv));
                        visual_update(siv);
                        keybind_close_manual_end(siv, false);
                    } else {
                        // Invalid
                        keybind_close_manual_end(siv, false); // NOTE: Error dialogue overwrites this one, so end it here beforehand.
                        error(
                            siv,
                            Labels::InvalidChannelNameExplination.localize(language),
                        );
                    }
                })
                .button(Labels::Cancel.localize(language), |siv| {
                    keybind_close_manual_end(siv, false);
                })
                .min_size(DIALOGUE_MIN_SIZE),
        );
    })
    .with_name(CHANNEL_NEW_BUTTON_ID);

    // Users
    let users_panel = {
        let mut users_view: SelectView<String> = SelectView::new();
        users_view.set_on_submit(move |siv, user: &String| actions::on_user_click(siv, user));
        Panel::new(ScrollView::new(users_view)).with_name(USERS_PANEL_ID)
    };

    // Messages
    let messages_panel = {
        let mut view: SelectView<String> = SelectView::new();
        view.set_on_submit(move |siv, message_id: &str| actions::on_message_click(siv, message_id));
        Panel::new(ScrollView::new(view)).with_name(MESSAGE_PANEL_ID)
    };

    // Input
    let input_panel = {
        let input_field = TextArea::new()
            .with_name(INPUT_FIELD_ID)
            .full_width()
            .max_height(10); // NOTE: 10 seems to be the size of 2 vertical buttons
        let input_button =
            Button::new("", move |siv| actions::on_input_submit(siv, INPUT_FIELD_ID))
                .with_name(INPUT_BUTTON_ID);

        let input_clear_button =
            Button::new("", actions::clear_input_field).with_name(INPUT_CLEAR_BUTTON_ID);

        Panel::new(
            LinearLayout::horizontal().child(input_field).child(
                LinearLayout::vertical()
                    .child(input_button)
                    .child(input_clear_button),
            ),
        )
        .with_name(INPUT_PANEL_ID)
    };

    // Language selector
    let language_button = Button::new("", move |siv| {
        // NOTE: This must match the order in the SelectView
        let current_language_index = match get_appdata(siv).persistent_data.language {
            Language::English => 0,
            Language::Dutch => 1,
            Language::Japanese => 2,
        };

        let language = get_appdata(siv).persistent_data.language;

        keybind_setup_close_once(siv);

        siv.add_layer(
            cursive::views::Dialog::new()
                .content(
                    LinearLayout::vertical().child(
                        SelectView::<Language>::new()
                            .item("English", Language::English)
                            .item("Nederlands", Language::Dutch)
                            .item("日本語", Language::Japanese)
                            .selected(current_language_index)
                            .on_submit(move |siv, language| {
                                change_language(siv, *language);
                                keybind_close_manual_end(siv, false);
                            }),
                    ),
                )
                .title(Labels::SetLanguage.localize(language))
                .button(Labels::Cancel.localize(language), |siv| {
                    keybind_close_manual_end(siv, false);
                })
                .min_size(DIALOGUE_MIN_SIZE),
        );
    })
    .with_name(LANGUAGE_BUTTON_ID);

    // Current username panel
    let current_username_panel = {
        let current_username = get_appdata(siv).persistent_data.username.clone();
        let view = TextView::new(current_username);
        Panel::new(view).with_name(CURRENT_USERNAME_PANEL_ID)
    };

    // Set username
    let username_button = Button::new("", move |siv| {
        let current_name = get_appdata(siv).persistent_data.username.clone();
        let language = get_appdata(siv).persistent_data.language;

        keybind_setup_close_once(siv);

        siv.add_layer(
            cursive::views::Dialog::new()
                .title(Labels::Username.localize(language))
                .content(
                    LinearLayout::vertical().child(
                        EditView::new()
                            .content(current_name)
                            .with_name(USERNAME_FIELD_ID)
                            .fixed_width(DIALOGUE_MIN_SIZE.0.into()),
                    ),
                )
                .button(Labels::Ok.localize(language), |siv| {
                    let input = siv
                        .call_on_name(USERNAME_FIELD_ID, |view: &mut EditView| view.get_content())
                        .expect("Failed to retrieve username input.");

                    if message::is_valid_username(&input) {
                        // Valid
                        siv.with_user_data(|appdata: &mut Appdata| {
                            appdata.persistent_data.username = input.to_string();
                        });

                        save_appdata(get_appdata(siv));
                        visual_update(siv);
                        keybind_close_manual_end(siv, false);
                    } else {
                        // Invalid
                        let language = get_appdata(siv).persistent_data.language;
                        keybind_close_manual_end(siv, false); // NOTE: Error dialogue overwrites this one, so end it here beforehand.
                        error(siv, Labels::InvalidUsernameExplination.localize(language));
                    }
                })
                .button(Labels::Cancel.localize(language), |siv| {
                    keybind_close_manual_end(siv, false);
                })
                .min_size(DIALOGUE_MIN_SIZE),
        );
    })
    .with_name(USERNAME_BUTTON_ID);

    // Quit button
    let quit_button = Button::new("", move |siv| {
        siv.quit();
    })
    .with_name(QUIT_BUTTON_ID);

    // Refresh button
    let refresh_button = Button::new("", move |siv| {
        if let Err(e) = actions::load_messages(siv) {
            let language = get_appdata(siv).persistent_data.language;
            error(siv, Labels::FailedToLoadMessages(e).localize(language));
        } else {
            alert(siv, "Refresh", "Refresh complete.");
        }
        visual_update(siv);
    })
    .with_name(REFRESH_BUTTON_ID);

    // Blocked words list
    let blocked_words_list_button = {
        Button::new("", move |siv| {
            let appdata = get_appdata(siv);
            let language = appdata.persistent_data.language;

            let wordslist_id = utils::new_id();
            let wordslist_id_clone = wordslist_id.clone();
            let wordslist_id_typingarea_clone = wordslist_id.clone();
            let mut wordslist_view =
                SelectView::<String>::new().on_submit(move |siv, word: &str| {
                    siv.with_user_data(|appdata: &mut Appdata| {
                        appdata.persistent_data.blocked_phrases.retain(|x| x != word);
                    });

                    siv.call_on_name(&wordslist_id_clone, |view: &mut NamedView<SelectView>| {
                        let idx = view
                            .get_mut()
                            .selected_id()
                            .expect("Selection should exist when fired from submit.");
                        view.get_mut().remove_item(idx);
                    });

                    save_appdata(get_appdata(siv));
                    visual_update(siv);
                });
            wordslist_view.add_all_str(&appdata.persistent_data.blocked_phrases);

            let typingarea_id = utils::new_id();
            let typing_area = EditView::new().with_name(&typingarea_id);
            let typing_button = Button::new(Labels::Submit.localize(language), move |siv| {
                let text = siv
                    .call_on_name(&typingarea_id, |view: &mut EditView| {
                        let s = utils::strings::insensitive_string(&view.get_content());
                        view.set_content("");
                        s
                    })
                    .map(|x| x.to_string())
                    .unwrap_or("".to_string());

                if get_appdata(siv).persistent_data.blocked_phrases.contains(&text) || text.is_empty() {
                    return;
                }

                siv.with_user_data(|appdata: &mut Appdata| {
                    appdata.persistent_data.blocked_phrases.push(text.clone());
                });

                siv.call_on_name(
                    &wordslist_id_typingarea_clone,
                    |view: &mut NamedView<SelectView>| {
                        view.get_mut().add_item_str(text);
                    },
                );

                save_appdata(get_appdata(siv));
                visual_update(siv);
            });

            let d = Dialog::new()
                .title(Labels::BlockedWords.localize(language))
                .button(Labels::Close.localize(language), |siv| {
                    keybind_close_manual_end(siv, false);
                })
                .content(
                    LinearLayout::vertical()
                        .child(ScrollView::new(wordslist_view.with_name(wordslist_id)))
                        .child(DummyView.fixed_height(1))
                        .child(
                            LinearLayout::horizontal()
                                .child(typing_area.min_width(DIALOGUE_MIN_SIZE.0 as usize))
                                .child(typing_button),
                        ),
                );

            keybind_setup_close_once(siv);
            siv.add_layer(d);
        })
        .with_name(BLOCKED_WORDS_BUTTON_ID)
    };

    // Server settings
    let server_settings_button = Button::new("", |siv| {
        let appdata = get_appdata(siv);
        let language = appdata.persistent_data.language;

        keybind_setup_close_once(siv);

        siv.add_layer(
            cursive::views::Dialog::new()
                .title(Labels::ServerSettings.localize(language))
                .content(
                    LinearLayout::vertical()
                        .child(
                            LinearLayout::horizontal()
                                .child(
                                    TextView::new(Labels::ServerAddress.localize(language))
                                        .fixed_width(SERVER_SETTINGS_FIELD_SIZE.0 / 2),
                                )
                                .child(DummyView.fixed_width(1))
                                .child(
                                    EditView::new()
                                        .content(appdata.persistent_data.api_endpoint)
                                        .with_name(SERVER_SETTINGS_ADDRESS_FIELD_ID)
                                        .min_width(SERVER_SETTINGS_FIELD_SIZE.0)
                                        .max_height(SERVER_SETTINGS_FIELD_SIZE.1),
                                ),
                        )
                        .child(
                            LinearLayout::horizontal()
                                .child(
                                    TextView::new(Labels::ServerRefreshRate.localize(language))
                                        .fixed_width(SERVER_SETTINGS_FIELD_SIZE.0 / 2),
                                )
                                .child(DummyView.fixed_width(1))
                                .child(
                                    EditView::new()
                                        .content(appdata.persistent_data.api_refresh_rate.to_string())
                                        .with_name(SERVER_SETTINGS_REFRESH_FIELD_ID)
                                        .min_width(SERVER_SETTINGS_FIELD_SIZE.0)
                                        .max_height(SERVER_SETTINGS_FIELD_SIZE.1),
                                ),
                        )
                        .child(
                            LinearLayout::horizontal()
                                .child(
                                    TextView::new(Labels::Password.localize(language))
                                        .fixed_width(SERVER_SETTINGS_FIELD_SIZE.0 / 2),
                                )
                                .child(DummyView.fixed_width(1))
                                .child(
                                    EditView::new()
                                        .content(appdata.persistent_data.api_password.to_string())
                                        .with_name(SERVER_SETTINGS_PASSWORD_FIELD_ID)
                                        .min_width(SERVER_SETTINGS_FIELD_SIZE.0)
                                        .max_height(SERVER_SETTINGS_FIELD_SIZE.1),
                                ),
                        ),
                )
                .button(Labels::Ok.localize(language), |siv| {
                    let input_addr = siv
                        .call_on_name(SERVER_SETTINGS_ADDRESS_FIELD_ID, |view: &mut EditView| {
                            view.get_content()
                        })
                        .expect("Failed to retrieve server settings address input.");

                    let input_refresh = siv
                        .call_on_name(SERVER_SETTINGS_REFRESH_FIELD_ID, |view: &mut EditView| {
                            view.get_content()
                        })
                        .expect("Failed to retrieve server settings refresh rate input.");

                    let input_password = siv
                        .call_on_name(SERVER_SETTINGS_PASSWORD_FIELD_ID, |view: &mut EditView| {
                            view.get_content()
                        })
                        .expect("Failed to retrieve server settings password input.");

                    siv.with_user_data(|appdata: &mut Appdata| {
                        appdata.persistent_data.api_endpoint = input_addr.to_string();

                        let mut password = input_password.to_string();
                        if password.is_empty() {
                            password = DEFAULT_PASSWORD.to_string();
                        }
                        appdata.persistent_data.api_password = password;

                        if let Ok(rate) = input_refresh.parse() {
                            appdata.persistent_data.api_refresh_rate = rate;
                        }
                    });

                    save_appdata(get_appdata(siv));
                    visual_update(siv);
                    keybind_close_manual_end(siv, false);
                })
                .button(Labels::Cancel.localize(language), |siv| {
                    keybind_close_manual_end(siv, false);
                })
                .min_size(DIALOGUE_MIN_SIZE),
        );
    })
    .with_name(SERVER_SETTINGS_BUTTON_ID);

    let info_button = Button::new("", move |siv| {
        let appdata = get_appdata(siv);
        let language = appdata.persistent_data.language;

        alert(siv, Labels::Information.localize(language), "Amy <rain@skuld.network>".to_string());
    })
    .with_name(INFO_BUTTON_ID);

    // Main layout
    let main_layout = LinearLayout::vertical()
        .child(
            LinearLayout::horizontal()
                .child(
                    LinearLayout::vertical()
                        .child(channels_panel.fixed_width(USER_PANEL_SIZE).full_height())
                        .child(channel_current_panel),
                )
                .child(
                    LinearLayout::vertical()
                        .child(users_panel.resized(
                            cursive::view::SizeConstraint::Fixed(USER_PANEL_SIZE),
                            cursive::view::SizeConstraint::Full,
                        ))
                        .child(current_username_panel),
                )
                .child(messages_panel.full_width()),
        )
        .child(input_panel)
        .child(
            LinearLayout::horizontal()
                .child(info_button)
                .child(DummyView.full_width())
                .child(language_button)
                .child(DummyView.full_width())
                .child(username_button)
                .child(DummyView.full_width())
                .child(channels_new_button)
                .child(DummyView.full_width())
                .child(blocked_words_list_button)
                .child(DummyView.full_width())
                .child(server_settings_button)
                .child(DummyView.full_width())
                .child(refresh_button)
                .child(DummyView.full_width())
                .child(quit_button)
                .full_width(),
        );

    main_layout
}
