#include "app.h"
#include <algorithm>
#include <string>
#include <regex>
#include <iomanip>
#include <rapidcsv.h>
#include <curl/curl.h>
#include <libxml2/libxml/parser.h>
#include <libxml2/libxml/tree.h>
#include <libxml2/libxml/xpath.h>
#include <Magick++.h>
#include <Wt/WMemoryResource.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WTemplate.h>
#include <Wt/WVBoxLayout.h>
#include <Wt/WHBoxLayout.h>
#include <Wt/WGridLayout.h>
#include <Wt/WCssDecorationStyle.h>
#include <Wt/WText.h>
#include <Wt/WPushButton.h>
#include <Wt/WLineEdit.h>
#include <Wt/WMessageBox.h>
#include <Wt/WComboBox.h>
#include <Wt/WEvent.h>
#include <Wt/WAny.h>
#include <Wt/WSpinBox.h>
#include <Wt/Json/Array.h>
#include <Wt/Json/Object.h>
#include <Wt/Json/Serializer.h>
#include <Wt/Json/Parser.h>
#include <Wt/WLink.h>
#include <Wt/WFileResource.h>
#include <Wt/WTable.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WDialog.h>
#include <Wt/WFileUpload.h>
#include <Wt/Utils.h>
#include <Wt/WSound.h>
#include <Wt/WSlider.h>
#include <Wt/WGridLayout.h>
#include <Wt/WCheckBox.h>
#include <Wt/WTimer.h>
#include <Wt/WResource.h>
#include <Wt/WLocale.h>

App::App(const Wt::WEnvironment &env, Broadcast &server):
        Wt::WApplication(env),
        env_(env),
        server_(server),
        updateLocationSignal_(this, "updateLocationSignal") {
    initLocale();
    initApp();
    updateLocation();
    enableUpdates(true);
    showStandardPage();
    messageReceived_ = std::make_unique<Wt::WSound>("newmessage.mp3");
    onInternalPathChanged(internalPath());
}

App::~App() {
    server_.disconnect(this);
}

void App::setMetaTags() {
    removeMetaHeader(Wt::MetaHeaderType::Meta, "robots");
    addMetaHeader("keywords", "chat,single chat,images,no registration,anonymous,direct chat,search,searchable,international,free,no cost,private chat,private");
    // Erlaube Indexierung und das Folgen von Links (u.a. für /partners)
    addMetaHeader(Wt::MetaHeaderType::Meta, "robots", "index,follow");
    addMetaHeader(Wt::MetaHeaderType::Meta, "google-adsense-account", "ca-pub-1104166651501135");
}

void App::initLocale() {
    auto locale = determineLocaleFromBrowser();
    setLocale(locale);
}

Wt::WLocale App::determineLocaleFromBrowser() const {
    auto makeLocale = [](const std::string &code) -> Wt::WLocale {
        return Wt::WLocale(code);
    };

    auto mapLanguageTag = [&makeLocale](std::string tag) -> std::string {
        // trim
        auto begin = tag.find_first_not_of(" \t");
        auto end = tag.find_last_not_of(" \t");
        if (begin == std::string::npos) {
            return "";
        }
        tag = tag.substr(begin, end - begin + 1);

        // nur Sprach-/Regionscode ohne q-Wert
        auto semicolonPos = tag.find(';');
        if (semicolonPos != std::string::npos) {
            tag = tag.substr(0, semicolonPos);
        }

        std::string lower = tag;
        std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);

        // Sprache und Region trennen (Unterstützung für '-' und '_')
        std::string lang = lower;
        std::string region;
        auto sepPos = lower.find_first_of("-_");
        if (sepPos != std::string::npos) {
            lang = lower.substr(0, sepPos);
            region = lower.substr(sepPos + 1);
        }

        if (lang == "en") {
            if (region == "us") {
                return "en_US";
            }
            // Standard: britisches Englisch
            return "en_GB";
        } else if (lang == "de") {
            return "de_DE";
        } else if (lang == "fr") {
            return "fr_FR";
        } else if (lang == "es") {
            return "es_ES";
        } else if (lang == "it") {
            return "it_IT";
        } else if (lang == "tl" || lang == "fil") {
            return "tl_PH";
        } else if (lang == "ja") {
            return "ja_JP";
        } else if (lang == "zh") {
            return "zh_CN";
        } else if (lang == "th") {
            return "th_TH";
        }

        return "";
    };

    // 1. Expliziter URL-Parameter ?lang=... hat Vorrang (z.B. de, de-DE, en, en-US, fr, es, it, tl, ja, zh, th)
    if (const auto *langParam = env_.getParameter("lang")) {
        auto localeName = mapLanguageTag(*langParam);
        if (!localeName.empty()) {
            return makeLocale(localeName);
        }
    }

    // 2. Accept-Language Header des Browsers auswerten
    std::string accept = env_.headerValue("Accept-Language");
    if (!accept.empty()) {
        std::stringstream ss(accept);
        std::string part;
        while (std::getline(ss, part, ',')) {
            auto localeName = mapLanguageTag(part);
            if (!localeName.empty()) {
                return makeLocale(localeName);
            }
        }
    }

    // 3. Sonst Locale verwenden, die Wt bereits aus dem Request ermittelt hat
    auto envLocaleName = env_.locale().name(); // z.B. "de_DE", "en_GB"
    auto mapped = mapLanguageTag(envLocaleName);
    if (!mapped.empty()) {
        return makeLocale(mapped);
    }

    // 4. Fallback: britisches Englisch
    return makeLocale("en_GB");
}

void App::initApp() {
    setMetaTags();
    setTitle("YP Direct Chat");
    setCssTheme("");
    useStyleSheet("style.css");
    // Basis-Bundle (englisch)
    messageResourceBundle().use("../docroot/text");

    // Sprachspezifische Bundles explizit nachladen, abhängig von der gesetzten Locale
    auto locName = locale().name(); // z.B. "de_DE", "en_GB", "en_US", "ja_JP"
    if (locName.rfind("de", 0) == 0) {
        messageResourceBundle().use("../docroot/text_de_DE");
    } else if (locName == "en_US") {
        messageResourceBundle().use("../docroot/text_en_US");
    } else if (locName.rfind("en", 0) == 0) {
        messageResourceBundle().use("../docroot/text_en_GB");
    } else if (locName.rfind("fr", 0) == 0) {
        messageResourceBundle().use("../docroot/text_fr_FR");
    } else if (locName.rfind("es", 0) == 0) {
        messageResourceBundle().use("../docroot/text_es_ES");
    } else if (locName.rfind("it", 0) == 0) {
        messageResourceBundle().use("../docroot/text_it_IT");
    } else if (locName.rfind("tl", 0) == 0 || locName.rfind("fil", 0) == 0) {
        messageResourceBundle().use("../docroot/text_tl_PH");
    } else if (locName.rfind("ja", 0) == 0) {
        messageResourceBundle().use("../docroot/text_ja_JP");
    } else if (locName.rfind("zh", 0) == 0) {
        messageResourceBundle().use("../docroot/text_zh_CN");
    } else if (locName.rfind("th", 0) == 0) {
        messageResourceBundle().use("../docroot/text_th_TH");
    }

    internalPathChanged().connect(this, &App::onInternalPathChanged);
}

void App::showStandardPage() {
    userName = server_.userNameForSessionId(sessionId());
    if (root()) {
        root()->clear();
        initApp();
    }
    auto verticalContainer = createVerticalLayout();
    createHeadContainer(verticalContainer);
    createMenuContainer(verticalContainer);
    auto horizontalContainer = createActionLayout(verticalContainer);
    createUserListContainer(horizontalContainer);
    createContentContainer(horizontalContainer);
    createImprintContainer(verticalContainer);
    reSetUser();
    if (userName == "") {
        showLogin();
    } else {
        startChat();
    }
}

void App::reSetUser() {
    if (env_.cookies().contains("wtd")) {
        auto userData = server_.reSetUser(env_.cookies().find("wtd")->second, sessionId());
        Wt::Json::Object emptyObject = {};
        if (userData != emptyObject) {
            userName = (std::string)userData["username"];
            gender = (std::string)userData["gender"];
            country = (std::string)userData["country"];
            isoCountryCode = (std::string)userData["iso-country-code"];
            age = (int)userData["age"];
        }
    }
}

Wt::WVBoxLayout *App::createVerticalLayout() {
    auto verticalBox = root()->addNew<Wt::WContainerWidget>();
    verticalBox->setHeight(Wt::WLength(100, Wt::LengthUnit::Percentage));
    verticalBox->setWidth(Wt::WLength(100, Wt::LengthUnit::Percentage));
    auto verticalContainer = verticalBox->setLayout(std::make_unique<Wt::WVBoxLayout>());
    verticalContainer->setContentsMargins(0, 0, 0, 0);
    verticalContainer->setSpacing(0);
    return verticalContainer;
}

Wt::WHBoxLayout *App::createActionLayout(Wt::WVBoxLayout *verticalContainer) {
    auto horizontalBox = verticalContainer->addWidget(std::make_unique<Wt::WContainerWidget>());
    horizontalBox->setStyleClass("horizontal-box");
    auto horizontalContainer = horizontalBox->setLayout(std::make_unique<Wt::WHBoxLayout>());
    horizontalContainer->setContentsMargins(0, 0, 0, 0);
    horizontalContainer->setSpacing(0);
    return horizontalContainer;
}

void App::createHeadContainer(Wt::WVBoxLayout *layout) {
    auto header = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    header->addNew<Wt::WText>("<h1>ypChat</h1>")->setInline(true);
    header->setStyleClass("header");
}

void App::createMenuContainer(Wt::WVBoxLayout *layout) {
    menuContainer_ = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    menuContainer_->setStyleClass("menu");
}

void App::showLogin() {
    contentContainer_->clear();
    createLoginContainer();
}

