#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "b_header.h"
#include "b_stack.h"
#include "b_path.h"
#include "b_util.h"

static inline uint64_t checksum(b_header_block *block) {
    uint64_t sum = 0;
    int i;

    for (i=0; i<B_HEADER_SIZE; i++) {
        sum += ((uint8_t *)(block))[i];
    }

    return sum;
}

static inline int is_big_endian() {
    uint16_t num = 1;

    return ((uint8_t *)&num)[1];
}

static inline void encode_base256_value(unsigned char *field, size_t len, uint64_t value) {
    size_t i;
    size_t value_size = sizeof(value);
    size_t offset     = len - value_size;

    for (i=0; i<value_size; i++) {
        int from_i = is_big_endian()? i: value_size - i - 1;

        field[offset + i] = ((uint8_t *)&value)[from_i];
    }

    /*
     * Set the uppermost bit to indicate a base256-encoded size value.
     */
    field[0] |= 0x80;
}

static inline void encode_checksum(b_header_block *block) {
    memcpy(  block->checksum, B_HEADER_EMPTY_CHECKSUM, B_HEADER_CHECKSUM_SIZE);
    snprintf(block->checksum, B_HEADER_CHECKSUM_LEN,   B_HEADER_CHECKSUM_FORMAT, checksum(block));

    block->checksum[7] = ' ';
}

size_t b_header_compute_pax_length(b_string *path, const char *record) {
    size_t len, i;
    char shortbuf[32];

    len = b_string_len(path);

    /* snprintf returns the number of characters (excluding the NUL) we would
     * have written had space been available.  Iterate three times to be sure
     * the value is stable.
     */
    for (i=0; i<3; i++) {
        len = snprintf(shortbuf, sizeof(shortbuf), "%d %s=%s\n", len, record, path->str);
    }

    return len;
}


b_header_block *b_header_encode_block(b_header_block *block, b_header *header) {
    if (header->suffix) {
        strncpy(block->suffix, header->suffix->str, 100);
    }

    snprintf(block->mode, B_HEADER_MODE_SIZE, B_HEADER_MODE_FORMAT, header->mode & S_IPERM);
    snprintf(block->uid,  B_HEADER_UID_SIZE,  B_HEADER_UID_FORMAT,  header->uid);
    snprintf(block->gid,  B_HEADER_GID_SIZE,  B_HEADER_GID_FORMAT,  header->gid);

    if (header->size >= B_HEADER_MAX_FILE_SIZE) {
        encode_base256_value(block->size, B_HEADER_SIZE_SIZE, header->size);
    } else {
        snprintf(block->size, B_HEADER_SIZE_SIZE, B_HEADER_LONG_SIZE_FORMAT, header->size);
    }

    snprintf(block->mtime, B_HEADER_MTIME_SIZE, B_HEADER_MTIME_FORMAT, header->mtime);

    block->linktype = header->linktype;

    if (header->linkdest != NULL) {
        strncpy(block->linkdest, header->linkdest->str, B_HEADER_LINKDEST_SIZE);
    }

    memcpy(block->magic, B_HEADER_MAGIC, B_HEADER_MAGIC_SIZE);

    if (header->user != NULL) {
        strncpy(block->user, header->user->str, B_HEADER_USER_SIZE);
    }

    if (header->group != NULL) {
        strncpy(block->group, header->group->str, B_HEADER_GROUP_SIZE);
    }

    if (header->major && header->minor) {
        snprintf(block->major, B_HEADER_MAJOR_SIZE, B_HEADER_MAJOR_FORMAT, header->major);
        snprintf(block->minor, B_HEADER_MINOR_SIZE, B_HEADER_MINOR_FORMAT, header->minor);
    }

    if (header->prefix) {
        strncpy(block->prefix, header->prefix->str, B_HEADER_PREFIX_SIZE);
    }

    encode_checksum(block);

    return block;
}

b_header_block *b_header_encode_longlink_block(b_header_block *block, b_string *path, int type) {
    memcpy(  block->magic,  B_HEADER_MAGIC,       B_HEADER_MAGIC_SIZE);
    snprintf(block->suffix, B_HEADER_SUFFIX_SIZE, B_HEADER_LONGLINK_PATH);
    snprintf(block->size,   B_HEADER_SIZE_SIZE,   B_HEADER_INT_SIZE_FORMAT,   b_string_len(path));

    block->linktype = type;

    encode_checksum(block);

    return block;
}

b_header_block *b_header_encode_pax_block(b_header_block *block, b_header *header, b_string *path) {
    size_t pax_len = b_header_compute_pax_length(path, "path");

    if (header->linkdest)
        pax_len += b_header_compute_pax_length(header->linkdest, "linkpath");

	b_header_encode_block(block, header);

    snprintf(block->size, B_HEADER_SIZE_SIZE, B_HEADER_LONG_SIZE_FORMAT, (unsigned long long)pax_len);

	memset(block->prefix, 0, sizeof(block->prefix));
	snprintf(block->prefix, sizeof(block->prefix), "./PaxHeaders.%d", getpid());
    block->linktype = B_HEADER_PAX_TYPE;

    encode_checksum(block);

    return block;
}

int b_header_set_usernames(b_header *header, b_string *user, b_string *group) {
    header->user  = user;
    header->group = group;

    return 0;
}

void b_header_destroy(b_header *header) {
    if (header == NULL) return;

    if (header->prefix != NULL) {
        b_string_free(header->prefix);
    }

    if (header->suffix != NULL) {
        b_string_free(header->suffix);
    }

    if (header->linkdest != NULL) {
        b_string_free(header->linkdest);
    }

    if (header->user != NULL) {
        b_string_free(header->user);
    }

    if (header->group != NULL) {
        b_string_free(header->group);
    }

    header->prefix   = NULL;
    header->suffix   = NULL;
    header->linkdest = NULL;
    header->user     = NULL;
    header->group    = NULL;

    free(header);
}