#ifndef BROADCAST_H
#define BROADCAST_H

#include <thread>
#include <mutex>
#include <functional>
#include <map>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <Wt/WServer.h>
#include <Wt/WDate.h>
#include <Wt/Json/Object.h>

namespace Magick {
    class Image;
    class Blob;
}

struct ConversationItem {
    Wt::WDateTime timestamp;
    bool selfSend;
    Wt::WString text;
};

class Client {
public:
    std::string gender;
    std::string country;
    std::string isoCountryCode;
    std::string userName;
    int age;
    std::unordered_map<std::string, std::vector<ConversationItem> > conversations;
    void setActivity();
    bool activitiesTimedOut();
    int currentlyLoggedInSeconds();
    virtual void incomingBroadcast() { std::cout << "incoming" << std::endl;};
    Wt::Json::Object json();
    int remainingSecondsToTimeout();
protected:
    void setLoggedIn();
    void setLoggedOut();
private:
    const int timeoutSeconds {1800};
    Wt::WDateTime lastActivity_;
    Wt::WDateTime loginTimeStamp_;
};

class Broadcast {
public:
    struct Message {
        Message()= default;
        Message(std::string fromSessionId_, Wt::WString message_);
        Message(std::string fromSessionId_, std::shared_ptr<Magick::Blob> image_);
        Message(Wt::WString message_);
        bool systemMessage{false};
        std::string fromSessionId;
        std::string sendType;
        Wt::WDateTime sendTime;
        Wt::WString message;
        Wt::WString messageId;
        Wt::Json::Object image;
        Wt::Json::Object json();
        void setNewSesionId(std::string oldSessionId, std::string newSessionId);
    };
    struct MessageQueue {
        bool user1Read{false};
        bool user2Read{false};
        std::vector<Message> messages;
        void setNewSesionId(std::string oldSessionId, std::string newSessionId);
    };
    Broadcast(Wt::WServer *server);
    ~Broadcast();
    void connect(Client *client, const std::function<void ()> &fct);
    void disconnect(Client *client);
    void addToDisconnectList(Client *client);
    Wt::Json::Object reSetUser(std::string oldSessionId, std::string newSessionId);
    int count() const;
    std::string userNameForSessionId(std::string sessionId);
    std::string sessionIdForUserName(std::string userName);
    Wt::Json::Object userForSessionId(std::string sessionId);
    Wt::Json::Object userForUserName(std::string userName);
    void changeClientForSessionId(std::string sessionId, Client *client);
    bool nameIsFree(std::string userNameToCheck);
    void downloadCountries();
    std::map<Wt::WString, Wt::WString> countries();
    Wt::WString getCountryIsoCodeByCountry(Wt::WString country);
    std::list<Wt::Json::Object> getBroadcastsForSession(std::string sessionId);
    Wt::Json::Object addMessage(std::string fromSessionId, std::string toUserName, Message message);
    Wt::Json::Object addImage(std::string fromSessionId, std::string toUserName, std::shared_ptr<Magick::Blob> image);
    void setConversationRead(std::string readingUserSessionId, std::string secondUserName);
    void sendConversation(std::string sessionId1, std::string userName2);
    void sendOpenConversations(std::string forSessionId);
    void sendUserInformation(std::string sendToSessionId, std::string userName, std::string requestingUserName);
    void toggleBlockUser(std::string blockingUserName, std::string blockedUser, std::string blockingUserSessionId);
    void requestConversation(std::string sendToSessionId, std::string withUserName, std::string requestingUserName);
    void userSearch(std::string toSession, std::string nameIncludes, int minAge, int maxAge,
                                             std::unordered_set<std::string> countries, std::unordered_set<std::string> genders, std::string excludeName);
    void sendHistory(std::string toSession);
protected:
    struct Connection {
        Connection(const std::string &id, Client *client, const std::function<void ()> &fct);
    public:
        Client *client() const;
        std::string sessionId() const;
        std::function<void ()> fct();
        std::string userName() const;
        std::string country() const;
        int age() const;
        std::string gender() const;
        void setClient(Client *client);
        void addBroadcast(Wt::Json::Object broadcast);
        std::list<Wt::Json::Object> getBroadcasts(bool clear = false);
        std::unordered_map<std::string, std::vector<std::string> > blockings_;
        bool setSessionId(std::string searchedSessionId, std::string newSessionId);
    private:
        std::string sessionId_;
        Client *client_;
        std::function<void()> fct_;
        int age_;
        std::list<Wt::Json::Object> broadcastFifo;
    };
private:
    Wt::WServer *wtServer_;
    bool stop_{false};
    mutable std::mutex mutex_;
    std::thread thread_;
    bool stop;
    std::vector<std::unique_ptr<Connection> > connections_;
    Wt::WDate lastCountriesDownload_;
    Wt::WString countriesDownloadUrl_{"https://pkgstore.datahub.io/core/country-list/data_csv/data/d7c9d7cfb42cb69f4422dec222dbbaa8/data_csv.csv"};
    std::map<Wt::WString, Wt::WString> countriesMap_;
    std::string responseData_;
    Wt::WDateTime lastTimeoutCheck_;
    std::unordered_map<std::string, MessageQueue> conversations_;
    std::unordered_map<std::string, std::set<std::string> > blockings_;
    std::vector<Client *> toDisconnect_;
    void run();
    Wt::Json::Object logoutBroadcast();
    static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output);
    bool parseCountriesData();
    void reSetSessionIdInMessages(std::string oldSessionId, std::string newSessionId);
    Wt::Json::Object createUserList();
    void sendMessageQueueToSession(std::string receiverSessionId, std::string user1, std::string user2, std::vector<Message> messages);
    void addMessageToSessionBroadcast(std::string sessionId, Wt::Json::Object message);
    void sendMessageCount(std::string toSessionId);
    void sendBlockedMessage(std::string sessionId, std::string toUserName);
    void sendBlockDone(std::string sessionId, std::string toUserName);
    void sendUnblockDone(std::string sessionId, std::string toUserName);
    void checkAndLogStart();
    void logClientLogin(const Wt::Json::Object &clientJson);
};

#endif // BROADCAST_H
