#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <patty/ax25.h>

static int init_tnc(patty_ax25_if *iface, const char *device) {
    static char *prefix = "kiss";
    static int number = 0;

    if ((iface->tnc = patty_kiss_tnc_open(device)) == NULL) {
        goto error_kiss_tnc_open;
    }

    iface->type = PATTY_AX25_IF_KISS_TNC;

    snprintf(iface->name, sizeof(iface->name), "%s%d", prefix, number++);

    return 0;

error_kiss_tnc_open:
    return -1;
}

static void close_tnc(patty_ax25_if *iface) {
    patty_kiss_tnc_close(iface->tnc);
}

patty_ax25_if *patty_ax25_if_new(int opts, void *info) {
    patty_ax25_if *iface;

    if ((iface = malloc(sizeof(*iface))) == NULL) {
        goto error_malloc_iface;
    }

    memset(iface, '\0', sizeof(*iface));

    if ((iface->rx_buf = malloc(PATTY_AX25_IF_BUFSZ)) == NULL) {
        goto error_malloc_rx_buf;
    } else {
        iface->rx_bufsz = PATTY_AX25_IF_BUFSZ;
    }

    if ((iface->tx_buf = malloc(PATTY_AX25_IF_BUFSZ)) == NULL) {
        goto error_malloc_tx_buf;
    } else {
        iface->tx_bufsz = PATTY_AX25_IF_BUFSZ;
    }

    if ((iface->addrs = patty_list_new()) == NULL) {
        goto error_list_new;
    }

    switch (PATTY_AX25_IF_OPT_TYPE(opts)) {
        case PATTY_AX25_IF_KISS_TNC:
            if (init_tnc(iface, (const char *)info) < 0) {
                goto error_init;
            }
            
            break;

        default:
            errno = EINVAL;

            goto error_invalid_if_type;
    }

    return iface;

error_invalid_if_type:
error_init:
    patty_list_destroy(iface->addrs);

error_list_new:
    free(iface->tx_buf);

error_malloc_tx_buf:
    free(iface->rx_buf);

error_malloc_rx_buf:
    free(iface);

error_malloc_iface:
    return NULL;
}

void patty_ax25_if_destroy(patty_ax25_if *iface) {
    switch (iface->type) {
        case PATTY_AX25_IF_KISS_TNC:
            close_tnc(iface);
            break;

        default:
            break;
    }

    patty_list_destroy(iface->addrs);

    free(iface->tx_buf);
    free(iface->rx_buf);
    free(iface);
}

int patty_ax25_if_addr_each(patty_ax25_if *iface,
                            int (*callback)(char *, uint8_t, void *), void *ctx) {
    patty_list_iterator *iter;
    patty_ax25_addr *addr;

    if ((iter = patty_list_start(iface->addrs)) == NULL) {
        goto error_list_start;
    }

    while ((addr = patty_list_next(iter)) != NULL) {
        char buf[7];
        uint8_t ssid;

        if (patty_ax25_ntop(addr, buf, &ssid, sizeof(buf)) < 0) {
            goto error_ntop;
        }

        if (callback(buf, ssid, ctx) < 0) {
            goto error_callback;
        }
    }

    patty_list_finish(iter);

    return 0;

error_callback:
error_ntop:
    patty_list_finish(iter);

error_list_start:
    return -1;
}

static patty_ax25_addr *find_addr(patty_ax25_if *iface,
                                  const char *callsign,
                                  uint8_t ssid) {
    patty_list_iterator *iter;
    patty_ax25_addr *addr;

    if ((iter = patty_list_start(iface->addrs)) == NULL) {
        goto error_list_start;
    }

    while ((addr = patty_list_next(iter)) != NULL) {
        char buf[7];
        uint8_t addr_ssid;

        if (patty_ax25_ntop(addr, buf, &addr_ssid, sizeof(buf)) < 0) {
            goto error_ntop;
        }

        if (strncmp(buf, callsign, sizeof(buf)) != 0) {
            continue;
        }

        
        if (addr_ssid != ssid) {
            continue;
        }

        patty_list_finish(iter);

        return addr;
    }

error_ntop:
    patty_list_finish(iter);

error_list_start:
    return NULL;
}

int patty_ax25_if_addr_add(patty_ax25_if *iface,
                           const char *callsign,
                           uint8_t ssid) {
    patty_ax25_addr *addr;

    if (find_addr(iface, callsign, ssid) != NULL) {
        errno = EEXIST;

        goto error_exists;
    }

    if ((addr = malloc(sizeof(*addr))) == NULL) {
        goto error_malloc_addr;
    }

    if (patty_ax25_pton(callsign, ssid, addr) < 0) {
        goto error_pton;
    }

    if ((patty_list_append(iface->addrs, addr)) == NULL) {
        goto error_list_append;
    }

    return 0;

error_list_append:
error_pton:
    free(addr);

error_malloc_addr:
error_exists:
    return -1;
}

int patty_ax25_if_addr_delete(patty_ax25_if *iface,
                              const char *callsign,
                              uint8_t ssid) {
    patty_list_item *item = iface->addrs->first;
    int i = 0;

    while (item) {
        char buf[7];
        uint8_t addr_ssid;

        patty_ax25_addr *addr = item->value;

        if (patty_ax25_ntop(addr, buf, &addr_ssid, sizeof(buf)) < 0) {
            goto error_ntop;
        }

        if (strncmp(buf, callsign, sizeof(buf)) == 0 && addr_ssid == ssid) {
            if (patty_list_splice(iface->addrs, i) == NULL) {
                goto error_list_splice;
            }
        }

        item = item->next;
        i++;
    }

    return 0;

error_list_splice:
error_ntop:
    return -1;
}

ssize_t patty_ax25_if_recv(patty_ax25_if *iface,
                           void **buf) {
    ssize_t readlen;
    int port;

    if ((readlen = patty_kiss_tnc_recv(iface->tnc,
                                       iface->rx_buf,
                                       iface->rx_bufsz,
                                       &port)) < 0) {
        goto error_kiss_tnc_recv;
    }

    *buf = iface->rx_buf;

    return readlen;

error_kiss_tnc_recv:
    return -1;
}

ssize_t patty_ax25_if_send(patty_ax25_if *iface,
                           const void *buf,
                           size_t len) {
    ssize_t wrlen;

    if ((wrlen = patty_kiss_tnc_send(iface->tnc, buf, len, 0)) < 0) {
        goto error_kiss_tnc_send;
    }

    return wrlen;

error_kiss_tnc_send:
    return -1;
}