void App::createLoginContainer() {
    auto loginContainer = contentContainer_->addNew<Wt::WContainerWidget>();
    loginContainer->resize(Wt::WLength(40, Wt::LengthUnit::FontEm), Wt::WLength::Auto);
    auto contentGrid = loginContainer->setLayout(std::make_unique<Wt::WGridLayout>());
    auto userName = addUsernameInput(contentGrid);
    auto gender = addGenderSelection(contentGrid);
    auto age = addAgeInput(contentGrid);
    auto countrySelection = addCountrySelection(contentGrid);
    addStartChatButton(contentGrid, userName, countrySelection, age, gender);
    contentContainer_->addNew<Wt::WText>(Wt::WString::tr("welcome"));
}

Wt::WLineEdit *App::addUsernameInput(Wt::WGridLayout* contentGrid) {
    contentGrid->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("label_nick")), 0, 0);
    auto userName = contentGrid->addWidget(std::make_unique<Wt::WLineEdit>(), 0, 1);
    userName->setTextSize(20);
    userName->setMaxLength(30);
    return userName;
}

Wt::WComboBox *App::addGenderSelection(Wt::WGridLayout* contentGrid) {
    contentGrid->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("label_gender")), 1, 0);
    auto genderWidget = contentGrid->addWidget(std::make_unique<Wt::WComboBox>(), 1, 1);
    populateGenderComboBox(genderWidget);
    return genderWidget;
}

Wt::WSpinBox *App::addAgeInput(Wt::WGridLayout* contentGrid) {
    contentGrid->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("label_age")), 2, 0);
    auto ageWidget = contentGrid->addWidget(std::make_unique<Wt::WSpinBox>(), 2, 1);
    ageWidget->setRange(18, 150);
    ageWidget->setValue(18);
    return ageWidget;
}

Wt::WComboBox *App::addCountrySelection(Wt::WGridLayout* contentGrid) {
    contentGrid->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("label_country")), 3, 0);
    auto countryWidget = contentGrid->addWidget(std::make_unique<Wt::WComboBox>(), 3, 1);
    populateCountryComboBox(countryWidget);
    return countryWidget;
}

void App::addStartChatButton(Wt::WGridLayout* contentGrid, Wt::WLineEdit *userName, Wt::WComboBox *country, Wt::WSpinBox *age, Wt::WComboBox *gender) {
    auto doLogin = contentGrid->addWidget(std::make_unique<Wt::WPushButton>(Wt::WString::tr("button_start_chat")), 4, 1);
    doLogin->clicked().connect([=, this]() {
        handleLogin(userName, country, age, gender);
    });
}

void App::populateCountryComboBox(Wt::WComboBox* countryWidget) {
    auto countries = server_.countries();
    int countryIndex{-1};
    auto isoCode = isoCountryCode;
    std::transform(isoCode.begin(), isoCode.end(), isoCode.begin(), ::tolower);
    for (const auto &countryItem: countries) {
        countryWidget->addItem(countryItem.first);
        std::string countryCode = Wt::asString(countryItem.second).toUTF8();
        if (countryCode == isoCode) {
            countryIndex = countryWidget->count();
            break;
        }
    }
    countryWidget->setCurrentIndex(countryIndex);
}

void App::handleLogin(Wt::WLineEdit* userName, Wt::WComboBox* countryWidget, Wt::WSpinBox *ageWidget, Wt::WComboBox *genderWidget) {
    try {
        std::string nick = extractTrimmedUserName(userName);
        std::cout << "handleLogin() nick='" << nick << "'" << std::endl;
        validateName(nick);
        validateGender(genderWidget);
        validateAge(ageWidget);
        setUserData(nick, countryWidget, ageWidget, genderWidget);
        connectToServer();
        startChat();
        setCookie("wtd", sessionId(), 21000);
    } catch (const std::exception& e) {
        std::cerr << "handleLogin() exception: " << e.what() << std::endl;
        Wt::WMessageBox::show("Attention", e.what(), Wt::StandardButton::Ok);
    }
}

std::string App::extractTrimmedUserName(Wt::WLineEdit* userName) {
    return userName->text().trim().toUTF8();
}

void App::validateName(const std::string& nick) {
    if (isInvalidName(nick) || isNameAlreadyInUse(nick) || !isNickAllowed(nick)) {
        throw std::runtime_error("The name you wish is already in use or not allowed. Please use another one.");
    }
}

bool App::isInvalidName(const std::string& nick) {
    return nick.empty();
}

bool App::isNameAlreadyInUse(const std::string& nick) {
    return !server_.nameIsFree(nick);
}

void App::validateGender(Wt::WComboBox* genderWidget) {
    if (!isGenderSelected(genderWidget)) {
        throw std::runtime_error("Please select a gender.");
    }
}

bool App::isGenderSelected(Wt::WComboBox* genderWidget) {
    return genderWidget->currentIndex() >= 0;
}

void App::validateAge(Wt::WSpinBox *ageWidget) {
    int ageValue = ageWidget->value();
    if (ageValue < 18 || ageValue > 150) {
        throw std::runtime_error("This age isn't allowed.");
    }
}

void App::setUserData(const std::string& nick, Wt::WComboBox* countryWidget, Wt::WSpinBox *ageWidget, Wt::WComboBox *genderWidget) {
    this->userName = nick;
    country = countryWidget->currentText().toUTF8();
    isoCountryCode = server_.getCountryIsoCodeByCountry(country).toUTF8();
    age = ageWidget->value();
    gender = getGenderShortByGender(genderWidget->currentText().toUTF8());
}

void App::connectToServer() {
    server_.connect(this, std::bind(&App::incomingBroadcast, this));
}

void App::populateGenderComboBox(Wt::WComboBox *genderWidget) {
    int markIndex{-1};
    for (const auto &genderItem: genders_) {
        auto label = Wt::WString::tr(genderItem.second.toUTF8());
        genderWidget->addItem(label);
        if (genderItem.first == "F") {
            markIndex = genderWidget->count();
        }
    }
    genderWidget->setCurrentIndex(markIndex);
}

std::string App::getGenderShortByGender(std::string gender) {
    for (const auto &genderItem: genders_) {
        auto label = Wt::WString::tr(genderItem.second.toUTF8()).toUTF8();
        if (gender == label) {
            return genderItem.first.toUTF8();
        }
    }
    return "";
}

void App::updateUserlist(Wt::Json::Array unsortedUserList, int size) {
    userListContainer_->clear();
    userListContainer_->resize(Wt::WLength(15, Wt::LengthUnit::FontEm), Wt::WLength::Auto);
    auto layout = userListContainer_->setLayout(std::make_unique<Wt::WVBoxLayout>());
    layout->setSpacing(1);
    layout->setContentsMargins(0, 0, 0, 0);
    userListContainer_->setOverflow(Wt::Overflow::Auto, Wt::Orientation::Vertical);
    userListContainer_->setOverflow(Wt::Overflow::Hidden, Wt::Orientation::Horizontal);
    layout->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("logged_in_count").arg(size)));
    auto sortedUserList = sortUserList(unsortedUserList);
    for (Wt::Json::Object &user: sortedUserList) {
        addUserItemToLayout(layout, user);
    }
    layout->addWidget(std::make_unique<Wt::WText>(), 1)->setStyleClass("height-spacer userlist");
    triggerUpdate();
}

std::vector<Wt::Json::Object> App::sortUserList(Wt::Json::Array unsortedUserList) {
    std::vector<Wt::Json::Object> sortedUserList;
    for (const Wt::Json::Object &item : unsortedUserList) {
        sortedUserList.push_back(item);
    }
    std::sort(sortedUserList.begin(), sortedUserList.end(), [this](const Wt::Json::Object& obj1, const Wt::Json::Object& obj2) {
        auto compareResult = compareJsonObjects(obj1, obj2);
        return compareResult;
    });
    return sortedUserList;
}

bool App::compareJsonObjects(const Wt::Json::Object& obj1, const Wt::Json::Object& obj2) {
    auto standardCountry = country;
    auto country1 = (std::string)obj1.get("country");
    auto country2 = (std::string)obj2.get("country");
    auto name1 = (std::string)obj1.get("name");
    auto name2 = (std::string)obj2.get("name");
    if (country1 == standardCountry && country2 == standardCountry) {
        return name1 < name2;
    }
    if (country1 == standardCountry) {
        return true;
    } else if (country2 == standardCountry) {
        return false;
    }
    if (country1 != country2) {
        return country1 < country2;
    }
    return name1 < name2;
}

void App::requestConversation(std::string conversationWith) {
    if (conversationWith == userName) {
        return;
    }
    currentConversationWith_ = conversationWith;
    server_.requestConversation(sessionId(), conversationWith, userName);
}

void App::showConversation(Wt::Json::Object data) {
    try {
        Wt::Json::Object userData = extractUserData(data);
        if (!shouldShowConversation(userData)) {
            return;
        }
        setupConversationUI(userData);
    } catch (const std::exception &e) {
        std::cout << __LINE__ << e.what() << std::endl;
    }
    triggerUpdate();
}

Wt::Json::Object App::extractUserData(Wt::Json::Object data) {
    return (Wt::Json::Object)data["data"];
}

bool App::shouldShowConversation(Wt::Json::Object userData) {
    return currentConversationWith_ == (std::string)userData["name"];
}

void App::setupConversationUI(Wt::Json::Object userData) {
    inboxOpen_ = false;
    searchFields.outputContainer = nullptr;
    contentContainer_->clear();
    auto layout = contentContainer_->setLayout(std::make_unique<Wt::WVBoxLayout>());
    createInfoWidget(layout, userData);
    layout->addWidget(std::make_unique<Wt::WContainerWidget>(), 1)->setOverflow(Wt::Overflow::Auto);
    createInputContainer(layout);
}

