#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <variant>
#include <vector>

struct Credentail {
    int cert_;
};

struct Download {
    enum ConnectionType { Telnet,
                          Ssh };

    std::string url_;
    Credentail credentail_;
    int maxMbps_;
    bool cache_;
    ConnectionType type_;
};

struct Upload {
    enum ConnectionType { Telnet,
                          Ssh };

    std::string url_;
    Credentail credentail_;
    int maxMbps_;
    int size_;
    ConnectionType type_;
};

struct RemoveFromCache {
    std::string url_;
};

struct ClearCache {
};

struct Image {
    std::vector<uint8_t> bitmap_;
};

class Server {
public:
    void handle(const Download& request, void (*callback)(bool, Image)) {
        Image image;

        for (const auto& pair : cache_) {
            if (pair.second == request.url_) {
                image = pair.first;
                callback(true, image);
                return;
            }
        }

        if (download(request, &image)) {
            if (request.cache_) {
                cache_.push_back(std::pair<Image, std::string>(image, request.url_));
            }

            callback(true, image);
            return;
        }

        callback(false, Image{});
    }

    void handle(const Upload& request, void (*callback)(bool, Image)) {
        if (request.size_ > 100) {
            callback(false, Image{});
            return;
        }

        Image image;
        bool succes = upload(request, &image);

        callback(succes, Image{});
    }

    void handle(const RemoveFromCache& request, void (*callback)(bool, Image)) {
        for (auto it = cache_.begin(); it != cache_.end(); ++it) {
            if (it->second == request.url_) {
                cache_.erase(it);
                callback(true, Image{});
                return;
            }
        }

        callback(false, Image{});
    }

    void handle(const ClearCache& request, void (*callback)(bool, Image)) {
        cache_.clear();

        callback(true, Image{});
    }

private:
    bool download(const Download& request, Image* image) {
        if (request.type_ == Download::Ssh && request.credentail_.cert_ != 123) {
            return false;
        }
        if (request.type_ == Download::Telnet && request.credentail_.cert_ != 231) {
            return false;
        }

        // Simulate some other error
        if (request.maxMbps_ % 2) {
            return false;
        }
        image->bitmap_ = {97, 98, 99, 100, 101, 102};
        return true;
    }

    bool upload(const Upload& request, const Image* image) {
        if (request.type_ == Upload::Ssh && request.credentail_.cert_ != 1234) {
            return false;
        }
        if (request.type_ == Upload::Telnet && request.credentail_.cert_ != 5432) {
            return false;
        }

        // Simulate some other error
        if (!request.maxMbps_ % 2) {
            return false;
        }
        return true;
    }

    std::vector<std::pair<Image, std::string>> cache_;
};

class RequestHandler {
public:
    enum RequestType { Download,
                       Upload,
                       Remove,
                       Clear };

    void start(Server* server) {
        std::thread(&RequestHandler::run, this, server).detach();
    }

    void stop() {
        stop_ = true;
    }

    void pushRequest(void* request, RequestType type, void (*callback)(bool, Image)) {
        requests_.push(std::tuple<void*, RequestType, void (*)(bool, Image)>(request, type, callback));
    }

private:
    void run(Server* server) {
        while (!stop_) {
            auto tuple = waitForRequest();
            switch (std::get<1>(tuple)) {
            case Download:
                server->handle(*((::Download*)(std::get<0>(tuple))), std::get<2>(tuple));
                break;
            case Upload:
                server->handle(*((::Upload*)(std::get<0>(tuple))), std::get<2>(tuple));
                break;
            case Remove:
                server->handle(*((RemoveFromCache*)(std::get<0>(tuple))), std::get<2>(tuple));
                break;
            case Clear:
                server->handle(*((ClearCache*)(std::get<0>(tuple))), std::get<2>(tuple));
                break;
            }
        }
    }

    std::tuple<void*, RequestType, void (*)(bool, Image)> waitForRequest() {
        while (requests_.empty() || !stop_) {
        }

        auto pair = requests_.front();
        requests_.pop();

        return pair;
    }

    bool stop_;
    std::queue<std::tuple<void*, RequestType, void (*)(bool, Image)>> requests_;
};

int main() {
    Server server;
    RequestHandler handler;

    handler.start(&server);

    Download d{"sth.png", 123, 100, true, Download::Ssh};
    handler.pushRequest((void*)(&d),
                        RequestHandler::Download,
                        [](bool succes, Image image) {
                            if (succes) {
                                for (auto el : image.bitmap_) {
                                    std::cout << el << ' ';
                                }
                                std::cout << '\n';
                            } else {
                                std::cout << "FAILED!\n";
                            }
                        });

    std::this_thread::sleep_for(std::chrono::seconds(1));
    handler.stop();
}
