/*
 * Written in 2009 by Lloyd Hilaiel
 *
 * License
 * 
 * All the cruft you find here is public domain.  You don't have to credit
 * anyone to use this code, but my personal request is that you mention
 * Igor Pavlov for his hard, high quality work.
 */

/* XXX: clean this up, it's mostly lifted from pavel */

#include "lzma_header.h"

#include <string.h>
#include <assert.h>

#define ELZMA_LZMA_HEADER_SIZE 13
#define ELZMA_LZMA_PROPSBUF_SIZE 5

/****************
  Header parsing
 ****************/

#ifndef UINT64_MAX
#define UINT64_MAX ((unsigned long long) -1)
#endif

/* Parse the properties byte */
static char
lzmadec_header_properties (
	unsigned char *pb, unsigned char *lp, unsigned char *lc, const unsigned char c)
{
/*	 pb, lp and lc are encoded into a single byte.  */
	if (c > (9 * 5 * 5))
		return -1;
	*pb = c / (9 * 5);        /* 0 <= pb <= 4 */
    *lp = (c % (9 * 5)) / 9;  /* 0 <= lp <= 4 */
    *lc = c % 9;              /* 0 <= lc <= 8 */
    
	assert (*pb < 5 && *lp < 5 && *lc < 9);
	return 0;
}

/* Parse the dictionary size (4 bytes, little endian) */
static char
lzmadec_header_dictionary (unsigned int *size, const unsigned char *buffer)
{
	unsigned int i;
	*size = 0;
	for (i = 0; i < 4; i++)
		*size += (unsigned int)(*buffer++) << (i * 8);
	/* The dictionary size is limited to 256 MiB (checked from
	 * LZMA SDK 4.30) */
	if (*size > (1 << 28))
		return -1;
	return 0;
}

/* Parse the uncompressed size field (8 bytes, little endian) */
static void
lzmadec_header_uncompressed (unsigned long long *size,
                             unsigned char *is_streamed,
                             const unsigned char *buffer)
{
	unsigned int i;

    /* Streamed files have all 64 bits set in the size field.
     * We don't know the uncompressed size beforehand. */
	*is_streamed = 1;  /* Assume streamed. */
	*size = 0;
	for (i = 0; i < 8; i++) {
		*size += (unsigned long long)buffer[i] << (i * 8);
		if (buffer[i] != 255)
			*is_streamed = 0;
	}
	assert ((*is_streamed == 1 && *size == UINT64_MAX)
			|| (*is_streamed == 0 && *size < UINT64_MAX));
}

static void
initLzmaHeader(struct elzma_file_header * hdr)
{
    memset((void *) hdr, 0, sizeof(struct elzma_file_header));
}

static int
parseLzmaHeader(const unsigned char * hdrBuf,
                struct elzma_file_header * hdr)
{
    if (lzmadec_header_properties(&(hdr->pb), &(hdr->lp), &(hdr->lc),
                                  *hdrBuf) ||
        lzmadec_header_dictionary(&(hdr->dictSize), hdrBuf + 1))
    {
        return 1;
    }
    lzmadec_header_uncompressed(&(hdr->uncompressedSize),
                                &(hdr->isStreamed),
                                hdrBuf + 5);
    
    return 0;
}

static int
serializeLzmaHeader(unsigned char * hdrBuf,
                    const struct elzma_file_header * hdr)
{
    unsigned int i;
    
    memset((void *) hdrBuf, 0, ELZMA_LZMA_HEADER_SIZE); 

    /* encode lc, pb, and lp */
    *hdrBuf++ = hdr->lc + (hdr->pb * 45) + (hdr->lp * 45 * 9);

    /* encode dictionary size */
    for (i = 0; i < 4; i++) {
        *(hdrBuf++) = (unsigned char) (hdr->dictSize >> (i * 8)); 
    }

    /* encode uncompressed size */
    for (i = 0; i < 8; i++) {
        if (hdr->isStreamed) {
            *(hdrBuf++) = 0xff; 
        } else {
            *(hdrBuf++) = (unsigned char) (hdr->uncompressedSize >> (i * 8)); 
        }
    }

    return 0;
}

void
initializeLZMAFormatHandler(struct elzma_format_handler * hand)
{
    hand->header_size = ELZMA_LZMA_HEADER_SIZE;
    hand->init_header = initLzmaHeader;
    hand->parse_header = parseLzmaHeader;    
    hand->serialize_header = serializeLzmaHeader;    
    hand->footer_size = 0;    
    hand->serialize_footer = NULL;
}
