// sb-send: Sonobus audio sender

// Add handle and remove peers as they join and leave the
// Sonobus connections server.

#include "sb-send.h"

void peer_list_init(PeerList *list) {
    list->capacity = MAX_PEERS;
    list->count = 0;
    list->items = calloc(list->capacity, sizeof(Peer));
}

void peer_list_destroy(PeerList *list) {
    free(list->items);
    list->items = NULL;
    list->count = 0;
    list->capacity = 0;
}

void peer_list_compact(PeerList *list) {
    int write = 0;
    for (int read = 0; read < list->count; read++) {
        if (list->items[read].active) {
            if (write != read) {
                list->items[write] = list->items[read];
            }
            write++;
        }
    }
    list->count = write;
}

Peer *find_peer_by_addr_locked(AppState *state, struct sockaddr_in *addr) {
    SB_ASSERT(state->peers.count >= 0);
    SB_ASSERT(state->peers.items != NULL);
    for (int i = 0; i < state->peers.count; i++) {
        if (!state->peers.items[i].active) {
            continue;
        }
        if (state->peers.items[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr &&
            state->peers.items[i].addr.sin_port == addr->sin_port) {
            return &state->peers.items[i];
        }
    }
    return NULL;
}

Peer *add_peer_locked(AppState *state, const char *name, const char *ip,
                      int port, int64_t token) {
    SB_ASSERT(name != NULL && name[0] != '\0');
    SB_ASSERT(ip != NULL && ip[0] != '\0');
    SB_ASSERT(port > 0 && port < 65536);
    SB_ASSERT(token != 0);
    if (token == state->client_token) {
        return NULL;
    }
    for (int i = 0; i < state->peers.count; i++) {
        if (state->peers.items[i].active && state->peers.items[i].token == token) {
            return &state->peers.items[i];
        }
    }
    int idx = -1;
    for (int i = 0; i < state->peers.count; i++) {
        if (!state->peers.items[i].active) { idx = i; break; }
    }
    if (idx < 0) {
        if (state->peers.count >= MAX_PEERS) {
            fprintf(stderr, "Peer list full (%d), ignoring %s\n", MAX_PEERS, name);
            return NULL;
        }
        idx = state->peers.count++;
    }
    Peer *p = &state->peers.items[idx];
    memset(p, 0, sizeof(Peer));
    strncpy(p->name, name, sizeof(p->name) - 1);
    p->token = token;
    p->our_source_id = ++state->next_sink_id;
    p->active = true;
    p->sequence = 0;
    memset(&p->addr, 0, sizeof(p->addr));
    p->addr.sin_family = AF_INET;
    p->addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &p->addr.sin_addr);
    return p;
}

void remove_peer_locked(AppState *state, const char *name) {
    for (int i = 0; i < state->peers.count; i++) {
        if (state->peers.items[i].active && strcmp(state->peers.items[i].name, name) == 0) {
            state->peers.items[i].active = false;
            fprintf(stderr, "Removed peer: %s\n", name);
            peer_list_compact(&state->peers);
            return;
        }
    }
}

int32_t generate_salt(Peer *p) {
    return (int32_t)(time(NULL) ^ (p->token & 0xFFFFFFFF) ^ rand());
}

void send_invite_to_peer(AppState *state, const Peer *p) {
    unsigned char buf[256];
    int len = build_peer_ping_msg(state->client_token, buf);
    sendto(state->udp_sock, (const char*)buf, len, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
    len = build_invite_msg(p->our_source_id, buf);
    sendto(state->udp_sock, (const char*)buf, len, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
}

void send_format_to_peer(AppState *state, const Peer *p) {
    if (p->remote_sink_id == 0 || p->salt == 0) {
        return;
    }
    unsigned char buf[512];
    int len = build_format_msg(state, p->remote_sink_id, p->our_source_id,
                               p->salt, buf);
    sendto(state->udp_sock, (const char*)buf, len, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
    fprintf(stderr, "Sent format to %s (sink=%d src=%d salt=%d)\n",
           p->name, p->remote_sink_id, p->our_source_id, p->salt);
}

void send_source_ping(AppState *state, const Peer *p) {
    if (p->remote_sink_id == 0 || p->our_source_id == 0) {
        return;
    }
    unsigned char buf[128];
    int len = build_source_ping_msg(p->remote_sink_id, p->our_source_id, buf);
    sendto(state->udp_sock, (const char*)buf, len, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
}

void send_uninvite_to_peer(AppState *state, const Peer *p) {
    if (p->remote_source_id == 0 || p->our_source_id == 0) {
        return;
    }
    unsigned char buf[128];
    int len = build_uninvite_msg(p->remote_source_id, p->our_source_id, buf);
    sendto(state->udp_sock, (const char*)buf, len, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
    fprintf(stderr, "sb-send: uninviting peer %s (remote_source_id=%d)\n",
            p->name, p->remote_source_id);
}
// EOF