Wt::WContainerWidget* App::createInfoWidget(Wt::WVBoxLayout *layout, Wt::Json::Object userData) {
    auto infoWidget = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    try {
        auto infoLayout = infoWidget->setLayout(std::make_unique<Wt::WHBoxLayout>());
        infoWidget->setStyleClass(Wt::WString("user-conversation-info userlist-gender-{1}").arg((std::string)userData["gender"]));
        auto flag = infoLayout->addWidget(std::make_unique<Wt::WImage>(
            Wt::WLink(std::make_shared<Wt::WFileResource>("../docroot/flags/" + (std::string)userData["isoCountryCode"] + ".png"))));
        flag->setToolTip(country);
        flag->setStyleClass("flag-icon");
        infoLayout->addWidget(createInfoText(userData), 1);
        auto blockButton = createBlockButton(userData);
        infoLayout->addWidget(std::move(blockButton));
    } catch(const std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    return infoWidget;
}

std::unique_ptr<Wt::WText> App::createInfoText(Wt::Json::Object userData) {
    return std::make_unique<Wt::WText>(Wt::WString("{1} ({2}) - {3}, {4}").arg((std::string)userData["name"])
        .arg((int)userData["age"]).arg((std::string)userData["country"]).arg(genders_[(std::string)userData["gender"]]));
}

std::unique_ptr<Wt::WPushButton> App::createBlockButton(Wt::Json::Object userData) {
    auto blockButton = std::make_unique<Wt::WPushButton>(
        (bool)userData["blocked"]
            ? Wt::WString::tr("button_unblock_user")
            : Wt::WString::tr("button_block_user"));
    blockButton->clicked().connect([=, this]() mutable {
        server_.toggleBlockUser(userName, (std::string)userData["name"], sessionId());
    });
    return blockButton;
}

Wt::WContainerWidget* App::createInputContainer(Wt::WVBoxLayout* layout) {
    auto inputContainer = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    auto inputLayout = inputContainer->setLayout(std::make_unique<Wt::WHBoxLayout>());
    auto inputLine = createInputLine(inputLayout);
    inputLine->setFocus();
    createSendImageButton(inputLayout);
    messageCursorPosition_ = std::make_shared<int>(0);
    createSmileyButton(inputLayout, inputLine, messageCursorPosition_);
    createSmileyBar(inputContainer, inputLine, messageCursorPosition_);
    createSendButton(inputLayout, inputLine);
    return inputContainer;
}

Wt::WLineEdit* App::createInputLine(Wt::WHBoxLayout* inputLayout) {
    auto inputLine = inputLayout->addWidget(std::make_unique<Wt::WLineEdit>(), 1);
    inputLine->setMaxLength(250);
    auto cursorPosition = std::make_shared<int>(0);
    auto updateCursorPosition = [=]() mutable {
        *cursorPosition = inputLine->cursorPosition();
    };
    inputLine->keyPressed().connect(updateCursorPosition);
    inputLine->clicked().connect(updateCursorPosition);
    return inputLine;
}

Wt::WImage* App::createSendImageButton(Wt::WHBoxLayout* inputLayout) {
    auto sendImageButton = inputLayout->addWidget(std::make_unique<Wt::WImage>(Wt::WLink("/image.png")));
    sendImageButton->setToolTip(Wt::WString::tr("tooltip_send_image"));
    sendImageButton->clicked().connect(this, &App::sendImage);
    return sendImageButton;
}

void App::sendImage() {
    auto fileDialog = root()->addNew<Wt::WDialog>(Wt::WString::tr("dialog_send_image_title"));
    auto layout = fileDialog->contents()->setLayout(std::make_unique<Wt::WVBoxLayout>());
    layout->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("dialog_send_image_text")));
    auto fileWidget = layout->addWidget(std::make_unique<Wt::WFileUpload>());
    fileWidget->setFilters("image/*");
    auto image = layout->addWidget(std::make_unique<Wt::WImage>());
    image->setMaximumSize(Wt::WLength(100, Wt::LengthUnit::Pixel), Wt::WLength(100, Wt::LengthUnit::Pixel));
    image->setHeight(Wt::WLength::Auto);
    auto localImage = std::make_shared<Magick::Blob>();
    auto buttonsContainer = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    auto okButton = buttonsContainer->addNew<Wt::WPushButton>(Wt::WString::tr("dialog_send_image_ok"));
    fileWidget->uploaded().connect([=, this]() mutable {
        imageUploaded(fileWidget, localImage, image, okButton);
    });
    fileWidget->changed().connect([=]() { fileWidget->upload(); });
    fileWidget->fileTooLarge().connect([](){
        std::cout << "file too big" << std::endl;
    });
    okButton->setDisabled(true);
    okButton->clicked().connect([=, this]() {
        server_.addImage(sessionId(), currentConversationWith_, localImage);
        fileDialog->accept();
    });
    auto cancelButton = buttonsContainer->addNew<Wt::WPushButton>(Wt::WString::tr("dialog_send_image_cancel"));
    cancelButton->clicked().connect([=](){ fileDialog->reject(); });
    fileDialog->setClosable(true);
    fileDialog->setModal(true);
    fileDialog->show();
}

void App::imageUploaded(Wt::WFileUpload *fileWidget, std::shared_ptr<Magick::Blob> localImage, Wt::WImage *image, Wt::WPushButton *okButton) {
    try {
        const std::string uploadedFile = fileWidget->spoolFileName();
        std::list<Magick::Image> originalImages;
        Magick::readImages(&originalImages, uploadedFile);
        std::list<Magick::Image> resizedForLocalImage;
        std::list<Magick::Image> resizedForDisplayImage;
        resizedForLocalImage = resizeImages(originalImages, 500, 500);
        resizedForDisplayImage = resizeImages(originalImages, 100, 100);
        std::string imageType = originalImages.front().magick();
        std::string mimeType = "image/" + imageType;
        Magick::writeImages(resizedForLocalImage.begin(), resizedForLocalImage.end(), localImage.get(), true);
        Magick::Blob displayBlob;
        Magick::writeImages(resizedForDisplayImage.begin(), resizedForDisplayImage.end(), &displayBlob, true);
        auto memoryResource = std::make_shared<Wt::WMemoryResource>(mimeType);
        memoryResource->setData(static_cast<const unsigned char*>(displayBlob.data()), displayBlob.length());
        image->setImageLink(Wt::WLink(memoryResource));
        image->setAlternateText(Wt::WString::tr("image_uploaded_processed"));
        okButton->setEnabled(true);
        triggerUpdate();
    } catch (const std::exception& e) {
        std::cerr << "Error processing uploaded image: " << e.what() << std::endl;
    }
}

std::list<Magick::Image> App::resizeImages(std::list<Magick::Image> &images, int maxWidth, int maxHeight) {
    std::list<Magick::Image> resizedImages;
    for (auto& img : images) {
        double originalWidth = img.columns();
        double originalHeight = img.rows();
        double widthRatio = static_cast<double>(maxWidth) / originalWidth;
        double heightRatio = static_cast<double>(maxHeight) / originalHeight;
        double resizeRatio = std::min(widthRatio, heightRatio); // Nehme das kleinere Verhältnis, um innerhalb der Grenzen zu bleiben
        int newWidth = static_cast<int>(originalWidth * resizeRatio);
        int newHeight = static_cast<int>(originalHeight * resizeRatio);
        if (resizeRatio < 1) {
            img.resize(Magick::Geometry(newWidth, newHeight));
        }
        resizedImages.push_back(img);
    }
    return resizedImages;
}

void App::onInternalPathChanged(const std::string &path) {
    std::cout << __LINE__ << "'" << path << "'" << std::endl << std::endl;
    if (path.empty() || path == "/") {
        showStandardPage();
    } else if (path == "/partners") {
        showPartnerSites();
    } else if (path == "/adm/info/logins" || path == "/adm/info/starts") {
        showAdminPage(path);
    } else {
        setInternalPath("/", true);
    }
}

void App::showAdminPage(std::string page) {
    if (isLoggedInAsAdmin) {
        if (page == "/adm/info/logins") {
            showAdminLogins();
        } else if (page == "/adm/info/starts") {
            showAdminStarts();
        } else {
            showPageNotExists();
        }
        return;
    }
    showAdminLogin(page);
}

void App::showAdminLogin(std::string page) {
    contentContainer_->clear();
    auto loginContainer = contentContainer_->addNew<Wt::WContainerWidget>();
    loginContainer->addNew<Wt::WText>(Wt::WString::tr("admin_name"));
    auto nameEdit = loginContainer->addNew<Wt::WLineEdit>();
    loginContainer->addNew<Wt::WBreak>();
    loginContainer->addNew<Wt::WText>(Wt::WString::tr("admin_password"));
    auto passwordEdit = loginContainer->addNew<Wt::WLineEdit>();
    passwordEdit->setEchoMode(Wt::EchoMode::Password);
    loginContainer->addNew<Wt::WBreak>();
    auto loginButton = loginContainer->addNew<Wt::WPushButton>(Wt::WString::tr("admin_login_button"));
    auto messageLabel = loginContainer->addNew<Wt::WText>();
    auto loginAction = [=, this] {
        if (nameEdit->text().toUTF8() == adminName && passwordEdit->text().toUTF8() == adminPassword) {
            isLoggedInAsAdmin = true;
            messageLabel->setText("");
            showAdminPage(page);
        } else {
            isLoggedInAsAdmin = false;
            messageLabel->setText(Wt::WString::tr("admin_login_error"));
            messageLabel->decorationStyle().setForegroundColor(Wt::WColor("red"));
        }
    };
    loginButton->clicked().connect(loginAction);
    passwordEdit->enterPressed().connect(loginAction);
}

void App::showPageNotExists() {
    contentContainer_->clear();
    auto errorMessageLabel = contentContainer_->addNew<Wt::WText>(Wt::WString::tr("error_page_not_found"));
    errorMessageLabel->decorationStyle().setForegroundColor(Wt::WColor("red"));
}

