/* 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.
 *
 */

/*
 * Based on
 * WebVenture (c) 2010, Sean Kasun
 * https://github.com/mrkite/webventure, http://seancode.com/webventure/
 *
 * Used with explicit permission from the author
 */

#include "macventure/text.h"

namespace MacVenture {
TextAsset::TextAsset(MacVentureEngine *engine, ObjID objid, ObjID source, ObjID target, Container *container, bool isOld, const HuffmanLists *huffman) {
	_id = objid;
	_sourceObj = source;
	_targetObj = target;
	_container = container;
	_huffman = huffman;
	_isOld = isOld;
	_engine = engine;

	if (_isOld) {
		decodeOld();
	} else {
		decodeHuffman();
	}
}

void TextAsset::decodeOld() {
	Common::SeekableReadStream *res = _container->getItem(_id);
	uint16 strLen = res->readUint16BE();
	Common::BitStream32BELSB stream(res, DisposeAfterUse::YES);
	char *str = new char[strLen + 1];
	bool lowercase = false;
	char c = ' ';
	for (uint16 i = 0; i < strLen; i++) {
		char val = stream.getBits(5);
		if (val == 0x0) { // Space
			c = ' ';
		} else if (val >= 0x1 && val <= 0x1A) {
			if (lowercase) { // Ascii a-z
				c = val + 0x60;
			} else { // Ascii A-Z
				c = val + 0x40;
			}
			lowercase = true;
		} else if (val == 0x1B) {
			if (lowercase) {
				c = '.';
			} else {
				c = ',';
			}
			lowercase = true;
		} else if (val == 0x1C) {
			if (lowercase) {
				c = '\'';
			} else {
				c = '"';
			}
			lowercase = true;
		} else if (val == 0x1D) { // Composite
			ObjID subval = stream.getBits(16);
			Common::String child;
			if (subval & 0x8000) {
				// Composite object id
				subval ^= 0xFFFF;
				child = getNoun(subval);
			} else {
				// Just another id
				// HACK, see below in getNoun()
				child = *TextAsset(_engine, subval, _sourceObj, _targetObj, _container, _isOld, _huffman).decode();
			}
			if (child.size() > 0) {
				c = '?'; // HACK Will fix later, should append
			}
			lowercase = true;
		} else if (val == 0x1E) {
			c = stream.getBits(8);
			lowercase = true;
		} else if (val == 0x1F) {
			lowercase = !lowercase;
		} else {
			warning("Unrecognized char in old text %d, pos %d", _id, i);
		}
		str[i] = c;
	}

	str[strLen] = '\0';
	debugC(3, kMVDebugText, "Decoded string [%d] (old encoding): %s", _id, str);
	_decoded = Common::String(str);
}

void TextAsset::decodeHuffman() {
	_decoded = Common::String("");
	Common::SeekableReadStream *res = _container->getItem(_id);
	Common::BitStream8MSB stream(res, DisposeAfterUse::YES);
	uint16 strLen = 0;
	if (stream.getBit()) {
		strLen = stream.getBits(15);
	} else {
		strLen = stream.getBits(7);
	}
	uint32 mask = 0;
	uint32 symbol = 0;
	char c;
	for (uint16 i = 0; i < strLen; i++) {
		mask = stream.peekBits(16);

		uint32 entry;
		// Find the length index
		for (entry = 0; entry < _huffman->getNumEntries(); entry++) {
			if (mask < _huffman->getMask(entry)) {
				break;
			}
		}

		stream.skip(_huffman->getLength(entry));

		symbol = _huffman->getSymbol(entry);

		if (symbol == 1) { // 7-bit ascii
			c = stream.getBits(7);
			_decoded += c;
		} else if (symbol == 2) { // Composite
			if (stream.getBit()) { // TextID
				ObjID embedId = stream.getBits(15);
				uint pos = stream.pos(); // HACK, part 1
				TextAsset embedded(_engine, embedId, _sourceObj, _targetObj, _container, _isOld, _huffman);
				stream.rewind();// HACK, part 2
				stream.skip(pos);

				_decoded.replace(_decoded.end(), _decoded.end(), *embedded.decode());

				// Another HACK, to get around that EOS char I insert at the end
				_decoded.replace(_decoded.end() - 1, _decoded.end(), "");
			} else { //Composite obj string
				ObjID embedId = stream.getBits(8);
				uint pos = stream.pos(); // HACK, part 1

				_decoded.replace(_decoded.end(), _decoded.end(), getNoun(embedId));
				stream.rewind();// HACK, part 2
				stream.skip(pos);

				// Another HACK, to get around that EOS char I insert at the end
				_decoded.replace(_decoded.end() - 1, _decoded.end(), "");
			}
		} else { // Plain ascii
			c = symbol & 0xFF;
			_decoded.replace(_decoded.end(), _decoded.end(), Common::String(c));
		}
	}
	_decoded += '\0';
	debugC(3, kMVDebugText, "Decoded string [%d] (new encoding): %s", _id, _decoded.c_str());
}
Common::String TextAsset::getNoun(ObjID subval) {
	ObjID obj;
	Common::String name;
	if (subval & 8) {
		obj = _targetObj;
	} else {
		obj = _sourceObj;
	}
	if ((subval & 3) == 1) {
		uint idx = _engine->getPrefixNdx(obj);
		idx = ((idx >> 4) & 3) + 1;
		name = _engine->getNoun(idx);
	} else {
		// HACK, there should be a pool of assets or something like in the GUI
		name = *TextAsset(_engine, obj, _sourceObj, _targetObj, _container, _isOld, _huffman).decode();
		switch (subval & 3) {
		case 2:
			name = _engine->getPrefixString(0, obj) + name;
			break;
		case 3:
			name = _engine->getPrefixString(2, obj) + name;
			break;
		}
	}
	if (name.size() && (subval & 4)) {
		Common::String tmp = name;
		name.toUppercase();
		name.replace(1, name.size() - 1, tmp, 1, tmp.size() - 1);
	}

	return name;
}

} // End of namespace MacVenture