/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.

 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-12-0/engines/made/redreader.cpp $
 * $Id: redreader.cpp 31678 2008-04-23 21:07:11Z eriktorbjorn $
 *
 */

#include "made/redreader.h"

namespace Made {


Common::MemoryReadStream *RedReader::load(const char *redFilename, const char *filename) {

	Common::File fd;
	FileEntry fileEntry;

	if (!fd.open(redFilename))
		error("RedReader::RedReader() Could not open %s", redFilename);

	if (!seekFile(fd, fileEntry, filename))
		error("RedReader::RedReader() Could not find %s in archive %s", filename, redFilename);

	byte *fileBuf = (byte *)malloc(fileEntry.origSize);
	
	LzhDecompressor lzhDec;
	lzhDec.decompress(fd, fileBuf, fileEntry.compSize, fileEntry.origSize);
	
	return new Common::MemoryReadStream(fileBuf, fileEntry.origSize, true);

}

Common::MemoryReadStream *RedReader::loadFromRed(const char *redFilename, const char *filename) {
	RedReader redReader;
	return redReader.load(redFilename, filename);
}

bool RedReader::seekFile(Common::File &fd, FileEntry &fileEntry, const char *filename) {
	char arcFilename[13];
	while (!fd.eof()) {
		fd.skip(8); // skip unknown
		fileEntry.compSize = fd.readUint32LE();
		fileEntry.origSize = fd.readUint32LE();
		fd.skip(10); // skip unknown
		fd.read(arcFilename, 13);
		fd.skip(2); // skip unknown
		// Check if we have found the file
		if (!scumm_stricmp(arcFilename, filename))
			return true;
		// Skip compressed data
		fd.skip(fileEntry.compSize);
	}
	return false;
}

LzhDecompressor::LzhDecompressor() {
}

LzhDecompressor::~LzhDecompressor() {
}

int LzhDecompressor::decompress(Common::SeekableReadStream &source, byte *dest, uint32 sourceLen, uint32 destLen) {

	int bufsize;
	byte buffer[DICSIZ];

	_source = &source;
	_compSize = sourceLen;

	count_len_depth = 0;

	_blockPos = 0;

	decode_start();
	while (destLen > 0) {
		bufsize = ((destLen > DICSIZ) ? DICSIZ : destLen);
		decode(bufsize, buffer);
		memcpy(dest, buffer, bufsize);
		dest += bufsize;
		destLen -= bufsize;
	}

	return 0;
}

byte LzhDecompressor::readByte() {
	if (_blockPos == 0xFFE) {
		_blockPos = 0;
		_source->skip(2); // skip unknown value
	}
	byte temp = _source->readByte();
	_blockPos++;
	return temp;
}

void LzhDecompressor::fillbuf(int count) {
	_bitbuf <<= count;
	while (count > _bitcount) {
		_bitbuf |= _subbitbuf << (count -= _bitcount);
		if (_compSize != 0) {
			_compSize--;
			_subbitbuf = readByte();
		} else _subbitbuf = 0;
		_bitcount = 8;
	}
	_bitbuf |= _subbitbuf >> (_bitcount -= count);
}

uint LzhDecompressor::getbits(int count) {
	uint x;
	x = _bitbuf >> (BITBUFSIZ - count);
	fillbuf(count);
	return x;
}

void LzhDecompressor::init_getbits() {
	_bitbuf = 0;
	_subbitbuf = 0;
	_bitcount = 0;
	fillbuf(BITBUFSIZ);
}

void LzhDecompressor::decode_start() {
	huf_decode_start();
	decode_j = 0;
}

void LzhDecompressor::decode(uint count, byte buffer[]) {
	uint r, c;
	r = 0;
	while (--decode_j >= 0) {
		buffer[r] = buffer[decode_i];
		decode_i = (decode_i + 1) & (DICSIZ - 1);
		if (++r == count) return;
	}
	for ( ; ; ) {
		c = decode_c();
		if (c <= 255) {
			buffer[r] = c;
			if (++r == count) return;
		} else {
			decode_j = c - (255 + 1 - THRESHOLD);
			decode_i = (r - decode_p() - 1) & (DICSIZ - 1);
			while (--decode_j >= 0) {
				buffer[r] = buffer[decode_i];
				decode_i = (decode_i + 1) & (DICSIZ - 1);
				if (++r == count) return;
			}
		}
	}
}

void LzhDecompressor::read_pt_len(int nn, int nbit, int i_special) {
	int i, c, v;
	unsigned int mask;
	v = getbits(nbit);
	if (v == 0) {
		c = getbits(nbit);
		for (i = 0; i < nn; i++) _pt_len[i] = 0;
		for (i = 0; i < 256; i++) _pt_table[i] = c;
	} else {
		i = 0;
		while (i < v) {
			c = _bitbuf >> (BITBUFSIZ - 3);
			if (c == 7) {
				mask = 1U << (BITBUFSIZ - 1 - 3);
				while (mask & _bitbuf) {  mask >>= 1;  c++;  }
			}
			fillbuf((c < 7) ? 3 : c - 3);
			_pt_len[i++] = c;
			if (i == i_special) {
				c = getbits(2);
				while (--c >= 0) _pt_len[i++] = 0;
			}
		}
		while (i < nn) _pt_len[i++] = 0;
		make_table(nn, _pt_len, 8, _pt_table);
	}
}

void LzhDecompressor::read_c_len() {
	uint i, v;
	int c;
	unsigned int mask;
	v = getbits(CBIT);
	if (v == 0) {
		c = getbits(CBIT);
		for (i = 0; i < NC; i++) _c_len[i] = 0;
		for (i = 0; i < 4096; i++) _c_table[i] = c;
	} else {
		i = 0;
		while (i < v) {
			c = _pt_table[_bitbuf >> (BITBUFSIZ - 8)];
			if (c >= NT) {
				mask = 1U << (BITBUFSIZ - 1 - 8);
				do {
					if (_bitbuf & mask) c = _right[c];
					else			   c = _left [c];
					mask >>= 1;
				} while (c >= NT);
			}
			fillbuf(_pt_len[c]);
			if (c <= 2) {
				if	  (c == 0) c = 1;
				else if (c == 1) c = getbits(4) + 3;
				else			 c = getbits(CBIT) + 20;
				while (--c >= 0) _c_len[i++] = 0;
			} else _c_len[i++] = c - 2;
		}
		while (i < NC) _c_len[i++] = 0;
		make_table(NC, _c_len, 12, _c_table);
	}
}

unsigned int LzhDecompressor::decode_c(void) {
	uint j, mask;
	if (_blocksize == 0) {
		_blocksize = getbits(16);
		read_pt_len(NT, TBIT, 3);
		read_c_len();
		read_pt_len(NP, PBIT, -1);
	}
	_blocksize--;
	j = _c_table[_bitbuf >> (BITBUFSIZ - 12)];
	if (j >= NC) {
		mask = 1U << (BITBUFSIZ - 1 - 12);
		do {
			if (_bitbuf & mask) j = _right[j];
			else			   j = _left [j];
			mask >>= 1;
		} while (j >= NC);
	}
	fillbuf(_c_len[j]);
	return j;
}

unsigned int LzhDecompressor::decode_p() {
	unsigned int j, mask;
	j = _pt_table[_bitbuf >> (BITBUFSIZ - 8)];
	if (j >= NP) {
		mask = 1U << (BITBUFSIZ - 1 - 8);
		do {
			if (_bitbuf & mask) j = _right[j];
			else			   j = _left [j];
			mask >>= 1;
		} while (j >= NP);
	}
	fillbuf(_pt_len[j]);
	if (j != 0) j = (1U << (j - 1)) + getbits(j - 1);
	return j;
}

void LzhDecompressor::huf_decode_start() {
	init_getbits();
	_blocksize = 0;
}

void LzhDecompressor::make_table(uint nchar, byte bitlen[], uint tablebits, uint16 table[]) {
	uint16 count[17], weight[17], start[18], *p;
	uint i, k, len, ch, jutbits, avail, nextcode, mask;
	for (i = 1; i <= 16; i++) count[i] = 0;
	for (i = 0; i < nchar; i++) count[bitlen[i]]++;
	start[1] = 0;
	for (i = 1; i <= 16; i++)
		start[i + 1] = start[i] + (count[i] << (16 - i));
	if (start[17] != (uint16)(1U << 16))
		error("LzhDecompressor::make_table() Bad table\n");
	jutbits = 16 - tablebits;
	for (i = 1; i <= tablebits; i++) {
		start[i] >>= jutbits;
		weight[i] = 1U << (tablebits - i);
	}
	for (; i <= 16; i++) {
		weight[i] = 1U << (16 - i);
	}
	i = start[tablebits + 1] >> jutbits;
	if (i != (uint16)(1U << 16)) {
		k = 1U << tablebits;
		while (i != k) table[i++] = 0;
	}
	avail = nchar;
	mask = 1U << (15 - tablebits);
	for (ch = 0; ch < nchar; ch++) {
		if ((len = bitlen[ch]) == 0) continue;
		nextcode = start[len] + weight[len];
		if (len <= tablebits) {
			for (i = start[len]; i < nextcode; i++) table[i] = ch;
		} else {
			k = start[len];
			p = &table[k >> jutbits];
			i = len - tablebits;
			while (i != 0) {
				if (*p == 0) {
					_right[avail] = _left[avail] = 0;
					*p = avail++;
				}
				if (k & mask) p = &_right[*p];
				else		  p = &_left[*p];
				k <<= 1;  i--;
			}
			*p = ch;
		}
		start[len] = nextcode;
	}
}

/* call with i = root */
void LzhDecompressor::count_len(int i) {
	if (i < tree_n)
		len_cnt[(count_len_depth < 16) ? count_len_depth : 16]++;
	else {
		count_len_depth++;
		count_len(_left [i]);
		count_len(_right[i]);
		count_len_depth--;
	}
}

void LzhDecompressor::make_len(int root) {
	int i, k;
	uint cum;
	for (i = 0; i <= 16; i++) len_cnt[i] = 0;
	count_len(root);
	cum = 0;
	for (i = 16; i > 0; i--)
		cum += len_cnt[i] << (16 - i);
	while (cum != (1U << 16)) {
		len_cnt[16]--;
		for (i = 15; i > 0; i--) {
			if (len_cnt[i] != 0) {
				len_cnt[i]--;
				len_cnt[i+1] += 2;
				break;
			}
		}
		cum--;
	}
	for (i = 16; i > 0; i--) {
		k = len_cnt[i];
		while (--k >= 0) len_table[*sortptr++] = i;
	}
}

void LzhDecompressor::downheap(int i) {
	int j, k;
	k = heap[i];
	while ((j = 2 * i) <= heapsize) {
		if (j < heapsize && freq[heap[j]] > freq[heap[j + 1]])
		 	j++;
		if (freq[k] <= freq[heap[j]]) break;
		heap[i] = heap[j];  i = j;
	}
	heap[i] = k;
}

void LzhDecompressor::make_code(int n, byte len[], uint16 code[]) {
	int	i;
	uint16 start[18];
	start[1] = 0;
	for (i = 1; i <= 16; i++)
		start[i + 1] = (start[i] + len_cnt[i]) << 1;
	for (i = 0; i < n; i++) code[i] = start[len[i]]++;
}

/* make tree, calculate len[], return root */
int LzhDecompressor::make_tree(int nparm, uint16 freqparm[], byte lenparm[], uint16 codeparm[]) {
	int i, j, k, avail;

	tree_n = nparm;
	freq = freqparm;
	len_table = lenparm;
	avail = tree_n;
	heapsize = 0;
	heap[1] = 0;
	for (i = 0; i < tree_n; i++) {
		len_table[i] = 0;
		if (freq[i]) heap[++heapsize] = i;
	}
	if (heapsize < 2) {
		codeparm[heap[1]] = 0;
		return heap[1];
	}
	for (i = heapsize / 2; i >= 1; i--)
		downheap(i);  /* make priority queue */
	sortptr = codeparm;
	do {  /* while queue has at least two entries */
		i = heap[1];  /* take out least-freq entry */
		if (i < tree_n) *sortptr++ = i;
		heap[1] = heap[heapsize--];
		downheap(1);
		j = heap[1];  /* next least-freq entry */
		if (j < tree_n) *sortptr++ = j;
		k = avail++;  /* generate new node */
		freq[k] = freq[i] + freq[j];
		heap[1] = k;
		downheap(1);  /* put into queue */
		_left[k] = i;
		_right[k] = j;
	} while (heapsize > 1);
	sortptr = codeparm;
	make_len(k);
	make_code(nparm, lenparm, codeparm);
	return k;  /* return root */
}

}