void App::showAdminLogins() {
    contentContainer_->clear();
    contentContainer_->addNew<Wt::WText>("<h2>Logins</h2>", Wt::TextFormat::UnsafeXHTML);
    std::ifstream file("../logs/logins.log");
    if (!file.is_open()) {
        contentContainer_->addNew<Wt::WText>(Wt::WString::tr("error_opening_file"));
        return;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string fileContent = buffer.str();
    Wt::Json::Array jsonArray;
    Wt::Json::parse(fileContent, jsonArray);
    auto table = contentContainer_->addNew<Wt::WTable>();
    table->setHeaderCount(1);
    table->elementAt(0, 0)->addNew<Wt::WText>(Wt::WString::tr("label_name"));
    table->elementAt(0, 1)->addNew<Wt::WText>(Wt::WString::tr("label_country"));
    table->elementAt(0, 2)->addNew<Wt::WText>(Wt::WString::tr("label_gender"));
    table->elementAt(0, 3)->addNew<Wt::WText>(Wt::WString::tr("label_age"));
    table->elementAt(0, 4)->addNew<Wt::WText>("Datum");
    table->elementAt(0, 5)->addNew<Wt::WText>("Uhrzeit");
    int row = 1;
    for (const auto& item : jsonArray) {
        Wt::Json::Object jsonData = item;
        std::string name = jsonData.get("name").orIfNull("");
        std::string country = jsonData.get("country").orIfNull("");
        std::string gender = jsonData.get("gender").orIfNull("");
        int age = jsonData.get("age").orIfNull(0);
        std::string timestamp = jsonData.get("timestamp").orIfNull("");
        auto nameCell = table->elementAt(row, 0)->addNew<Wt::WText>(name);
        nameCell->setStyleClass("padding-right");
        auto countryCell = table->elementAt(row, 1)->addNew<Wt::WText>(country);
        countryCell->setStyleClass("padding-right");
        auto genderCell = table->elementAt(row, 2)->addNew<Wt::WText>(gender);
        genderCell->setStyleClass("padding-right");
        auto ageCell = table->elementAt(row, 3)->addNew<Wt::WText>(std::to_string(age));
        ageCell->setStyleClass("padding-right");

        // Zeitstempel in deutsches Format umwandeln (falls vorhanden und gültig)
        if (!timestamp.empty()) {
            std::istringstream ss(timestamp);
            std::tm tm{};
            ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

            if (!ss.fail()) {
                std::ostringstream dateStream;
                std::ostringstream timeStream;
                dateStream.imbue(std::locale("de_DE.utf8"));
                timeStream.imbue(std::locale("de_DE.utf8"));
                dateStream << std::put_time(&tm, "%d.%m.%Y");
                timeStream << std::put_time(&tm, "%H:%M:%S");

                auto dateCell = table->elementAt(row, 4)->addNew<Wt::WText>(dateStream.str());
                dateCell->setStyleClass("padding-right");
                table->elementAt(row, 5)->addNew<Wt::WText>(timeStream.str());
            } else {
                // Fallback bei ungültigem Zeitstempel
                auto dateCell = table->elementAt(row, 4)->addNew<Wt::WText>("-");
                dateCell->setStyleClass("padding-right");
                table->elementAt(row, 5)->addNew<Wt::WText>("-");
            }
        } else {
            // Fallback für alte Einträge ohne Zeitstempel
            auto dateCell = table->elementAt(row, 4)->addNew<Wt::WText>("-");
            dateCell->setStyleClass("padding-right");
            table->elementAt(row, 5)->addNew<Wt::WText>("-");
        }
        row++;
    }
}


void App::showAdminStarts() {
    contentContainer_->clear();
    contentContainer_->addNew<Wt::WText>("<h2>Chat starts</h2>", Wt::TextFormat::UnsafeXHTML);
    std::ifstream file("../logs/starts.log");
    if (!file.is_open()) {
        contentContainer_->addNew<Wt::WText>("Error opening file.");
        return;
    }
    auto table = contentContainer_->addNew<Wt::WTable>();
    table->setHeaderCount(1);
    table->elementAt(0, 0)->addNew<Wt::WText>("Datum");
    table->elementAt(0, 1)->addNew<Wt::WText>("Uhrzeit");
    std::string line;
    int row = 1;
    while (std::getline(file, line)) {
        std::istringstream ss(line);
        std::tm tm{};
        ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
        std::ostringstream dateStream;
        std::ostringstream timeStream;
        dateStream.imbue(std::locale("de_DE.utf8"));
        timeStream.imbue(std::locale("de_DE.utf8"));
        dateStream << std::put_time(&tm, "%d.%m.%Y");
        timeStream << std::put_time(&tm, "%H:%M:%S");
        auto dateCell = table->elementAt(row, 0)->addNew<Wt::WText>(dateStream.str());
        dateCell->setStyleClass("padding-right");
        table->elementAt(row, 1)->addNew<Wt::WText>(timeStream.str());
        row++;
    }
}

Wt::WContainerWidget* App::createSmileyButton(Wt::WHBoxLayout* inputLayout, Wt::WLineEdit* inputLine, std::shared_ptr<int> cursorPosition) {
    auto smileyButton = inputLayout->addWidget(std::make_unique<Wt::WContainerWidget>());
    smileyButton->addNew<Wt::WImage>(Wt::WLink("/smileys.png"));
    smileyButton->setStyleClass("no-style");
    smileyButton->setToolTip("Add a smiley");
    smileyButton->setInline(true);
    auto smileyBar = createSmileyBar(smileyButton, inputLine, cursorPosition);
    smileyBar->setHidden(true);
    smileyButton->clicked().connect([=]() {
        smileyBar->setHidden(!smileyBar->isHidden());
    });
    return smileyButton;
}

Wt::WContainerWidget* App::createSmileyBar(Wt::WContainerWidget* parent, Wt::WLineEdit* inputLine, std::shared_ptr<int> cursorPosition) {
    auto smileyBar = parent->addWidget(createSmileysBar(inputLine, cursorPosition));
    smileyBar->setHidden(true);
    return smileyBar;
}

Wt::WPushButton* App::createSendButton(Wt::WHBoxLayout* inputLayout, Wt::WLineEdit* inputLine) {
    auto sendButton = inputLayout->addWidget(std::make_unique<Wt::WPushButton>(Wt::WString::tr("button_send")));
    auto sendMessageFunction = [=, this]() {
        sendMessage(inputLine);
    };
    inputLine->enterPressed().connect(sendMessageFunction);
    sendButton->clicked().connect(sendMessageFunction);
    inputLine->setFocus();
    return sendButton;
}

void App::sendMessage(Wt::WLineEdit *inputLine) {
    setActivity();
    auto utf8String = inputLine->valueText().trim().toUTF8();
    if (utf8String == "") {
        return;
    }
    size_t pos = 0;
    while ((pos = utf8String.find("<", pos)) != std::string::npos) {
        utf8String.replace(pos, 1, "&lt;");
        pos += 4;
    }
    inputLine->setValueText("");
    auto sendString = Wt::WString(utf8String.substr(0, 250));
    *messageCursorPosition_ = 0;
    server_.addMessage(sessionId(), currentConversationWith_, Broadcast::Message(sessionId(), sendString));
}

void App::renderConversation(Wt::Json::Object conversation) {
    if (currentConversationWith_ != (std::string)conversation["user1"] && currentConversationWith_ != (std::string)conversation["user2"]) {
        return;
    }
    updateOutputContainer(conversation);
    server_.setConversationRead(sessionId(), currentConversationWith_);
    triggerUpdate();
}

void App::updateOutputContainer(Wt::Json::Object conversation) {
    auto containerLayout = (Wt::WVBoxLayout*)contentContainer_->layout();
    auto outputLayoutItem = containerLayout->itemAt(1);
    auto outputContainer = (Wt::WContainerWidget*)outputLayoutItem->widget();
    outputContainer->setOverflow(Wt::Overflow::Auto);
    renderChatLines(conversation, outputContainer);
}

void App::renderChatLines(Wt::Json::Object conversation, Wt::WContainerWidget* outputContainer) {
    Wt::Json::Array chat = conversation["data"];
    for (Wt::Json::Object &line : chat) {
        try {
            renderChatLine(line, conversation, outputContainer);
        } catch (const std::exception &e) {
            std::cout << e.what() << std::endl;
        }
    }
    doJavaScript(outputContainer->jsRef() + ".scrollTop += "
                 + outputContainer->jsRef() + ".scrollHeight;");
}

void App::renderChatLine(Wt::Json::Object &line, Wt::Json::Object conversation, Wt::WContainerWidget* outputContainer) {
    std::string writer = getChatLineWriter(line, conversation);
    Wt::WWebWidget* item;
    auto id = (std::string)line["id"];
    for (const auto widget: outputContainer->children()) {
        if (widget->attributeValue("dummy") == id) {
            return;
        }
    }
    if ((std::string)line["type"] == "text") {
        item = createTextElement(writer, (std::string)line["string"], outputContainer, id);
    } else if ((std::string)line["type"] == "image") {
        item = createImageElement(line, writer, outputContainer, id);
    } else {
        item = new Wt::WText("");
    }
    item->setToolTip(line["timestamp"]);
    item->setStyleClass("output-box-format");
    item->addStyleClass(writer == "you" ? "ouput-box-format-self" : "output-box-format-other");
    item->setInline(false);
}

Wt::WWebWidget* App::createTextElement(const std::string& writer, const std::string& text, Wt::WContainerWidget* outputContainer, std::string id) {
    std::string outputText = replaceSmileys(text);
    Wt::WString output = Wt::WString("<b>{1}</b>: {2}").arg(writer).arg(outputText);
    auto line = outputContainer->addNew<Wt::WText>(output);
    line->setAttributeValue("dummy", id);
    return line;
}

void App::createImprintContainer(Wt::WVBoxLayout *containerLayout) {
    auto imprintContainer = containerLayout->addWidget(std::make_unique<Wt::WContainerWidget>());
    imprintContainer->setContentAlignment(Wt::AlignmentFlag::Justify);
    imprintContainer->setPadding(Wt::WLength(0.5, Wt::LengthUnit::FontEm));
    auto partnerPagesButton = imprintContainer->addNew<Wt::WText>("Partners");
    partnerPagesButton->clicked().connect(this, &App::showPartnerSites);
    partnerPagesButton->setFloatSide(Wt::Side::Left);
    auto imprintButton = imprintContainer->addNew<Wt::WText>("Imprint");
    imprintButton->setFloatSide(Wt::Side::Right);
    Wt::WCssDecorationStyle imprintButtonDecorationStyle;
    imprintButtonDecorationStyle.setForegroundColor(Wt::StandardColor::White);
    imprintButtonDecorationStyle.setBackgroundColor(Wt::WColor(0x42, 0x90, 0x43));
    imprintButtonDecorationStyle.setCursor(Wt::Cursor::PointingHand);
    imprintContainer->setDecorationStyle(imprintButtonDecorationStyle);
    imprintButton->clicked().connect([=, this]() {
        auto imprintDialog = root()->addNew<Wt::WMessageBox>("Imprint", "", Wt::Icon::None, Wt::StandardButton::Ok);
        imprintDialog->contents()->addNew<Wt::WText>("<h1>Imprint</h1>");
        imprintDialog->contents()->addNew<Wt::WText>("<p>Information according to § 5 TMG</p>");
        imprintDialog->contents()->addNew<Wt::WText>("<p>Torsten Schulz <br> "
                                                     "Friedrich-Stampfer-Str. 21<br>  "
                                                     "60437 Frankfurt <br>  "
                                                     "</p>", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->addNew<Wt::WText>("<p> <strong>Represented by: </strong><br> "
                                                     "Torsten Schulz<br> "
                                                     "</p>", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->addNew<Wt::WText>("<p><strong>Contact:</strong> <br> "
                                                     "Phone: 069-95 64 17 10<br> "
                                                     "Email: <a href='mailto:tsschulz@tsschulz.de'>tsschulz@tsschulz.de</a></br> "
                                                     "</p>", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->addNew<Wt::WText>("<p><strong>Disclaimer: </strong><br><br><strong>Liability for Contents</strong><br><br> "
                                                     "The contents of our pages were created with the greatest care. However, we cannot guarantee the correctness, completeness, and topicality of the contents. As a service provider, we are responsible for our own content on these pages in accordance with § 7 para.1 TMG (German Telemedia Act) and general laws. According to §§ 8 to 10 TMG, however, we are not obliged as service providers to monitor transmitted or stored third-party information or to investigate circumstances that indicate illegal activity. Obligations to remove or block the use of information under general law remain unaffected. However, liability in this regard is only possible from the time of knowledge of a concrete infringement. If we become aware of any such legal infringements, we will remove the content immediately.<br><br><strong>Liability for Links</strong><br><br> "
                                                     "Our offer contains links to external websites of third parties, on whose contents we have no influence. Therefore, we cannot assume any liability for these external contents. The respective provider or operator of the pages is always responsible for the contents of the linked pages. The linked pages were checked for possible legal violations at the time of linking. Illegal contents were not recognizable at the time of linking. However, permanent monitoring of the content of the linked pages is not reasonable without concrete evidence of a violation of the law. If we become aware of any infringements, we will remove such links immediately.<br><br><strong>Data Protection</strong><br><br> "
                                                     "The use of our website is usually possible without providing personal data. As far as personal data (e.g., name, address, or email addresses) is collected on our website, this is always done on a voluntary basis as far as possible. This data will not be passed on to third parties without your express consent. <br> "
                                                     "We would like to point out that data transmission over the Internet (e.g., communication by email) can have security gaps. A complete protection of data against access by third parties is not possible. <br> "
                                                     "The use of contact data published within the scope of the imprint obligation by third parties for sending unsolicited advertising and information materials is hereby expressly prohibited. The operators of these pages expressly reserve the right to take legal action in the event of unsolicited sending of advertising information, such as spam emails.<br> "
                                                     "</p>", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->addNew<Wt::WText>("<br>  "
                                                     "Imprint from <a href=\"https://www.impressum-generator.de\">Imprint Generator</a> of <a href=\"https://www.kanzlei-hasselbach.de/\">Kanzlei Hasselbach, Lawyers for Labor Law and Family Law</a> ", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->addNew<Wt::WText>("<br>  "
                                                     "Thanks for the flag icons to <a href=\"https://flagpedia.net\">flagpedia.net</a>", Wt::TextFormat::UnsafeXHTML);
        imprintDialog->contents()->setMaximumSize(Wt::WLength(60, Wt::LengthUnit::FontEm), Wt::WLength(40, Wt::LengthUnit::FontEm));
        imprintDialog->contents()->setOverflow(Wt::Overflow::Auto);
        imprintDialog->buttonClicked().connect([=]() { imprintDialog->accept(); });
        imprintDialog->show();
    });
}

Wt::WWebWidget* App::createImageElement(Wt::Json::Object& line, const std::string& writer, Wt::WContainerWidget* outputContainer, std::string id) {
    Wt::Json::Object imageDescription = line["image"];
    auto imageLineItem = outputContainer->addNew<Wt::WContainerWidget>();
    auto outputText = Wt::WString("<b>{1}:</b>&nbsp;").arg(writer);
    imageLineItem->addNew<Wt::WText>(outputText)->setStyleClass("output-line");
    auto image = imageLineItem->addNew<Wt::WImage>();
    std::string base64Data = (std::string)imageDescription["imageblobbase64"];
    std::string decodedData = Wt::Utils::base64Decode(base64Data);
    std::string imageType = imageDescription["type"].toString(); // Lese den Bildtyp
    auto imageResource = std::make_shared<Wt::WMemoryResource>(
        "image/" + imageType,
        std::vector<unsigned char>(decodedData.begin(), decodedData.end()));
    std::cout << __LINE__ << std::endl;
    image->setImageLink(Wt::WLink(imageResource));
    imageLineItem->setAttributeValue("dummy", id);
    return imageLineItem;
}

std::string App::getChatLineWriter(Wt::Json::Object &line, Wt::Json::Object conversation) {
    std::string sender = (std::string)line["sender"];
    if (sender == sessionId()) {
        return "you";
    } else if (sender == (std::string)conversation["sessionid1"]) {
        return (std::string)conversation["user1"];
    } else {
        return (std::string)conversation["user2"];
    }
}

std::string App::replaceSmileys(std::string outputText) {
    for (const auto &smiley : smileys_) {
        std::size_t pos = outputText.find(smiley.first);
        while (pos != std::string::npos) {
            outputText.replace(pos, smiley.first.length(), "&#x" + smiley.second.code + ";");
            pos = outputText.find(smiley.first, pos + smiley.second.code.length());
        }
    }
    return outputText;
}

void App::showUnreadMessages(Wt::Json::Object data) {
    Wt::WString base = Wt::WString::tr("menu_inbox");
    Wt::WString buttonText = (int)data["data"] == 0 ? base : Wt::WString("{1} ({2})").arg(base).arg((int)data["data"]);
    auto currentText = inbox_->text();
    inbox_->setText(buttonText);
    std::string pattern{"Posteingang\\(\\s*(\\d+)\\)"};
    std::regex regex(pattern);
    std::string text = currentText.toUTF8();
    std::smatch match;
    auto doPlay{false};
    if (std::regex_search(text, match, regex)) {
        std::string numberStr = match[2].str();
        auto oldValue = std::stoi(numberStr);
        if (oldValue < (int)data["data"] && (int)data["data"] > 0) {
            doPlay = true;
        }
    } else if (text == Wt::WString::tr("menu_inbox") && buttonText != Wt::WString::tr("menu_inbox")) {
        doPlay = true;
    }
    if (doPlay) {
        messageReceived_->play();
    }
    triggerUpdate();
    if (inboxOpen_) {
        server_.sendOpenConversations(sessionId());
    }
}

void App::showOpenInbox(Wt::Json::Object data) {
    searchFields.outputContainer = nullptr;
    contentContainer_->clear();
    contentContainer_->setPadding(Wt::WLength(1, Wt::LengthUnit::FontEm), Wt::Side::Top | Wt::Side::Bottom);
    contentContainer_->setPadding(Wt::WLength(2, Wt::LengthUnit::FontEm), Wt::Side::Left | Wt::Side::Right);
    contentContainer_->setOverflow(Wt::Overflow::Auto);
    auto headline = contentContainer_->addNew<Wt::WText>("<h2>Inbox</h2>");
    headline->setInline(false);
    auto conversationsTable = contentContainer_->addNew<Wt::WTable>();
    conversationsTable->resize(Wt::WLength(15, Wt::LengthUnit::FontEm), Wt::WLength::Auto);
    for (Wt::Json::Object &user: (Wt::Json::Array)data["data"]) {
        auto row = conversationsTable->rowCount();
        auto userItem = conversationsTable->elementAt(row, 0)->addNew<Wt::WContainerWidget>();
        Wt::WString partnerName = (std::string)user["name"];
        auto flag = userItem->addNew<Wt::WImage>(Wt::WLink(std::make_shared<Wt::WFileResource>("../docroot/flags/" + (std::string)user["isoCountryCode"] + ".png")));
        flag->setToolTip(country);
        flag->setStyleClass("flag-icon");
        userItem->addNew<Wt::WText>(Wt::WString("{1} ({2})").arg(partnerName).arg((int)user["age"]));
        userItem->setStyleClass(Wt::WString("userlist-item userlist-gender-{1}").arg((std::string)user["gender"]));
        userItem->setHeight(Wt::WLength(2, Wt::LengthUnit::FontEm));
        userItem->setPadding(Wt::WLength(3, Wt::LengthUnit::Pixel));
        userItem->clicked().connect([=, this]() {
            requestConversation(partnerName.toUTF8());
        });
    }
    triggerUpdate();
}

void App::updateUserinfo(Wt::Json::Object data) {
    Wt::Json::Object userData = data["data"];
    if ((std::string)userData["name"] != currentConversationWith_) {
        return;
    }
    auto containerLayout = (Wt::WVBoxLayout*)contentContainer_->layout();
    auto infoLayoutItem = containerLayout->itemAt(0);
    auto infoWidget = (Wt::WContainerWidget*)(infoLayoutItem->widget());
    auto infoWidgetLayout = (Wt::WHBoxLayout*)infoWidget->layout();
    auto infoTextWidget = dynamic_cast<Wt::WText*>(infoWidgetLayout->itemAt(1)->widget());
    auto blockButton = dynamic_cast<Wt::WPushButton*>(infoWidgetLayout->itemAt(2)->widget());
    try {
        infoWidget->setStyleClass(Wt::WString("user-conversation-info userlist-gender-{1}").arg((std::string)userData["gender"]));
        if (infoTextWidget) {
            infoTextWidget->setText(Wt::WString("{1} ({2}) - {3}, {4}").arg((std::string)userData["name"])
                .arg((int)userData["age"]).arg((std::string)userData["country"]).arg(genders_[(std::string)userData["gender"]]));
        }
        blockButton->setText((bool)userData["blocked"] ? "Unblock" : "Block");
    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl << Wt::Json::serialize(userData) << std::endl;
    }
    triggerUpdate();
}

std::unique_ptr<Wt::WContainerWidget> App::createSmileysBar(Wt::WLineEdit *inputLine, std::shared_ptr<int> cursorPosition) {
    auto smileyBar = std::make_unique<Wt::WContainerWidget>();
    smileyBar->setHidden(true);
    for (auto &smiley: smileys_) {
        auto code = smileyPlaceholder_;
        code = code.arg(smiley.second.code);
        auto item = smileyBar->addNew<Wt::WText>(code, Wt::TextFormat::UnsafeXHTML);
        item->setToolTip(smiley.second.tooltip);
        item->clicked().connect([=]() {
            try {
                auto currentText = inputLine->text().toUTF8();
                currentText.insert(*cursorPosition, smiley.first);
                inputLine->setText(currentText);
                inputLine->setFocus();
                *cursorPosition += smiley.first.length();
            } catch (const std::exception &e) {
                std::cout << e.what() << std::endl;
            }
        });
    }
    smileyBar->setStyleClass("smiley-bar");
    return smileyBar;
}

void App::toggleSmileysBar(Wt::WContainerWidget *smileyBar) {
    smileyBar->setHidden(!smileyBar->isHidden());
}

void App::systemEvent(Wt::Json::Object broadcast) {
    if ((std::string)broadcast["related-user"] != currentConversationWith_) {
        return;
    }
    auto containerLayout = (Wt::WVBoxLayout*)contentContainer_->layout();
    auto infoLayoutItem = containerLayout->itemAt(0);
    auto infoWidget = (Wt::WContainerWidget*)(infoLayoutItem->widget());
    auto infoWidgetLayout = (Wt::WHBoxLayout*)infoWidget->layout();
    auto blockButton = dynamic_cast<Wt::WPushButton*>(infoWidgetLayout->itemAt(2)->widget());
    if (broadcast["data"] == "blocked") {
        blockButton->setText("Unblock");
    } else if (broadcast["data"] == "unblocked") {
        blockButton->setText("Block");
    }
}


void App::createMenu() {
    menuContainer_->clear();
    menuContainer_->setStyleClass("menu");
    addLeaveButton();
    addIdentifier();
    addSearchButton();
    addInboxButton();
    addHistoryButton();
    addLoginTimeView();
    addTimeoutView();
}

void App::addLeaveButton() {
    auto leaveButton = menuContainer_->addNew<Wt::WPushButton>(Wt::WString::tr("menu_leave"));
    leaveButton->clicked().connect(this, &App::logout);
}

void App::logout() {
    server_.addToDisconnectList(this);
    userListContainer_->clear();
    menuContainer_->clear();
    userName = "";
    showLogin();
    inboxOpen_ = false;
    searchFields.outputContainer = nullptr;
}

void App::addHistoryButton() {
    auto history = menuContainer_->addNew<Wt::WPushButton>(Wt::WString::tr("menu_history"));
    history->clicked().connect(this, &App::requestHistory);
}

void App::addIdentifier() {
    menuContainer_->setPositionScheme(Wt::PositionScheme::Relative);
    auto flag = menuContainer_->addNew<Wt::WImage>(Wt::WLink(std::make_shared<Wt::WFileResource>("../docroot/flags/" + isoCountryCode + ".png")));
    flag->setToolTip(country);
    flag->setStyleClass("flag-icon");
    auto identifier = menuContainer_->addNew<Wt::WText>(Wt::WString("{1} ({2}), {3}").arg(userName).arg(isoCountryCode).arg(age));
    identifier->setMargin(Wt::WLength(0.3, Wt::LengthUnit::FontEm), Wt::Side::Bottom | Wt::Side::Top);
    Wt::WCssDecorationStyle backgroundStyle;
    backgroundStyle.setBackgroundColor(Wt::StandardColor::White);
    identifier->setStyleClass("inline-block");
    identifier->setPadding(Wt::WLength(0.3, Wt::LengthUnit::FontEm), Wt::Side::Left | Wt::Side::Right);
    identifier->setDecorationStyle(backgroundStyle);
    identifier->setWidth(Wt::WLength(25, Wt::LengthUnit::FontEm));
}

void App::addSearchButton() {
    auto searchButton = menuContainer_->addNew<Wt::WPushButton>(Wt::WString::tr("menu_search"));
    searchButton->clicked().connect(this, &App::showSearchWindow);
}

void App::addInboxButton() {
    inbox_ = menuContainer_->addNew<Wt::WPushButton>(Wt::WString::tr("menu_inbox"));
    inbox_->clicked().connect(this, &App::openInbox);
}

void App::showSearchWindow() {
    setActivity();
    auto contentLayout = resetSearchFields();
    auto userNameField = setupNameSearchField(contentLayout);
    auto ageSearchFields = setupSearchFields(contentLayout);
    auto countryFields = setupCountryDropDown(contentLayout);
    auto gendersFields = setupGendersDropDown(contentLayout);
    searchFields.outputContainer = setupSearchButton(contentLayout);
    restoreSearchFields(searchFields.outputContainer, userNameField, ageSearchFields.first, ageSearchFields.second,
                        countryFields.second, gendersFields.second, countryFields.first, gendersFields.first);
}

Wt::WVBoxLayout *App::resetSearchFields() {
    currentConversationWith_ = "";
    contentContainer_->clear();
    inboxOpen_ = false;
    auto contentLayout = contentContainer_->setLayout(std::make_unique<Wt::WVBoxLayout>());
    contentLayout->addWidget(std::make_unique<Wt::WText>(Wt::WString::tr("search_title"), Wt::TextFormat::UnsafeXHTML));
    return contentLayout;
}

std::pair<Wt::WSpinBox*, Wt::WSpinBox*> App::setupSearchFields(Wt::WVBoxLayout *contentLayout) {
    auto minAgeEdit = addSearchItemLine<Wt::WSpinBox>(contentLayout, Wt::WString::tr("search_from_age").toUTF8());
    minAgeEdit->setRange(18, 150);
    minAgeEdit->setValue(18);
    minAgeEdit->changed().connect([=, this] { searchFields.minAge = minAgeEdit->value(); });
    auto maxAgeEdit = addSearchItemLine<Wt::WSpinBox>(contentLayout, Wt::WString::tr("search_to_age").toUTF8());
    maxAgeEdit->setRange(18, 150);
    maxAgeEdit->setValue(150);
    maxAgeEdit->changed().connect([=, this] { searchFields.maxAge = maxAgeEdit->value(); });
    return {minAgeEdit, maxAgeEdit};
}

std::pair<Wt::WContainerWidget*, Wt::WContainerWidget*> App::setupCountryDropDown(Wt::WVBoxLayout *contentLayout) {
    auto countryOpenList = addSearchItemLine<Wt::WContainerWidget>(contentLayout, Wt::WString::tr("search_country").toUTF8());
    countryOpenList->addNew<Wt::WText>(Wt::WString::tr("search_all"));
    countryOpenList->setStyleClass("selectBoxes-drop-down-trigger");
    auto countryDropDown = countryOpenList->addNew<Wt::WContainerWidget>();
    countryDropDown->setStyleClass("selectBoxes-dropdown");
    countryOpenList->setToolTip(Wt::WString::tr("search_country_tooltip"));
    std::map<Wt::WString, Wt::WString> countries = server_.countries();
    addItem("All", countryDropDown, countryOpenList, &searchFields.countries, true);
    addItem(country, countryDropDown, countryOpenList, &searchFields.countries);
    for (const auto &itemCountry: countries) {
        if (itemCountry.first.toUTF8() != country) {
            addItem(itemCountry.first.toUTF8(), countryDropDown, countryOpenList, &searchFields.countries);
        }
    }
    return {countryOpenList, countryDropDown};
}

std::pair<Wt::WContainerWidget*, Wt::WContainerWidget*> App::setupGendersDropDown(Wt::WVBoxLayout *contentLayout) {
    auto gendersOpenList = addSearchItemLine<Wt::WContainerWidget>(contentLayout, Wt::WString::tr("search_genders").toUTF8());
    gendersOpenList->addNew<Wt::WText>(Wt::WString::tr("search_all"));
    gendersOpenList->setStyleClass("selectBoxes-drop-down-trigger");
    auto gendersDropDown = gendersOpenList->addNew<Wt::WContainerWidget>();
    gendersDropDown->setStyleClass("selectBoxes-dropdown");
    gendersOpenList->setToolTip(Wt::WString::tr("search_genders_tooltip"));
    addItem("All", gendersDropDown, gendersOpenList, &searchFields.gender, true);
    std::map<Wt::WString, Wt::WString> swappedGenders;
    for (const auto& pair : genders_) {
        swappedGenders[pair.second] = pair.first;
    }
    for (const auto &itemGender: swappedGenders) {
        addItem(itemGender.first.toUTF8(), gendersDropDown, gendersOpenList, &searchFields.gender);
    }
    return {gendersOpenList, gendersDropDown};
}


void App::addItem(const std::string& country, Wt::WContainerWidget *dropDownContainer, Wt::WContainerWidget *container, std::unordered_set<std::string> *saveItems, bool isSelected) {
    auto menuItem = dropDownContainer->addNew<Wt::WCheckBox>(country);
    menuItem->changed().connect([=, this]() mutable { itemChanged(menuItem, dropDownContainer, container, saveItems); });
    menuItem->setInline(false);
    if (isSelected) {
        menuItem->setChecked();
    }
}

void App::addUserItemToLayout(Wt::WVBoxLayout *layout, Wt::Json::Object userObject) {
    auto userName = (std::string)userObject["name"];
    auto line = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    auto flag = line->addNew<Wt::WImage>(Wt::WLink(std::make_shared<Wt::WFileResource>("../docroot/flags/" + (std::string)userObject["isoCountryCode"] + ".png")));
    flag->setMaximumSize(Wt::WLength(60, Wt::LengthUnit::Pixel), Wt::WLength(20, Wt::LengthUnit::Pixel));
    flag->setHeight(Wt::WLength(2, Wt::LengthUnit::FontEm));
    flag->setStyleClass("flag-icon");
    flag->setToolTip(userObject["country"]);
    line->addNew<Wt::WText>(Wt::WString("{1} ({2})").arg(userName).arg((int)userObject["age"]));
    line->setStyleClass(Wt::WString("userlist-item userlist-gender-{1}").arg((std::string)userObject["gender"]));
    line->setHeight(Wt::WLength(2, Wt::LengthUnit::FontEm));
    line->setPadding(Wt::WLength(3, Wt::LengthUnit::Pixel));
    line->clicked().connect([=, this]() {
        requestConversation(userName);
    });

}

std::unordered_set<std::string> App::gendersListToShortGendersList(std::unordered_set<std::string> gendersList) {
    std::unordered_set<std::string> result;
    for (const auto &gender: gendersList) {
        result.insert(genderShortOfGender(gender));
    }
    return result;
}

std::string App::genderShortOfGender(const std::string incomingGender) {
    for (const auto &genderItem: genders_) {
        if (incomingGender == genderItem.second) {
            return genderItem.first.toUTF8();
        }
    }
    return incomingGender;
}

void App::extendSearchResultIfNeeded(Wt::Json::Object broadcast) {
    if (searchFields.outputContainer == nullptr) {
        return;
    }
    auto user = (Wt::Json::Object)broadcast["data"];
    auto age = (int)user["age"];
    auto country = (std::string)user["country"];
    auto gender = (std::string)user["gender"];
    if (
            (searchFields.userName.toUTF8() == "" || ((std::string)user["name"]).find(searchFields.userName.toUTF8()) != std::string::npos)
            && (searchFields.minAge <= age)
            && (searchFields.maxAge >= age)
            && (searchFields.countries.contains("All") || searchFields.countries.contains(country) || searchFields.countries.size() == 0)
            && (searchFields.gender.contains("All") || searchFields.gender.contains(gender) || searchFields.gender.size() == 0)
    ) {
        startSearch();
    }
}

void App::removeUserFromSearch(Wt::Json::Object) {
    if (searchFields.outputContainer == nullptr) {
        return;
    }
    startSearch();
}

void App::requestHistory() {
    setActivity();
    server_.sendHistory(sessionId());
}

void App::showHistory(Wt::Json::Object broadcast) {
    try {
        contentContainer_->clear();
        auto headerWidget = contentContainer_->addNew<Wt::WText>(Wt::WString::tr("history_title"), Wt::TextFormat::UnsafeXHTML);
        headerWidget->setInline(false);
        auto listWidget = contentContainer_->addNew<Wt::WTable>();
        auto dataArray = (Wt::Json::Array)broadcast["data"];

        if (dataArray.empty()) {
            // Keine bisherigen Unterhaltungen vorhanden
            contentContainer_->addNew<Wt::WText>(Wt::WString::tr("history_empty"));
        } else {
            for (Wt::Json::Object user: dataArray) {
                auto userName = std::make_shared<std::string>((std::string)user["name"]);
                auto tableCell = listWidget->elementAt(listWidget->rowCount(), 0);
                tableCell->addNew<Wt::WText>(Wt::WString("{1} ({2})").arg(*userName).arg((int)user["age"]));
                tableCell->setStyleClass(Wt::WString("user-conversation-info userlist-item userlist-gender-{1}").arg((std::string)user["gender"]));
                tableCell->clicked().connect([=, this]() {
                    requestConversation(*userName);
                });
            }
        }
    } catch(std::exception &e) {
        std::cout << e.what() << std::endl;
    }

    triggerUpdate();
}

void App::connectionTimedOut() {
    showLogin();
    loginTimer_->stop();
    timeoutRemainingTimer_->stop();
    menuContainer_->clear();
    userListContainer_->clear();
    triggerUpdate();
}

void App::addLoginTimeView() {
    auto loggedinTimeWidget = menuContainer_->addNew<Wt::WText>();
    loggedinTimeWidget->setStyleClass("menu-info-text");
    loginTimer_ = root()->addChild(std::make_unique<Wt::WTimer>());
    loginTimer_->setInterval(std::chrono::seconds(1));
    auto loggedInRefresh = [=, this]() {
        auto currentLoginSeconds = currentlyLoggedInSeconds();
        int hours = currentLoginSeconds / 3600;
        int minutes = (currentLoginSeconds % 3600) / 60;
        std::stringstream elapsedTimeStream;
        elapsedTimeStream << std::setw(2) << std::setfill('0') << hours << ":"
                          << std::setw(2) << std::setfill('0') << minutes << " h";
        std::string elapsedTimeString = elapsedTimeStream.str();
        loggedinTimeWidget->setText(Wt::WString::tr("menu_in_chat_for").arg(elapsedTimeString));
    };
    loginTimer_->timeout().connect(loggedInRefresh);
    loggedInRefresh();
    loginTimer_->start();
}

void App::addTimeoutView() {
    auto timeoutRemainingWidget = menuContainer_->addNew<Wt::WText>();
    timeoutRemainingWidget->setStyleClass("menu-info-text");
    timeoutRemainingTimer_ = root()->addChild(std::make_unique<Wt::WTimer>());
    timeoutRemainingTimer_->setInterval(std::chrono::milliseconds(500));
    auto timeoutRemainingRefresh = [=, this]() {
        auto remainingLoginSeconds = remainingSecondsToTimeout();
        int minutes = remainingLoginSeconds / 60;
        int seconds = (remainingLoginSeconds % 60);
        std::stringstream remainingTimeStream;
        remainingTimeStream << std::setw(2) << std::setfill('0') << minutes << ":"
                          << std::setw(2) << std::setfill('0') << seconds << " m";
        std::string elapsedTimeString = remainingTimeStream.str();
        timeoutRemainingWidget->setText(Wt::WString::tr("menu_timeout_in").arg(elapsedTimeString));
    };
    timeoutRemainingTimer_->timeout().connect(timeoutRemainingRefresh);
    timeoutRemainingRefresh();
    timeoutRemainingTimer_->start();
}

void App::showPartnerSites() {
    setMetaTags();
    contentContainer_->clear();
    contentContainer_->setOverflow(Wt::Overflow::Auto);
    auto linkContainer = contentContainer_->addNew<Wt::WContainerWidget>();
    auto contentLayout = linkContainer->setLayout(std::make_unique<Wt::WVBoxLayout>());
    contentLayout->addWidget(std::make_unique<Wt::WText>("<h1>Partners</h1>"));
    if (userName == "") {
        auto hpLink = contentLayout->addWidget(std::make_unique<Wt::WText>("Back to main page"));
        hpLink->clicked().connect(this, &App::showLogin);
    }
    rapidcsv::Document doc("../docroot/links.csv");
    for (size_t i = 0; i < doc.GetRowCount(); ++i) {
        auto url = doc.GetCell<std::string>(1, i);
        auto name = doc.GetCell<std::string>(0, i);
        auto link = Wt::WLink(url);
        link.setTarget(Wt::LinkTarget::NewWindow);
        contentLayout->addWidget(std::make_unique<Wt::WAnchor>(link, name));
    }
}

void App::itemChanged(Wt::WCheckBox *item, Wt::WContainerWidget *dropDownContainer, Wt::WContainerWidget *container, std::unordered_set<std::string> *saveItems) {
    saveItems->clear();
    bool unselect = (item->text() == "All" && item->isChecked());
    for (auto &widgetItem: dropDownContainer->children()) {
        auto widgetCheckBox = static_cast<Wt::WCheckBox*>(widgetItem);
        if (unselect && widgetCheckBox->text() != "All" && widgetCheckBox->isChecked()) {
            widgetCheckBox->setChecked(false);
        }
        if (widgetCheckBox->isChecked()) {
            saveItems->insert(widgetCheckBox->text().toUTF8());
        }
    }
    if (saveItems->size() > 1 && saveItems->find("All") != saveItems->end()) {
        saveItems->erase(saveItems->find("All"));
        static_cast<Wt::WCheckBox*>(dropDownContainer->children().at(0))->setChecked(false);
    }
    if (saveItems->empty()) {
        saveItems->insert("All");
    }
    std::string result;
    for (const auto &selected: *saveItems) {
        result += (result.empty() ? "" : ", ") + selected;
    }
    auto outputWidget = (Wt::WText*)container->children().at(0);
    outputWidget->setText(result);
}

void App::restoreSearchFields(Wt::WContainerWidget *searchResultContainer, Wt::WLineEdit *userNameEdit, Wt::WSpinBox *minAgeEdit,
                              Wt::WSpinBox *maxAgeEdit, Wt::WContainerWidget *countryDropDownContainer, Wt::WContainerWidget *gendersDropDownContainer,
                              Wt::WContainerWidget *countryOpenList, Wt::WContainerWidget *gendersOpenList) {
    if (!searchFields.set) {
        searchFields = Search(searchResultContainer);
    } else {
        userNameEdit->setValueText(searchFields.userName);
        minAgeEdit->setValue(searchFields.minAge);
        maxAgeEdit->setValue(searchFields.maxAge);
        for (auto countryWidget: countryDropDownContainer->children()) {
            auto countryCheckBox = (Wt::WCheckBox*)countryWidget;
            countryCheckBox->setChecked(searchFields.countries.find(countryCheckBox->text().toUTF8()) != searchFields.countries.end());
        }
        itemChanged((Wt::WCheckBox*)*countryDropDownContainer->children().begin(), countryDropDownContainer, countryOpenList, &searchFields.countries);
        for (auto genderWidget: gendersDropDownContainer->children()) {
            auto genderCheckBox = (Wt::WCheckBox*)genderWidget;
            genderCheckBox->setChecked(searchFields.gender.find(genderCheckBox->text().toUTF8()) != searchFields.gender.end());
        }
        itemChanged((Wt::WCheckBox*)*gendersDropDownContainer->children().begin(), gendersDropDownContainer, gendersOpenList, &searchFields.gender);
        startSearch();
    }
}

Wt::WLineEdit *App::setupNameSearchField(Wt::WVBoxLayout *contentLayout) {
    auto userNameEdit = addSearchItemLine<Wt::WLineEdit>(contentLayout, Wt::WString::tr("search_username_includes").toUTF8());
    userNameEdit->changed().connect([=, this] { searchFields.userName = userNameEdit->text().trim(); });
    return userNameEdit;
}

Wt::WContainerWidget *App::setupSearchButton(Wt::WVBoxLayout *contentLayout) {
    auto searchButton = addSearchItemLine<Wt::WPushButton>(contentLayout, "");
    searchButton->setText(Wt::WString::tr("search_button"));
    auto searchResultContainer = contentLayout->addWidget(std::make_unique<Wt::WContainerWidget>(), 1);
    searchResultContainer->addNew<Wt::WText>(Wt::WString::tr("search_no_results"));
    searchButton->clicked().connect(this, &App::startSearch);
    return searchResultContainer;
}

void App::startSearch() {
    if (searchFields.minAge > searchFields.maxAge) {
        searchFields.outputContainer->clear();
        searchFields.outputContainer->addNew<Wt::WText>(Wt::WString::tr("search_min_age_error"));
        return;
    }
    server_.userSearch(sessionId(), searchFields.userName.toUTF8(), searchFields.minAge, searchFields.maxAge,
                       searchFields.countries, gendersListToShortGendersList(searchFields.gender), userName);
}

void App::showSearch(Wt::Json::Object broadcast) {
    if (!searchFields.outputContainer) {
        return;
    }
    searchFields.outputContainer->clear();
    auto searchResult = (Wt::Json::Array)broadcast["data"];
    if (searchResult.size() == 0) {
        searchFields.outputContainer->addNew<Wt::WText>(Wt::WString::tr("search_no_results"));
    }
    auto searchListContainer = searchFields.outputContainer->addNew<Wt::WContainerWidget>();
    auto searchList = searchListContainer->setLayout(std::make_unique<Wt::WVBoxLayout>());
    searchListContainer->setOverflow(Wt::Overflow::Auto);
    for (const Wt::Json::Object &searchItem: searchResult) {
        addUserItemToLayout(searchList, searchItem);
    }
    triggerUpdate();
}

void App::openInbox() {
    setActivity();
    currentConversationWith_ = "";
    contentContainer_->clear();
    contentContainer_->addNew<Wt::WText>("<h2>Inbox</h2>");
    inboxOpen_ = true;
    searchFields.outputContainer = nullptr;
    server_.sendOpenConversations(sessionId());
}

void App::incomingBroadcast() {
    auto broadcasts = server_.getBroadcastsForSession(sessionId());
    for (Wt::Json::Object &broadcast: broadcasts) {
        if (broadcast["type"] == "userlist") {
            updateUserlist(broadcast["data"], (int)broadcast["count"]);
        } else if (broadcast["type"] == "logout") {
            server_.disconnect(this);
        } else if (broadcast["type"] == "messagequeue") {
            renderConversation(broadcast);
        } else if (broadcast["type"] == "unread-chats") {
            showUnreadMessages(broadcast);
        } else if (broadcast["type"] == "openconversations" && inboxOpen_) {
            showOpenInbox(broadcast);
        } else if (broadcast["type"] == "userinfo") {
            updateUserinfo(broadcast);
        } else if (broadcast["type"] == "system") {
            systemEvent(broadcast);
        } else if (broadcast["type"] == "conversation-start") {
            showConversation(broadcast);
        } else if (broadcast["type"] == "search-result") {
            showSearch(broadcast);
        } else if (broadcast["type"] == "newuser") {
            extendSearchResultIfNeeded(broadcast);
        } else if (broadcast["type"] == "userleft") {
            removeUserFromSearch(broadcast);
        } else if (broadcast["type"] == "history") {
            showHistory(broadcast);
        } else if (broadcast["type"] == "timedout") {
            connectionTimedOut();
        }
    }
}

void App::startChat() {
    createMenu();
    contentContainer_->clear();
    contentContainer_->addNew<Wt::WText>(Wt::WString::tr("introduction"), Wt::TextFormat::UnsafeXHTML);
    setLoggedIn();
}

void App::createUserListContainer(Wt::WHBoxLayout *layout) {
    userListContainer_ = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    userListContainer_->setStyleClass("userlist");
}

void App::createContentContainer(Wt::WHBoxLayout *layout) {
    contentContainer_ = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    contentContainer_->setStyleClass("content");
}

void App::updateLocation() {
    CURL *curl;
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    try {
        if (curl) {
            std::string userIP = getUserIP();
            std::string apiUrl = buildApiUrl(userIP);
            setCurlOptions(curl, apiUrl);
            performCurlRequest(curl);
            processCurlResponse();
            curl_easy_cleanup(curl);
        }
    } catch (std::exception &e) {
        handleException(e);
    }
    curl_global_cleanup();
}

std::string App::getUserIP() {
    std::string userIP = env_.clientAddress();
    return (userIP == "127.0.0.1") ? "77.189.91.196" : userIP;
}

std::string App::buildApiUrl(const std::string &userIP) {
    return "http://ip-api.com/xml/" + userIP;
}

void App::setCurlOptions(CURL *curl, const std::string &apiUrl) {
    curl_easy_setopt(curl, CURLOPT_URL, apiUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &App::WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData_);
}

void App::performCurlRequest(CURL *curl) {
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        throw std::runtime_error("Curl request failed");
    }
}

void App::processCurlResponse() {
    xmlDocPtr doc = xmlReadMemory(responseData_.c_str(), responseData_.size(), NULL, NULL, 0);
    if (doc != NULL) {
        parseXmlDocument(doc);
        xmlFreeDoc(doc);
    }
}

void App::parseXmlDocument(xmlDocPtr doc) {
    xmlNodePtr countryCodeNode = xmlDocGetRootElement(doc)->children;
    while (countryCodeNode != nullptr) {
        processXmlNode(countryCodeNode);
        countryCodeNode = countryCodeNode->next;
    }
}

void App::processXmlNode(xmlNodePtr node) {
    if (xmlStrEqual(node->name, BAD_CAST "countryCode")) {
        isoCountryCode = reinterpret_cast<const char *>(xmlNodeGetContent(node));
    } else if (xmlStrEqual(node->name, BAD_CAST "country")) {
        country = reinterpret_cast<const char *>(xmlNodeGetContent(node));
    }
}

void App::handleException(const std::exception &e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

bool App::isNickAllowed(const std::string& nick) {
    std::string lowercaseNick = nick;
    std::transform(lowercaseNick.begin(), lowercaseNick.end(), lowercaseNick.begin(), ::tolower);
    return (lowercaseNick != "self") &&
            (lowercaseNick != "system") &&
            (lowercaseNick != "you") &&
            std::all_of(notAllowedNickPhrases_.begin(), notAllowedNickPhrases_.end(),
                       [&](const std::string& phrase) {
                           std::string lowercasePhrase = phrase;
                           std::transform(lowercasePhrase.begin(), lowercasePhrase.end(), lowercasePhrase.begin(), ::tolower);
                           return lowercaseNick.find(lowercasePhrase) == std::string::npos;
                       });
}

template<class Class>
Class *App::addSearchItemLine(Wt::WVBoxLayout *layout, std::string label, std::unique_ptr<Wt::WContainerWidget> additionalItem) {
    auto lineContainer = layout->addWidget(std::make_unique<Wt::WContainerWidget>());
    lineContainer->setStyleClass("search-line");
    lineContainer->setPositionScheme(Wt::PositionScheme::Relative);
    auto lineLayout = lineContainer->setLayout(std::make_unique<Wt::WHBoxLayout>());
    lineContainer->setPadding(Wt::WLength("0"));
    lineContainer->setMargin(Wt::WLength("0"));
    auto labelWidget = lineLayout->addWidget(std::make_unique<Wt::WText>(label));
    labelWidget->setWidth(Wt::WLength(9, Wt::LengthUnit::FontEm));
    lineLayout->setContentsMargins(0, 0, 0, 0);
    lineLayout->setSpacing(0);
    auto input = std::make_unique<Class>();
    auto returnInput = input.get();
    lineLayout->addWidget(std::move(input), 1);
    if (additionalItem) {
        lineLayout->addWidget(std::move(additionalItem));
        lineContainer->setOverflow(Wt::Overflow::Visible);
    }
    return returnInput;
}
