/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001/2002 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 */

#include "stdafx.h"
#include "charset.h"
#include "scumm.h"
#include "nut_renderer.h"

void CharsetRendererCommon::setCurID(byte id)
{
	_vm->checkRange(_vm->_maxCharsets - 1, 0, _curId, "Printing with bad charset %d");

	_curId = id;

	_fontPtr = _vm->getResourceAddress(rtCharset, id);
	assert(_fontPtr);
	if (_vm->_features & GF_SMALL_HEADER)
		_fontPtr += 17;
	else
		_fontPtr += 29;
}

// do spacing for variable width old-style font
int CharsetRendererClassic::getCharWidth(byte chr)
{
	int spacing = 0;

	int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4);
	if (offs) {
		spacing = _fontPtr[offs];
		if (_fontPtr[offs + 2] >= 0x80) {
			spacing += _fontPtr[offs + 2] - 0x100;
		} else {
			spacing += _fontPtr[offs + 2];
		}
	}
	
	return spacing;
}

int CharsetRendererOld256::getCharWidth(byte chr)
{
	int spacing = 0;
	
	spacing = *(_fontPtr - 11 + chr);

	// FIXME - this fixes the inventory icons in Zak256/Indy3
	//  see bug #613109.
	// chars 1,2: up arrow chars 3,4: down arrow
	if ((_vm->_gameId == GID_ZAK256 || _vm->_gameId == GID_INDY3_256)
			&& (chr >= 1 && chr <= 4))
		spacing = 6;
	
	return spacing;
}

int CharsetRenderer::getStringWidth(int arg, byte *text)
{
	int pos = 0;
	int width = 1;
	byte chr;
	int oldID = getCurID(); 

	while ((chr = text[pos++]) != 0) {
		if (chr == 0xD)
			break;
		if (chr == '@')
			continue;
		if (chr == 254 || chr == 255) {
			chr = text[pos++];
			if (chr == 3)	// 'WAIT'
				break;
			if (chr == 8) { // 'Verb on next line'
				if (arg == 1)
					break;
				while (text[pos] == ' ')
					text[pos++] = '@';
				continue;
			}
			if (chr == 10 || chr == 21 || chr == 12 || chr == 13) {
				pos += 2;
				continue;
			}
			if (chr == 9 || chr == 1 || chr == 2) // 'Newline'
				break;
			if (chr == 14) {
				int set = text[pos] | (text[pos + 1] << 8);
				pos += 2;
				setCurID(set);
				continue;
			}
		}
		width += getCharWidth(chr);
	}

	setCurID(oldID);

	return width;
}

void CharsetRenderer::addLinebreaks(int a, byte *str, int pos, int maxwidth)
{
	int lastspace = -1;
	int curw = 1;
	byte chr;
	int oldID = getCurID(); 

	while ((chr = str[pos++]) != 0) {
		if (chr == '@')
			continue;
		if (chr == 254)
			chr = 255;
		if (chr == 255) {
			chr = str[pos++];
			if (chr == 3) // 'Wait'
				break;
			if (chr == 8) { // 'Verb on next line'
				if (a == 1) {
					curw = 1;
				} else {
					while (str[pos] == ' ')
						str[pos++] = '@';
				}
				continue;
			}
			if (chr == 10 || chr == 21 || chr == 12 || chr == 13) {
				pos += 2;
				continue;
			}
			if (chr == 1) { // 'Newline'
				curw = 1;
				continue;
			}
			if (chr == 2) // 'Don't terminate with \n'
				break;
			if (chr == 14) {
				int set = str[pos] | (str[pos + 1] << 8);
				pos += 2;
				setCurID(set);
				continue;
			}
		}

		if (chr == ' ')
			lastspace = pos - 1;

		curw += getCharWidth(chr);
		if (lastspace == -1)
			continue;
		if (curw > maxwidth) {
			str[lastspace] = 0xD;
			curw = 1;
			pos = lastspace + 1;
			lastspace = -1;
		}
	}

	setCurID(oldID);
}


void CharsetRendererOld256::printChar(int chr)
{																// Indy3 / Zak256
	VirtScreen *vs;
	byte *char_ptr, *dest_ptr;
	unsigned int buffer = 0, mask = 0, x = 0, y = 0;
	unsigned char color;

	_vm->checkRange(_vm->_maxCharsets - 1, 0, _curId, "Printing with bad charset %d");

	if ((vs = _vm->findVirtScreen(_top)) == NULL)
		return;

	if (chr == '@')
		return;

	if (_firstChar) {
		_strLeft = _left;
		_strTop = _top;
		_strRight = _left;
		_strBottom = _top;
		_firstChar = false;
	}
	char_ptr = _fontPtr + 207 + (chr + 1) * 8;
	dest_ptr = vs->screenPtr + vs->xstart + (_top - vs->topline) * _vm->_realWidth + _left;
	_vm->updateDirtyRect(vs->number, _left, _left + 8, _top - vs->topline, _top - vs->topline + 8, 0);

	for (y = 0; y < 8; y++) {
		for (x = 0; x < 8; x++) {
			if ((mask >>= 1) == 0) {
				buffer = *char_ptr++;
				mask = 0x80;
			}
			color = ((buffer & mask) != 0);
			if (color)
				*(dest_ptr + y * _vm->_realWidth + x) = _color;
		}
	}

	// FIXME
	_left += getCharWidth(chr);

	if (_left > _strRight)
		_strRight = _left;

	if (_top + 8 > _strBottom)
		_strBottom = _top + 8;

}


