#ifndef APP_H
#define APP_H

#include <Wt/WApplication.h>
#include <Wt/WEnvironment.h>
#include "broadcast.h"
#include "curl/curl.h"
#include <libxml2/libxml/tree.h>
#include <libxml2/libxml/xpath.h>
#include <unordered_set>

namespace Magick {
    class Image;
}

class App : public Wt::WApplication, public Client {
public:
    App(const Wt::WEnvironment& env, Broadcast& server);
    ~App();

private:
    std::map<Wt::WString, Wt::WString> genders_ {
        {"F", "Female"},
        {"M", "Male"},
        {"P", "Pair"},
        {"TF", "Transgender (M->F)"},
        {"TM", "Transgender (F->M)"}
    };
    std::vector<std::string> notAllowedNickPhrases_ {
        "whore",
        "hitler",
        "nazi",
        "admin"
    };
    struct Smiley {
        std::string code;
        std::string tooltip;
        Smiley()=default;
        Smiley(std::string code_, std::string tooltip_): code(code_), tooltip(tooltip_) {};
    };
    std::unordered_map<std::string, Smiley> smileys_ {
        {":)", Smiley("1F642", "Smile")},
        {":D", Smiley("1F600", "Laugh")},
        {":(", Smiley("1F641", "Sad")},
        {";)", Smiley("1F609", "Twinkle")},
        {":p", Smiley("1F60B", "Tongue")},
        {";p", Smiley("1F61C", "Twinkle tongue")},
        {"O)", Smiley("1F607", "Angel")},
        {":*", Smiley("1F617", "Kiss")},
        {"<3", Smiley("1FA77", "Heart")},
        {"xD", Smiley("1F602", "Laughing hard")},
        {":@", Smiley("1F635", "Confused")},
        {":O", Smiley("1F632", "Surprised")},
        {":3", Smiley("1F63A", "Cat face")},
        {":|", Smiley("1F610", "Neutral")},
        {":/", Smiley("1FAE4", "Skeptical")},
        {":#", Smiley("1F912", "Sick")},
        {"#)", Smiley("1F973", "Partied")},
        {"%)", Smiley("1F974", "Drunk")},
        {":'(", Smiley("1F622", "Cry")}
    };
    struct Search {
        Wt::WContainerWidget *outputContainer = nullptr;
        Wt::WString userName{""};
        int minAge{18};
        int maxAge{150};
        std::unordered_set<std::string> gender;
        std::unordered_set<std::string> countries;
        Search()=default;
        Search(Wt::WContainerWidget *outputContainer_):
            outputContainer(outputContainer_) {
            set = true;
            gender.insert("All");
            countries.insert("All");
        }
        bool set = false;
    };
    Wt::WString smileyPlaceholder_ = "&#x{1};";
    const Wt::WEnvironment &env_;
    Broadcast &server_;
    Wt::WContainerWidget *menuContainer_;
    Wt::WContainerWidget *userListContainer_;
    Wt::WContainerWidget *contentContainer_;
    Wt::JSignal<std::string, std::string> updateLocationSignal_;
    std::string responseData_{""};
    Wt::WPushButton *inbox_;
    std::vector<std::string> searchResults_;
    std::string currentConversationWith_{""};
    bool inboxOpen_{false};
    int messageCursorPosition_{-1};
    std::unique_ptr<Wt::WSound>   messageReceived_;
    Search searchFields;
    void initApp();
    void reSetUser();
    Wt::WVBoxLayout *createVerticalLayout();
    Wt::WHBoxLayout *createActionLayout(Wt::WVBoxLayout *verticalContainer);
    void createUserListContainer(Wt::WHBoxLayout *layout);
    void createContentContainer(Wt::WHBoxLayout *layout);
    void createHeadContainer(Wt::WVBoxLayout *layout);
    void createMenuContainer(Wt::WVBoxLayout *layout);
    void showLogin();
    void incomingBroadcast();
    void startChat();
    void updateLocation();
    static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *output) {
        size_t totalSize = size * nmemb;
        output->append(reinterpret_cast<const char *>(contents), totalSize);
        return totalSize;
    };
    void populateCountryComboBox(Wt::WComboBox *countryWidget);
    void handleLogin(Wt::WLineEdit *userName, Wt::WComboBox *countryWidget, Wt::WSpinBox *ageWidget, Wt::WComboBox *genderWidget);
    void populateGenderComboBox(Wt::WComboBox *genderWidget);
    std::string getGenderShortByGender(std::string gender);
    void updateUserlist(Wt::Json::Array userList);
    std::vector<Wt::Json::Object> sortUserList(Wt::Json::Array unsortedUserList);
    void createMenu();
    void addLeaveButton();
    void logout();
    void addHistoryButton();
    void addIdentifier();
    void addSearchButton();
    void addInboxButton();
    void showSearchWindow();
    void startSearch();
    void showSearch(Wt::Json::Object broadcast);
    template<class Class> Class *addSearchItemLine(Wt::WVBoxLayout *layout, std::string label, std::unique_ptr<Wt::WContainerWidget> additionalItem = nullptr);
    void openInbox();
    bool isNickAllowed(const std::string &nick);
    bool compareJsonObjects(const Wt::Json::Object &obj1, const Wt::Json::Object &obj2);
    void requestConversation(std::string conversationWith);
    void showConversation(Wt::Json::Object data);
    void sendMessage(Wt::WLineEdit *inputLine);
    void renderConversation(Wt::Json::Object conversation);
    void showUnreadMessages(Wt::Json::Object data);
    void showOpenInbox(Wt::Json::Object data);
    void updateUserinfo(Wt::Json::Object data);
    std::unique_ptr<Wt::WContainerWidget> createSmileysBar(Wt::WLineEdit *inputLine, std::shared_ptr<int> cursorPosition);
    void toggleSmileysBar(Wt::WContainerWidget *smileyBar);
    void showSystemMessage(Wt::Json::Object broadcast);
    void addStartChatButton(Wt::WGridLayout *contentGrid, Wt::WLineEdit *userName, Wt::WComboBox *country, Wt::WSpinBox *age, Wt::WComboBox *gender);
    Wt::WComboBox *addCountrySelection(Wt::WGridLayout *contentGrid);
    Wt::WSpinBox *addAgeInput(Wt::WGridLayout *contentGrid);
    Wt::WLineEdit *addUsernameInput(Wt::WGridLayout *contentGrid);
    Wt::WComboBox *addGenderSelection(Wt::WGridLayout *contentGrid);
    void createLoginContainer();
    void connectToServer();
    void setUserData(const std::string &nick, Wt::WComboBox *countryWidget, Wt::WSpinBox *ageWidget, Wt::WComboBox *genderWidget);
    void validateAge(Wt::WSpinBox *ageWidget);
    bool isGenderSelected(Wt::WComboBox *genderWidget);
    void validateGender(Wt::WComboBox *genderWidget);
    bool isNameAlreadyInUse(const std::string &nick);
    bool isInvalidName(const std::string &nick);
    void validateName(const std::string &nick);
    std::string extractTrimmedUserName(Wt::WLineEdit *userName);
    Wt::WPushButton *createSendButton(Wt::WHBoxLayout *inputLayout, Wt::WLineEdit *inputLine);
    Wt::WContainerWidget *createSmileyBar(Wt::WContainerWidget *parent, Wt::WLineEdit *inputLine, std::shared_ptr<int> cursorPosition);
    Wt::WContainerWidget *createSmileyButton(Wt::WHBoxLayout *inputLayout, Wt::WLineEdit *inputLine, std::shared_ptr<int> cursorPosition);
    Wt::WImage *createSendImageButton(Wt::WHBoxLayout *inputLayout);
    Wt::WLineEdit *createInputLine(Wt::WHBoxLayout *inputLayout);
    Wt::WContainerWidget *createInputContainer(Wt::WVBoxLayout *layout);
    std::unique_ptr<Wt::WText> createInfoText(Wt::Json::Object userData);
    Wt::WContainerWidget *createInfoWidget(Wt::WVBoxLayout *layout, Wt::Json::Object userData);
    void setupConversationUI(Wt::Json::Object userData);
    bool shouldShowConversation(Wt::Json::Object userData);
    Wt::Json::Object extractUserData(Wt::Json::Object data);
    std::unique_ptr<Wt::WPushButton> createBlockButton(Wt::Json::Object userData);
    std::string replaceSmileys(std::string outputText);
    void renderChatLine(Wt::Json::Object &line, Wt::Json::Object conversation, Wt::WContainerWidget *outputContainer);
    std::string getChatLineWriter(Wt::Json::Object &line, Wt::Json::Object conversation);
    void renderChatLines(Wt::Json::Object conversation, Wt::WContainerWidget *outputContainer);
    void updateOutputContainer(Wt::Json::Object conversation);
    void handleException(const std::exception &e);
    void processXmlNode(xmlNodePtr node);
    void parseXmlDocument(xmlDocPtr doc);
    void processCurlResponse();
    void performCurlRequest(CURL *curl);
    void setCurlOptions(CURL *curl, const std::string &apiUrl);
    std::string buildApiUrl(const std::string &userIP);
    std::string getUserIP();
    Magick::Image scaleImage(const Magick::Image &originalImage, int maxSize) const;
    Wt::WWebWidget *createImageElement(Wt::Json::Object &line, const std::string &writer, Wt::WContainerWidget *outputContainer);
    Wt::WWebWidget *createTextElement(const std::string &writer, const std::string &text, Wt::WContainerWidget *outputContainer);
    void createImprintContainer(Wt::WVBoxLayout *containerLayout);
    Wt::WContainerWidget *setupSearchButton(Wt::WVBoxLayout *contentLayout);
    void restoreSearchFields(Wt::WContainerWidget *searchResultContainer, Wt::WLineEdit *userNameEdit, Wt::WSpinBox *minAgeEdit, Wt::WSpinBox *maxAgeEdit, Wt::WContainerWidget *countryDropDownContainer, Wt::WContainerWidget *gendersDropDownContainer, Wt::WPushButton *countryOpenList, Wt::WPushButton *gendersOpenList);
    Wt::WLineEdit *setupNameSearchField(Wt::WVBoxLayout *contentLayout);
    std::pair<Wt::WPushButton *, Wt::WContainerWidget *> setupGendersDropDown(Wt::WVBoxLayout *contentLayout);
    std::pair<Wt::WPushButton *, Wt::WContainerWidget *> setupCountryDropDown(Wt::WVBoxLayout *contentLayout);
    std::pair<Wt::WSpinBox*, Wt::WSpinBox*> setupSearchFields(Wt::WVBoxLayout *contentLayout);
    Wt::WVBoxLayout *resetSearchFields();
    void itemChanged(Wt::WCheckBox *item, Wt::WContainerWidget *dropDownContainer, Wt::WPushButton *openButton, std::unordered_set<std::string> *saveItems);
    void addItem(const std::string &country, Wt::WContainerWidget *dropDownContainer, Wt::WPushButton *openListButton, std::unordered_set<std::string> *saveItems, bool isSelected = false);
    void addUserItemToLayout(Wt::WVBoxLayout *layout, Wt::Json::Object userObject);
    std::unordered_set<std::string> gendersListToShortGendersList(std::unordered_set<std::string> gendersList);
    std::string genderShortOfGender(const std::string incomingGender);
    void extendSearchResultIfNeeded(Wt::Json::Object broadcast);
    void removeUserFromSearch(Wt::Json::Object broadcast);
    void requestHistory();
    void showHistory(Wt::Json::Object broadcast);
};

#endif // APP_H
