/* 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/archive.h"
#include "common/stream.h"
#include "common/unzip.h"
#include "common/macresman.h"
#include "graphics/fonts/bdf.h"
#include "graphics/fonts/macfont.h"

#include "graphics/macgui/macfontmanager.h"

namespace Graphics {

// Source: Apple IIGS Technical Note #41, "Font Family Numbers"
// http://apple2.boldt.ca/?page=til/tn.iigs.041
static const char *const fontNames[] = {
	"Chicago",	// system font
	"Geneva",	// application font
	"New York",
	"Geneva",

	"Monaco",
	"Venice",
	"London",
	"Athens",

	"San Francisco",
	"Toronto",
	NULL,
	"Cairo",
	"Los Angeles", // 12

	"Zapf Dingbats",
	"Bookman",
	"Helvetica Narrow",
	"Palatino",
	NULL,
	"Zapf Chancery",
	NULL,

	"Times", // 20
	"Helvetica",
	"Courier",
	"Symbol",
	"Taliesin", // mobile?
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL, // 30
	NULL,
	NULL,
	"Avant Garde",
	"New Century Schoolbook"
};

static const char *const fontStyleSuffixes[] = {
	"",
	"Bold",
	"Italic",
	"Underline",
	"Outline",
	"Shadow",
	"Condense",
	"Extend"
};

MacFontManager::MacFontManager() {
	for (uint i = 0; i < ARRAYSIZE(fontNames); i++)
		if (fontNames[i])
			_fontIds.setVal(fontNames[i], i);

	loadFonts();
}

void MacFontManager::loadFontsBDF() {
	Common::Archive *dat;

	dat = Common::makeZipArchive("classicmacfonts.dat");

	if (!dat) {
		warning("Could not find classicmacfonts.dat. Falling back to built-in fonts");
		_builtInFonts = true;

		return;
	}

	Common::ArchiveMemberList list;
	dat->listMembers(list);

	for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) {
		Common::SeekableReadStream *stream = dat->createReadStreamForMember((*it)->getName());

		Graphics::BdfFont *font = Graphics::BdfFont::loadFont(*stream);

		delete stream;

		Common::String fontName;
		MacFont *macfont;

		if (font->getFamilyName() && *font->getFamilyName()) {
			fontName = Common::String::format("%s-%s-%d", font->getFamilyName(), font->getFontSlant(), font->getFontSize());

			macfont = new MacFont(_fontIds.getVal(font->getFamilyName(), kMacFontNonStandard), font->getFontSize(), parseFontSlant(font->getFontSlant()));
		} else { // Get it from the file name
			fontName = (*it)->getName();

			// Trim the .bdf extension
			for (int i = fontName.size() - 1; i >= 0; --i) {
				if (fontName[i] == '.') {
					while ((uint)i < fontName.size()) {
						fontName.deleteLastChar();
					}
					break;
				}
			}

			macfont = new MacFont(kMacFontNonStandard);
			macfont->setName(fontName);
		}

		FontMan.assignFontToName(fontName, font);
		//macfont->setFont(font);
		_fontRegistry.setVal(fontName, macfont);

		debug(2, " %s", fontName.c_str());
	}

	_builtInFonts = false;

	delete dat;
}

void MacFontManager::loadFonts() {
	Common::Archive *dat;

	dat = Common::makeZipArchive("classicmacfonts.dat");

	if (!dat) {
		warning("Could not find classicmacfonts.dat. Falling back to built-in fonts");
		_builtInFonts = true;

		return;
	}

	Common::ArchiveMemberList list;
	dat->listMembers(list);

	for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) {
		Common::SeekableReadStream *stream = dat->createReadStreamForMember((*it)->getName());

		loadFonts(stream);
	}

	_builtInFonts = false;

	delete dat;
}

void MacFontManager::loadFonts(Common::SeekableReadStream *stream) {
	Common::MacResManager fontFile;

	if (!fontFile.loadFromMacBinary(*stream))
		return;

	loadFonts(&fontFile);
}

void MacFontManager::loadFonts(const Common::String &fileName) {
	Common::MacResManager fontFile;

	if (!fontFile.open(fileName))
		return;

	loadFonts(&fontFile);
}

void MacFontManager::loadFonts(Common::MacResManager *fontFile) {
	Common::MacResIDArray fonds = fontFile->getResIDArray(MKTAG('F','O','N','D'));
	if (fonds.size() > 0) {
		for (Common::Array<uint16>::iterator iterator = fonds.begin(); iterator != fonds.end(); ++iterator) {
			Common::SeekableReadStream *fond = fontFile->getResource(MKTAG('F', 'O', 'N', 'D'), *iterator);

			Common::String familyName = fontFile->getResName(MKTAG('F', 'O', 'N', 'D'), *iterator);

			Graphics::MacFontFamily *fontFamily = new MacFontFamily();
			fontFamily->load(*fond);

			Common::Array<Graphics::MacFontFamily::AsscEntry> *assoc = fontFamily->getAssocTable();

			for (uint i = 0; i < assoc->size(); i++) {
				debug(8, "size: %d style: %d id: %d", (*assoc)[i]._fontSize, (*assoc)[i]._fontStyle,
										(*assoc)[i]._fontID);

				Common::SeekableReadStream *fontstream;
				MacFont *macfont;
				Graphics::MacFONTFont *font;

				fontstream = fontFile->getResource(MKTAG('N', 'F', 'N', 'T'), (*assoc)[i]._fontID);

				if (!fontstream)
					fontstream = fontFile->getResource(MKTAG('F', 'O', 'N', 'T'), (*assoc)[i]._fontID);

				if (!fontstream) {
					warning("MacFontManager: Unknown FontId: %d", (*assoc)[i]._fontID);

					continue;
				}

				font = new Graphics::MacFONTFont;
				font->loadFont(*fontstream, fontFamily, (*assoc)[i]._fontSize, (*assoc)[i]._fontStyle);

				delete fontstream;

				Common::String fontName = Common::String::format("%s-%d-%d", familyName.c_str(), (*assoc)[i]._fontStyle, (*assoc)[i]._fontSize);

				macfont = new MacFont(_fontIds.getVal(familyName, kMacFontNonStandard), (*assoc)[i]._fontSize, (*assoc)[i]._fontStyle);

				FontMan.assignFontToName(fontName, font);
				macfont->setFont(font);
				_fontRegistry.setVal(fontName, macfont);

				debug(2, " %s", fontName.c_str());
			}

			delete fond;
		}
	}
}

const Font *MacFontManager::getFont(MacFont macFont) {
	const Font *font = 0;

	if (!_builtInFonts) {
		if (macFont.getName().empty())
			macFont.setName(getFontName(macFont.getId(), macFont.getSize(), macFont.getSlant()));

		if (!_fontRegistry.contains(macFont.getName())) {
			// Let's try to generate name
			if (macFont.getSlant() != kMacFontRegular)
				macFont.setName(getFontName(macFont.getId(), macFont.getSize(), macFont.getSlant(), true));

			if (!_fontRegistry.contains(macFont.getName()))
				generateFontSubstitute(macFont);
		}

		font = FontMan.getFontByName(macFont.getName());

		if (!font) {
			debug(1, "Cannot load font '%s'", macFont.getName().c_str());

			font = FontMan.getFontByName(MacFont(kMacFontChicago, 12).getName());
		}
	}

	if (_builtInFonts || !font)
		font = FontMan.getFontByUsage(macFont.getFallback());

	return font;
}

int MacFontManager::parseFontSlant(Common::String slant) {
	slant.toUppercase();
	int slantVal = 0;

	if (slant == "I")
		slantVal |= kMacFontItalic;
	if (slant == "B")
		slantVal |= kMacFontBold;
	if (slant == "R")
		slantVal |= kMacFontRegular;

	return slantVal;
}

void MacFontManager::registerFontMapping(uint16 id, Common::String name) {
	_extraFontNames[id] = name;
	_extraFontIds[name] = id;
}

void MacFontManager::clearFontMapping() {
	_extraFontNames.clear();
	_extraFontIds.clear();
}

const char *MacFontManager::getFontName(int id, int size, int slant, bool tryGen) {
	static char name[128];
	Common::String n;

	if (_extraFontNames.contains(id)) {
		n = _extraFontNames[id];
	} else if (id < ARRAYSIZE(fontNames)) {
		n = fontNames[id];
	} else {
		warning("MacFontManager: Requested font ID %d not found. Falling back to Chicago", id);
		n = fontNames[0]; // Fallback to Chicago
	}

	if (tryGen && slant != kMacFontRegular) {
		for (int i = 0; i < 7; i++) {
			if (slant & (1 << i)) {
				n += ' ';
				n += fontStyleSuffixes[i + 1];
			}
		}

		slant = 0;
	}

	snprintf(name, 128, "%s-%d-%d", n.c_str(), slant, size);

	return name;
}

const char *MacFontManager::getFontName(MacFont &font) {
	return getFontName(font.getId(), font.getSize(), font.getSlant());
}

int MacFontManager::getFontIdByName(Common::String name) {
	if (_extraFontIds.contains(name))
		return _extraFontIds[name];

	for (int f = 0; f < ARRAYSIZE(fontNames); f++)
		if (fontNames[f] != NULL && strcmp(fontNames[f], name.c_str()) == 0)
			return f;
	return 1;
}

void MacFontManager::generateFontSubstitute(MacFont &macFont) {
	Common::String name;

	// First we try twice size
	name = getFontName(macFont.getId(), macFont.getSize() * 2, macFont.getSlant());
	if (_fontRegistry.contains(name) && !_fontRegistry[name]->isGenerated()) {
		generateFont(macFont, *_fontRegistry[name]);

		return;
	}

	// Now half size
	name = getFontName(macFont.getId(), macFont.getSize() / 2, macFont.getSlant());
	if (_fontRegistry.contains(name) && !_fontRegistry[name]->isGenerated()) {
		generateFont(macFont, *_fontRegistry[name]);

		return;
	}

	// No simple substitute was found. Looking for neighborhood fonts

	// First we gather all font sizes for this font
	Common::Array<int> sizes;
	for (Common::HashMap<Common::String, MacFont *>::iterator i = _fontRegistry.begin(); i != _fontRegistry.end(); ++i) {
		if (i->_value->getId() == macFont.getId() && i->_value->getSlant() == macFont.getSlant() && !i->_value->isGenerated())
			sizes.push_back(i->_value->getSize());
	}

	if (sizes.empty()) {
		debug(1, "No viable substitute found for font %s", getFontName(macFont));
		return;
	}

	// Now looking next larger font, and store the largest one for next check
	int candidate = 1000;
	int maxSize = sizes[0];
	for (uint i = 0; i < sizes.size(); i++) {
		if (sizes[i] > macFont.getSize() && sizes[i] < candidate)
			candidate = sizes[i];

		if (sizes[i] > maxSize)
			maxSize = sizes[i];
	}

	if (candidate != 1000) {
		generateFont(macFont, *_fontRegistry[getFontName(macFont.getId(), candidate, macFont.getSlant())]);
		return;
	}

	// Now next smaller font, which is the biggest we have
	generateFont(macFont, *_fontRegistry[getFontName(macFont.getId(), maxSize, macFont.getSlant())]);
}

void MacFontManager::generateFont(MacFont &toFont, MacFont &fromFont) {
	debugN("Found font substitute for font '%s' ", getFontName(toFont));
	debug("as '%s'", getFontName(fromFont));

	MacFONTFont *font = Graphics::MacFONTFont::scaleFont(fromFont.getFont(), toFont.getSize());

	if (!font) {
		warning("Failed to generate font '%s'", getFontName(toFont));
	}

	toFont.setGenerated(true);
	toFont.setFont(font);

	FontMan.assignFontToName(getFontName(toFont), font);
	_fontRegistry.setVal(getFontName(toFont), new MacFont(toFont));

	debug("Generated font '%s'", getFontName(toFont));
}

} // End of namespace Graphics