void CharsetRendererClassic::printChar(int chr)
{
	int width, height;
	int offsX, offsY;
	int d;
	VirtScreen *vs;

	_vm->checkRange(_vm->_maxCharsets - 1, 1, _curId, "Printing with bad charset %d");
	
	if ((vs = _vm->findVirtScreen(_top)) == NULL)
		return;

	if (chr == '@')
		return;

	_bpp = *_fontPtr;
	_vm->_charsetColorMap[1] = _color;

	uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4);

	if (!charOffs)
		return;

	assert(charOffs < 0x10000);

	_charPtr = _fontPtr + charOffs;

	width = _charPtr[0];
	height = _charPtr[1];
	if (_firstChar) {
		_strLeft = 0;
		_strTop = 0;
		_strRight = 0;
		_strBottom = 0;
	}

	if (_disableOffsX) {
		offsX = 0;
	} else {
		d = _charPtr[2];
		if (d >= 0x80)
			d -= 0x100;
		offsX = d;
	}

	d = _charPtr[3];
	if (d >= 0x80)
		d -= 0x100;
	offsY = d;

	_top += offsY;
	_left += offsX;

	if (_left + width > _right + 1 || _left < 0) {
		_left += width;
		_top -= offsY;
		return;
	}

	_disableOffsX = false;

	if (_firstChar) {
		_strLeft = _left;
		_strTop = _top;
		_strRight = _left;
		_strBottom = _top;
		_firstChar = false;
	}

	if (_left < _strLeft)
		_strLeft = _left;

	if (_top < _strTop)
		_strTop = _top;

	int drawTop = _top - vs->topline;
	if (drawTop < 0)
		drawTop = 0;
	int bottom = drawTop + height + offsY;

	_vm->updateDirtyRect(vs->number, _left, _left + width, drawTop, bottom, 0);

	if (vs->number != 0)
		_blitAlso = false;
	if (vs->number == 0 && !_blitAlso)
		_hasMask = true;

	_charPtr += 4;

	byte *mask = _vm->getResourceAddress(rtBuffer, 9)
		+ drawTop * _vm->gdi._numStrips + _left / 8 + _vm->_screenStartStrip;

	byte *dst = vs->screenPtr + vs->xstart + drawTop * _vm->_realWidth + _left;

	if (_blitAlso) {
		byte *back = dst;
		dst = _vm->getResourceAddress(rtBuffer, vs->number + 5)
			+ vs->xstart + drawTop * _vm->_realWidth + _left;

		drawBits(vs, dst, mask, drawTop, width, height);

		_vm->blit(back, dst, width, height);
	} else {
		drawBits(vs, dst, mask, drawTop, width, height);
	}
	
	_left += width;
	if (_left > _strRight)
		_strRight = _left;

	if (_top + height > _strBottom)
		_strBottom = _top + height;

	_top -= offsY;
}

void CharsetRendererClassic::drawBits(VirtScreen *vs, byte *dst, byte *mask, int drawTop, int width, int height)
{
	byte maskmask;
	int y, x;
	int maskpos;
	int color;
	byte numbits, bits;
	bool useMask = (vs->number == 0 && !_ignoreCharsetMask);

	bits = *_charPtr++;
	numbits = 8;

	y = 0;

	for (y = 0; y < height && y + drawTop < vs->height; y++) {
		maskmask = revBitMask[_left & 7];
		maskpos = 0;

		for (x = 0; x < width; x++) {
			int myMask = (0xFF << (8 - _bpp)) & 0xFF;
			int myColor = (bits & myMask) >> (8 - _bpp);
			color = (bits >> (8 - _bpp)) & 0xFF;
			assert(color == myColor);
			
			if (color) {
				if (useMask) {
					mask[maskpos] |= maskmask;
				}
				*dst = _vm->_charsetColorMap[color];
			}
			dst++;
			bits <<= _bpp;
			numbits -= _bpp;
			if (numbits == 0) {
				bits = *_charPtr++;
				numbits = 8;
			}
			maskmask >>= 1;
			if (maskmask == 0) {
				maskmask = 0x80;
				maskpos++;
			}
		}
		dst += _vm->_realWidth - width;
		mask += _vm->gdi._numStrips;
	}
}

CharsetRendererNut::CharsetRendererNut(Scumm *vm)
	 : CharsetRenderer(vm)
{
	_current = 0;
	
	for (int i = 0; i < 5; i++) {
		char fontname[256];
		sprintf(fontname, "font%d.nut", i);
		_fr[i] = new NutRenderer(_vm);
		if (!(_fr[i]->loadFont(fontname, _vm->getGameDataPath()))) {
			delete _fr[i];
			_fr[i] = NULL;
		}
	}
}

CharsetRendererNut::~CharsetRendererNut()
{
	for (int i = 0; i < 5; i++)
		delete _fr[i];
}

void CharsetRendererNut::setCurID(byte id)
{
	assert(id < 5);
	_curId = id;
	_current = _fr[id];
}

int CharsetRendererNut::getCharWidth(byte chr)
{
	assert(_current);
	return _current->getCharWidth(chr);
}

int CharsetRendererNut::getFontHeight()
{
	// FIXME / TODO: how to implement this properly???
	assert(_current);
	return _current->getCharHeight('|');
}

void CharsetRendererNut::printChar(int chr)
{
	assert(_current);
	
	if (chr == '@')
		return;

	if (_firstChar) {
		_strLeft = _left;
		_strTop = _top;
		_strRight = _left;
		_strBottom = _top;
		_firstChar = false;
	}

	int width = _current->getCharWidth(chr);
	int height = _current->getCharHeight(chr);

	_hasMask = true;
	_current->drawChar((char)chr, _left, _top, _color);
	_vm->updateDirtyRect(0, _left, _left + width, _top, _top + height, 0);

	_left += width;
	if (_left > _strRight)
		_strRight = _left;

	if (_top + height > _strBottom)
		_strBottom = _top + height;
}