/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "log.h"
#include "string-table.h"
#include "timesyncd-manager.h"
#include "timesyncd-server.h"

static const char * const server_type_table[_SERVER_TYPE_MAX] = {
        [SERVER_SYSTEM]   = "system",
        [SERVER_FALLBACK] = "fallback",
        [SERVER_LINK]     = "link",
        [SERVER_RUNTIME]  = "runtime",
};

DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(server_type, ServerType);

int server_address_new(
                ServerName *n,
                ServerAddress **ret,
                const union sockaddr_union *sockaddr,
                socklen_t socklen) {

        ServerAddress *a, *tail;

        assert(n);
        assert(sockaddr);
        assert(socklen >= offsetof(struct sockaddr, sa_data));
        assert(socklen <= sizeof(union sockaddr_union));

        a = new(ServerAddress, 1);
        if (!a)
                return -ENOMEM;

        *a = (ServerAddress) {
                .name = n,
                .socklen = socklen,
        };

        memcpy(&a->sockaddr, sockaddr, socklen);

        tail = LIST_FIND_TAIL(addresses, n->addresses);
        LIST_INSERT_AFTER(addresses, n->addresses, tail, a);

        if (ret)
                *ret = a;

        return 0;
}

ServerAddress* server_address_free(ServerAddress *a) {
        if (!a)
                return NULL;

        if (a->name) {
                LIST_REMOVE(addresses, a->name->addresses, a);

                if (a->name->manager && a->name->manager->current_server_address == a)
                        manager_set_server_address(a->name->manager, NULL);
        }

        return mfree(a);
}

static int enable_ntp_server_defer_event(Manager *m, ServerType type) {
        int r;

        assert(m);
        assert((type >= 0) && (type < _SERVER_TYPE_MAX));

        m->ntp_server_change_mask |= 1U << type;

        r = bus_manager_emit_ntp_server_changed(m);
        if (r < 0)
                return r;

        return 1;
}

int server_name_new(
                Manager *m,
                ServerName **ret,
                ServerType type,
                const char *string) {
        int r;
        ServerName *n;

        assert(m);
        assert(string);

        n = new(ServerName, 1);
        if (!n)
                return -ENOMEM;

        *n = (ServerName) {
                .manager = m,
                .type = type,
                .string = strdup(string),
        };

        if (!n->string) {
                free(n);
                return -ENOMEM;
        }

        switch (type) {
        case SERVER_SYSTEM:
                LIST_APPEND(names, m->system_servers, n);
                break;
        case SERVER_LINK:
                LIST_APPEND(names, m->link_servers, n);
                break;
        case SERVER_FALLBACK:
                LIST_APPEND(names, m->fallback_servers, n);
                break;
        case SERVER_RUNTIME:
                LIST_APPEND(names, m->runtime_servers, n);
                break;
        default:
                assert_not_reached();
        }

        r = enable_ntp_server_defer_event(m, type);
        if (r < 0)
                log_debug_errno(r, "Failed to enable ntp server defer event, ignoring: %m");

        if (type != SERVER_FALLBACK &&
            m->current_server_name &&
            m->current_server_name->type == SERVER_FALLBACK)
                manager_set_server_name(m, NULL);

        log_debug("Added new %s server %s.", server_type_to_string(type), string);

        if (ret)
                *ret = n;

        return 0;
}

ServerName *server_name_free(ServerName *n) {
        int r;

        if (!n)
                return NULL;

        server_name_flush_addresses(n);

        if (n->manager) {
                if (n->type == SERVER_SYSTEM)
                        LIST_REMOVE(names, n->manager->system_servers, n);
                else if (n->type == SERVER_LINK)
                        LIST_REMOVE(names, n->manager->link_servers, n);
                else if (n->type == SERVER_FALLBACK)
                        LIST_REMOVE(names, n->manager->fallback_servers, n);
                else if (n->type == SERVER_RUNTIME)
                        LIST_REMOVE(names, n->manager->runtime_servers, n);
                else
                        assert_not_reached();

                r = enable_ntp_server_defer_event(n->manager, n->type);
                if (r < 0)
                        log_debug_errno(r, "Failed to enable ntp server defer event, ignoring: %m");

                if (n->manager->current_server_name == n)
                        manager_set_server_name(n->manager, NULL);
        }

        log_debug("Removed server %s.", n->string);

        free(n->string);
        return mfree(n);
}

void server_name_flush_addresses(ServerName *n) {
        assert(n);

        while (n->addresses)
                server_address_free(n->addresses);
}
