//
// Created by WolverinDEV on 28/04/2020.
//

#include <netinet/in.h>
#include <log/LogUtils.h>
#include "LocalFileProvider.h"

using namespace ts::server;
using LocalFileServer = file::LocalFileProvider;
using LocalVirtualFileServer = file::LocalVirtualFileServer;

std::shared_ptr<LocalFileServer> server_instance{};
bool file::initialize(std::string &error, const std::string& hostnames, uint16_t port) {
    server_instance = std::make_shared<LocalFileProvider>();

    if(!server_instance->initialize(error)) {
        server_instance = nullptr;
        return false;
    }

    bool any_bind{false};
    for(const auto& binding : net::resolve_bindings(hostnames, port)) {
        if(!get<2>(binding).empty()) {
            logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
            continue;
        }

        auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ get<0>(binding), get<1>(binding) });
        switch (result) {
            case transfer::NetworkingBindResult::SUCCESS:
                any_bind = true;
                break;

            case transfer::NetworkingBindResult::OUT_OF_MEMORY:
                logWarning(LOG_FT, "Failed to listen to address {}: Out of memory", get<0>(binding));
                continue;

            case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
                logWarning(LOG_FT, "Failed to listen on {}: {}/{}", get<0>(binding), errno, strerror(errno));
                continue;

            case transfer::NetworkingBindResult::FAILED_TO_BIND:
                logWarning(LOG_FT, "Failed to bind on {}: {}/{}", get<0>(binding), errno, strerror(errno));
                continue;

            case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
                logWarning(LOG_FT, "Failed to bind on {}: binding already exists", get<0>(binding));
                continue;

            case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
                logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", get<0>(binding));
                continue;

            case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
                logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", get<0>(binding), errno, strerror(errno));
                continue;
        }
    }
#if 0
    {
        auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].as<string>();
        auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as<uint16_t>();
        auto query_bindings = net::resolve_bindings(query_bindings_string, query_port);
        deque<shared_ptr<QueryServer::Binding>> bindings;

        for(auto& binding : query_bindings) {
            if(!get<2>(binding).empty()) {
                logError(LOG_QUERY, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
                continue;
            }
            auto entry = make_shared<QueryServer::Binding>();
            memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));

            entry->file_descriptor = -1;
            entry->event_accept = nullptr;
            bindings.push_back(entry);
        }

        logMessage(LOG_QUERY, "Starting server on {}:{}", query_bindings_string, query_port);
        if(!queryServer->start(bindings, errorMessage)) {
            logCritical(LOG_QUERY, "Failed to start query server: {}", errorMessage);
            return false;
        }
    }
#endif

    return true;
}

void file::finalize() {
    auto server = std::exchange(server_instance, nullptr);
    if(!server) return;

    server->finalize();
}

std::shared_ptr<file::AbstractFileServer> file::server() {
    return server_instance;
}

LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
LocalFileServer::~LocalFileProvider() = default;

bool LocalFileServer::initialize(std::string &error) {
    if(!this->file_system_.initialize(error, "files/"))
        return false;

    if(!this->file_transfer_.start()) {
        error = "transfer server startup failed";
        this->file_system_.finalize();
        return false;
    }
    return true;
}

void LocalFileServer::finalize() {
    this->file_transfer_.stop();
    this->file_system_.finalize();
}

file::filesystem::AbstractProvider &LocalFileServer::file_system() {
    return this->file_system_;
}

file::transfer::AbstractProvider &LocalFileServer::file_transfer() {
    return this->file_transfer_;
}

std::string file::LocalFileProvider::file_base_path() const {
    return this->file_system_.root_path();
}

std::shared_ptr<file::VirtualFileServer> LocalFileServer::register_server(ServerId server_id) {
    auto server = this->find_virtual_server(server_id);
    if(server) return server;

    server = std::make_shared<file::LocalVirtualFileServer>(server_id, std::to_string(server_id));
    {
        std::lock_guard slock{this->servers_mutex};
        this->servers_.push_back(server);
    }

    return server;
}

void LocalFileServer::unregister_server(ServerId server_id) {
    auto server_unique_id = std::to_string(server_id);

    std::lock_guard slock{this->servers_mutex};
    auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
        return server->unique_id() == server_unique_id;
    });

    if(it == this->servers_.end()) return;
    this->servers_.erase(it);
}

void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
    VirtualFileServer::max_networking_upload_bandwidth(value);
    this->upload_throttle.set_max_bandwidth(value);
}

void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
    VirtualFileServer::max_networking_download_bandwidth(value);
    this->download_throttle.set_max_bandwidth(value);
}