/*
 * © Copyright 2024 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"
#include "mars_client_config.h"

typedef struct chunked {
    chunked *next;
    int fd;
    int chunk_count;
    int write_count;
    int rewind_count;
    int error_count;
    int closed;
} chunked;


static chunked *current_chunked = NULL;

static err _chunked_write(chunked* c, const void* buffer, long length) {
// https://en.wikipedia.org/wiki/Chunked_transfer_encoding
// https://tools.ietf.org/html/rfc7230#section-4.1
    char header[32] = {0,};
    int header_length = snprintf(header, sizeof(header), "%lX;rewind=%d;error=%d\r\n", length, c->rewind_count, c->error_count);

    c->chunk_count++;
    // marslog(LOG_INFO, "chunked_write %ld bytes chunk=%d", length, c->chunk_count);

    int written = write(c->fd, header, header_length);
    if(written != header_length) {
        marslog(LOG_EROR|LOG_PERR, "Failed to write chunked header %d/%d", written, header_length);
        return -2;
    }

    if(length > 0) {
        written = write(c->fd, buffer, length);
        if(written != length) {
            marslog(LOG_EROR|LOG_PERR, "Failed to write chunked buffer %d/%ld", written, length);
            return -2;
        }
    }
    if(write(c->fd, "\r\n", 2) != 2) {
        marslog(LOG_EROR|LOG_PERR, "Failed to write chunked newline");
        return -2;
    }
    // Reset counters
    c->rewind_count = c->error_count = 0;
    return 0;
}

chunked *new_chunked(int fd) {

    chunked *c = current_chunked;
    while(c) {
        if(c->fd == fd) {
            return c;
        }
        c = c->next;
    }

    c = NEW_CLEAR(chunked);
    c->fd = fd;
    c->next = current_chunked;
    current_chunked = c;

    return c;
}

static err free_chunked(chunked* c, int send_endr) {
    err e = NOERR;
    // marslog(LOG_INFO, "free_chunked");
    if(send_endr) {
        if(_chunked_write(c, "ENDR", 4)) {e=-2;}
        if(_chunked_write(c, "", 0)) {e=-2;}
    }
    FREE(c);
    return e;
}

err chunked_write(chunked* c, const void* buffer, long length) {
    if(length == 0) {
        return 0;
    }
    c->write_count++;
    return _chunked_write(c, buffer, length);
}

err chunked_rewind(chunked* c) {
    c->rewind_count++;
    marslog(LOG_INFO, "chunked_rewind");
    return _chunked_write(c, "RWND", 4);
}

void chunked_error(chunked* c, int code) {
    c->error_count++;
    marslog(LOG_INFO, "chunked_error");
    _chunked_write(c, "EROR", 4);
}

void chunked_close(chunked* c) {
    marslog(LOG_INFO, "chunked_close");

    if(c->closed) {
        marslog(LOG_WARN, "chunked_close(%d) already closed", c->fd);
        return;
    }
    c->closed = 1;

}

err flush_chunked_targets() {
    chunked *c = current_chunked;
    err e = NOERR;
    while(c) {
        current_chunked = c->next;
         if(!c->closed) {
            marslog(LOG_WARN, "flush_chunked_targets(%d) not closed", c->fd);
            e = -2;
        }
        if(free_chunked(c, c->closed)) {
            e = -2;
        }
        c = current_chunked;
    }

    return e;

}
