#include "common.h"

ssize_t readn(int fd, void *vptr, size_t n) {
    size_t nleft;
    ssize_t nread;
    char *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR) {
                nread = 0;      /* call read() again */
            } else {
                return -1;
            }
        } else if (nread == 0) {
            break;              /* EOF */
        }
        nleft -= nread;
        ptr   += nread;
    }
    return n - nleft;
}

ssize_t writen(int fd, const void *vptr, size_t n) {
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;   /* and call write() again */
            else
                return -1;     /* error */
        }
        nleft  -= nwritten;
        ptr    += nwritten;
    }
    return n;
}

// ─── Simple path creation ───────────────────────────────────────────────
int make_path(const char *path, mode_t mode) {
    char tmp[2048], *p = NULL;
    size_t len;

    snprintf(tmp, sizeof(tmp), "%s", path);
    len = strlen(tmp);
    if (tmp[len - 1] == '/')
        tmp[len - 1] = '\0';
    for (p = tmp + 1; *p; p++)
        if (*p == '/') {
            *p = '\0';
            if (mkdir(tmp, mode) == -1 && errno != EEXIST)
                return -1;
            *p = '/';
        }
    if (mkdir(tmp, mode) == -1 && errno != EEXIST)
        return -1;
    return 0;
}

// ─── Work-Queue (client-side, thread-safe) ───────────────────────────────────

void wq_init(work_queue_t *q) {
    q->head  = NULL;
    q->tail  = NULL;
    q->done  = 0;
    q->error = 0;
    pthread_mutex_init(&q->lock, NULL);
    pthread_cond_init(&q->cond, NULL);
}

void wq_push(work_queue_t *q, const char *full_path, const char *rel_path, uint64_t file_size, uint64_t checksum) {
    file_task_t *t = malloc(sizeof(file_task_t));
    if (!t) { perror("wq_push malloc"); return; }
    snprintf(t->full_path, sizeof(t->full_path), "%s", full_path);
    snprintf(t->rel_path,  sizeof(t->rel_path),  "%s", rel_path);
    t->file_size = file_size;
    t->checksum = checksum;
    t->next = NULL;

    pthread_mutex_lock(&q->lock);
    if (q->tail) {
        q->tail->next = t;
    } else {
        q->head = t;
    }
    q->tail = t;
    pthread_cond_signal(&q->cond);
    pthread_mutex_unlock(&q->lock);
}

/* Returns NULL when queue is empty AND done=1 (no more work ever). */
file_task_t *wq_pop(work_queue_t *q) {
    pthread_mutex_lock(&q->lock);
    while (q->head == NULL && !q->done) {
        pthread_cond_wait(&q->cond, &q->lock);
    }
    file_task_t *t = q->head;
    if (t) {
        q->head = t->next;
        if (!q->head) q->tail = NULL;
    }
    pthread_mutex_unlock(&q->lock);
    return t;   /* NULL means done */
}

void wq_finish(work_queue_t *q) {
    pthread_mutex_lock(&q->lock);
    q->done = 1;
    pthread_cond_broadcast(&q->cond);   /* wake ALL waiting workers */
    pthread_mutex_unlock(&q->lock);
}

void wq_destroy(work_queue_t *q) {
    /* drain any leftover tasks */
    file_task_t *t = q->head;
    while (t) {
        file_task_t *next = t->next;
        free(t);
        t = next;
    }
    pthread_mutex_destroy(&q->lock);
    pthread_cond_destroy(&q->cond);
}
