/* vim: set ts=8 sts=4 sw=4 tw=80 noet: */
/*======================================================================
Copyright (C) 2004,2005,2009,2011 Walter Doekes <walter+tthsum@wjd.nu>
This file is part of tthsum.

tthsum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

tthsum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with tthsum.  If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
#include "thex.h"

#include "endian.h"
#include "read.h"
#include "tiger.h"
#include <stdio.h>
#include <string.h>

#ifdef USE_TEXTS
#   include "texts.h"
#endif /* USE_TEXTS */

#define TWO_HASH_OCTETS (2*3*8)


static unsigned get_progress_mask(uint64_t filesize) {
    unsigned bit = 2;
    for (filesize >>= 18; filesize != 0; filesize >>= 1)
	bit <<= 1;
    return bit - 1;
}

int thex_tiger_root(struct rofile* stream, uint64_t* result,
	void (*progress_func)(uint64_t,uint64_t)) {
    int ret;
    unsigned progress_mask = 0x7fff;
    /* Read data */
    unsigned blocksize;
    uint64_t filesize;
    /* Tiger data */
    uint64_t data[128]; /* with 128, we have enough room for a 2^137B file */
    uint64_t leafcount = 0;
    unsigned pos = 0;

    /* Assert that the blocksize is a multiple of 1024 */
    rofinfo(&blocksize, &filesize, stream);
    if (blocksize % 1024 != 0) {
#ifdef USE_TEXTS
	set_error("thex_tiger_root", THEX_INVALID_BLOCK_SIZE);
#endif /* USE_TEXTS */
	return -1;
    }

    /* Get number progress_func mask to check against leafcount */
    if (filesize != (uint64_t)-1)
	progress_mask = get_progress_mask(filesize);

#if 0
    /* 4gb == 2^42, requires 33 blocks */
    if (filesize >= 1024)
	tthblocks = (int)ceil(log((double)filesize / 1024.0) / log(2)) + 1;
    else
	tthblocks = 1;
    printf("fs = %lli, tths = %u\r\n", filesize, tthblocks);
    data = (uint64_t*)malloc(tthblocks * 3 * sizeof(uint64_t));
#endif

    while (1) {
	const char* p;
	unsigned size;

	/* Read block of data */
	ret = rofread(&p, &size, stream);
	if (ret <= 0)
	    break;

	/* Iterate over our fetched block of data */
	while (1) {
	    unsigned i;
	    tiger_bp(0x00, p, size < 1024 ? size : 1024, data + pos);
	    /* Print progress */
	    if (progress_func && ((unsigned)leafcount & progress_mask) == 0) {
		progress_func(leafcount * 1024, filesize);
	    }
	    /* Merge backwards */
	    ++leafcount;
	    for (i = 2; (unsigned)leafcount % i == 0; i *= 2) {
#if BYTE_ORDER == BIG_ENDIAN
		uint8_t temp[TWO_HASH_OCTETS];
		int j;
		pos -= 3;
		for (j = 0; j < TWO_HASH_OCTETS; ++j)
		    temp[j ^ 7] = ((uint8_t*)(data + pos))[j];
		tiger_bp(0x01, &temp[0], TWO_HASH_OCTETS, data + pos);
#else /* BYTE_ORDER != BIG_ENDIAN */
		uint64_t temp[3];
		pos -= 3;
		tiger_bp(0x01, data + pos, TWO_HASH_OCTETS, temp);
		data[pos] = temp[0];
		data[pos + 1] = temp[1];
		data[pos + 2] = temp[2];
#endif /* BYTE_ORDER != BIG_ENDIAN */
	    }
	    pos += 3;

	    if (size <= 1024)
		break;

	    size -= 1024;
	    p += 1024;
	}
    }

    if (progress_func)
	progress_func(leafcount, filesize);

    /* Did some error occur? */
    if (ret == -1)
	return ret;
    
    if (pos == 0) {
	/* Nothing written, create the single leaf root */
	tiger_bp(0x00, (const void*)0, 0, data);
    } else {
	/* Merge backwards */
	pos -= 3;
	while (pos != 0) {
#if BYTE_ORDER == BIG_ENDIAN
	    uint8_t temp[TWO_HASH_OCTETS];
	    int j;
	    pos -= 3;
	    for (j = 0; j < TWO_HASH_OCTETS; ++j)
		temp[j ^ 7] = ((const uint8_t*)(data + pos))[j];
	    tiger_bp(0x01, &temp[0], TWO_HASH_OCTETS, data + pos);
#else /* BYTE_ORDER != BIG_ENDIAN */
	    uint64_t temp[3];
	    pos -= 3;
	    tiger_bp(0x01, data + pos, TWO_HASH_OCTETS, temp);
	    data[pos] = temp[0];
	    data[pos + 1] = temp[1];
	    data[pos + 2] = temp[2];
#endif /* BYTE_ORDER != BIG_ENDIAN */
	}
    }

    /* Done, return success */
    result[0] = data[0];
    result[1] = data[1];
    result[2] = data[2];
    return 0;
}
