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

#include "common/file.h"

#include "draci/draci.h"
#include "draci/font.h"
#include "draci/surface.h"

namespace Draci {

const char * const kFontSmall = "Small.fon";
const char * const kFontBig = "Big.fon";

Font::Font(const Common::String &filename) {
	_fontHeight = 0;
	_maxCharWidth = 0;
	_charWidths = NULL;
	_charData = NULL;

	loadFont(filename);
}

Font::~Font() {
	 freeFont();
}

/**
 * @brief Loads fonts from a file
 * @param path Path to font file
 * @return true if the font was loaded successfully, false otherwise
 *
 * Loads fonts from a file into a Font instance. The original game uses two
 * fonts (located inside files "Small.fon" and "Big.fon"). The characters in the
 * font are indexed from the space character so an appropriate offset must be
 * added to convert them to equivalent char values, i.e. kDraciIndexOffset.
 * Characters in the higher range are non-ASCII and vary between different
 * language versions of the game.
 *
 * font format: [1 byte] maximum character width
 *              [1 byte] font height
 *              [138 bytes] character widths of all 138 characters in the font
 *              [138 * fontHeight * maxWidth bytes] character data, stored row-wise
 */

bool Font::loadFont(const Common::String &filename) {
	// Free previously loaded font (if any)
	freeFont();

	Common::File f;

	f.open(filename);
	if (f.isOpen()) {
		debugC(6, kDraciGeneralDebugLevel, "Opened font file %s",
			filename.c_str());
	} else {
		debugC(6, kDraciGeneralDebugLevel, "Error opening font file %s",
			filename.c_str());
		return false;
	}

	_maxCharWidth = f.readByte();
	_fontHeight = f.readByte();

	// Read in the widths of the glyphs
	_charWidths = new uint8[kCharNum];
	for (uint i = 0; i < kCharNum; ++i) {
		_charWidths[i] = f.readByte();
	}

	// Calculate size of font data
	uint fontDataSize = kCharNum * _maxCharWidth * _fontHeight;

	// Read in all glyphs
	_charData = new byte[fontDataSize];
	f.read(_charData, fontDataSize);

	debugC(5, kDraciGeneralDebugLevel, "Font %s loaded", filename.c_str());

	return true;
}

void Font::freeFont() {
	delete[] _charWidths;
	delete[] _charData;
}

uint8 Font::getCharWidth(uint8 chr) const {
	// Safe-guard against incorrect strings containing localized characters
	// with inaccessible codes.  These strings do not exist in the original
	// Czech version, but they do in the (never properly reviewed) English
	// version.
	return chr >= kCharIndexOffset && chr < kCharIndexOffset + kCharNum
		? _charWidths[chr - kCharIndexOffset] : 0;
}

/**
 * @brief Draw a char to a Draci::Surface
 *
 * @param dst   Pointer to the destination surface
 * @param chr   Character to draw
 * @param tx    Horizontal offset on the surface
 * @param ty    Vertical offset on the surface
 */

void Font::drawChar(Surface *dst, uint8 chr, int tx, int ty, int with_color) const {
	assert(dst != NULL);
	assert(tx >= 0);
	assert(ty >= 0);

	byte *ptr = (byte *)dst->getBasePtr(tx, ty);
	const uint8 currentWidth = getCharWidth(chr);
	if (currentWidth == 0) {
		return;
	}

	const uint8 charIndex = chr - kCharIndexOffset;
	const int charOffset = charIndex * _fontHeight * _maxCharWidth;

	// Determine how many pixels to draw horizontally (to prevent overflow)
	int xSpaceLeft = dst->w - tx - 1;
	int xPixelsToDraw = (currentWidth < xSpaceLeft) ? currentWidth : xSpaceLeft;

	// Determine how many pixels to draw vertically
	int ySpaceLeft = dst->h - ty - 1;
	int yPixelsToDraw = (_fontHeight < ySpaceLeft) ? _fontHeight : ySpaceLeft;

	int _transparent = dst->getTransparentColor();

	for (int y = 0; y < yPixelsToDraw; ++y) {
		for (int x = 0; x <= xPixelsToDraw; ++x) {

			int curr = y * _maxCharWidth + x;
			int color = _charData[charOffset + curr];

			// If pixel is transparent, skip it
			if (color == _transparent)
				continue;

			// Replace color with font colors
			switch (color) {
			case 254:
				color = with_color;
				break;

			case 253:
				color = kFontColor2;
				break;

			case 252:
				color = kFontColor3;
				break;

			case 251:
				color = kFontColor4;
				break;
			}

			// Paint the pixel
			ptr[x] = color;
		}

		// Advance to next row
		ptr += dst->pitch;
	}
}

/**
 * @brief Draw a string to a Draci::Surface
 *
 * @param dst       Pointer to the destination surface
 * @param str       Buffer containing string data
 * @param len       Length of the data
 * @param x         Horizontal offset on the surface
 * @param y         Vertical offset on the surface
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 */
void Font::drawString(Surface *dst, const byte *str, uint len,
	                  int x, int y, int with_color, int spacing, bool markDirty) const {
	drawString(dst, Common::String((const char *)str, len), x, y, with_color, spacing, markDirty);
}

/**
 * @brief Draw a string to a Draci::Surface
 *
 * @param dst       Pointer to the destination surface
 * @param str       String to draw
 * @param x         Horizontal offset on the surface
 * @param y         Vertical offset on the surface
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 */

void Font::drawString(Surface *dst, const Common::String &str,
	                  int x, int y, int with_color, int spacing, bool markDirty) const {
	assert(dst != NULL);
	assert(x >= 0);
	assert(y >= 0);

	uint widest = getStringWidth(str, spacing);

	int curx = x + (widest - getLineWidth(str, 0, spacing)) / 2;
	int cury = y;

	for (uint i = 0; i < str.size(); ++i) {

		// If we encounter the '|' char (newline and end of string marker),
		// skip it and go to the start of the next line
		if (str[i] == '|') {
			cury += getFontHeight();
			curx = x + (widest - getLineWidth(str, i+1, spacing) - 1) / 2;
			continue;
		}

		// Break early if there's no more space on the screen
		if (curx >= dst->w - 1 || cury >= dst->h - 1) {
			break;
		}

		drawChar(dst, str[i], curx, cury, with_color);
		curx += getCharWidth(str[i]) + spacing;
	}

	if (markDirty) {
		Common::Rect r(x, y, x + widest, y + getStringHeight(str));
		dst->markDirtyRect(r);
	}
}

/**
 * @brief Calculate the width of a string when drawn in the current font
 *
 * @param str       String to draw
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 *
 * @return The calculated width of the string
 */
uint Font::getStringWidth(const Common::String &str, int spacing) const {
	uint width = 0;

	// Real length, including '|' separators
	uint len = str.size();

	for (uint i = 0, tmp = 0; i < len; ++i) {

		if (str[i] != '|') {
			tmp += getCharWidth(str[i]) + spacing;
		}

		// Newline char encountered, skip it and store the new length if it is greater.
		// Also, all strings in the data files should end with '|' but not all do.
		// This is why we check whether we are at the last char too.
		if (str[i] == '|' || i == len - 1) {
			if (tmp > width) {
				width = tmp;
			}

			tmp = 0;
		}
	}

	return width + 1;
}

uint Font::getLineWidth(const Common::String &str, uint startIndex, int spacing) const {
	uint width = 0;

	// If the index is greater or equal to the string size,
	// the width of the line is 0
	if (startIndex >= str.size())
		return 0;

	for (uint i = startIndex; i < str.size(); ++i) {

		// EOL encountered
		if (str[i] == '|')
			break;

		// Add width of the current char
		width += getCharWidth(str[i]) + spacing;
	}

	return width;
}

/**
 * @brief Calculate the height of a string by counting the number of '|' chars (which
 *        are used as newline characters and end-of-string markers)
 *
 * @param str       String to draw
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 *
 * @return The calculated height of the string
 */
uint Font::getStringHeight(const Common::String &str) const {
	uint len = str.size();
	int separators = 0;

	for (uint i = 0; i < len; ++i) {
		// All strings in the data files should end with '|' but not all do.
		// This is why we check whether we are at the last char too.
		if (str[i] == '|' || i == len - 1) {
			++separators;
		}
	}

	return separators * getFontHeight();
}

} // End of namespace Draci