/****************************************************************************\
 Part of the XeTeX typesetting system
 Copyright (c) 1994-2008 by SIL International
 Copyright (c) 2009-2012 by Jonathan Kew

 SIL Author(s): Jonathan Kew

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the copyright holders
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holders.
\****************************************************************************/

#ifdef XETEX_MAC
#include "XeTeXFontMgr_Mac.h"
#else
#include "XeTeXFontMgr_FC.h"
#endif
#include "XeTeXFontInst.h"

extern "C" {
extern Fixed loadedfontdesignsize;
}

// functions from the Pascal/WEB side
extern "C" {
	void zprintnl(int s);
	void zprintchar(int c);
	void begindiagnostic();
	void zenddiagnostic(int nl);
	int gettracingfontsstate();
};

// see cpascal.h
#define printcstring(STR)        \
  do {                           \
    const char* ch_ptr = (STR);  \
    while (*ch_ptr)              \
      zprintchar(*(ch_ptr++));    \
  } while (0)

XeTeXFontMgr*	XeTeXFontMgr::sFontManager = NULL;
char XeTeXFontMgr::sReqEngine = 0;

/* use our own fmax function because it seems to be missing on certain platforms
   (solaris2.9, at least) */
static inline double
my_fmax(double x, double y)
{
	return (x > y) ? x : y;
}

XeTeXFontMgr*
XeTeXFontMgr::GetFontManager()
{
	if (sFontManager == NULL) {
#ifdef XETEX_MAC
		sFontManager = new XeTeXFontMgr_Mac;
#else
		sFontManager = new XeTeXFontMgr_FC;
#endif
		sFontManager->initialize();
	}
	
	return sFontManager;
}

void
XeTeXFontMgr::Terminate()
{
	if (sFontManager != NULL) {
		sFontManager->terminate();
		// we don't actually deallocate the manager, just ask it to clean up
		// any auxiliary data such as the cocoa pool or freetype/fontconfig stuff
		// as we still need to access font names after this is called
	}
}

PlatformFontRef
XeTeXFontMgr::findFont(const char* name, char* variant, double ptSize)
	// ptSize is in TeX points, or negative for 'scaled' factor
	// "variant" string will be shortened (in-place) by removal of /B and /I if present
{
	std::string	nameStr(name);
	Font*	font = NULL;
	int dsize = 100;
	loadedfontdesignsize = 655360L;

	for (int pass = 0; pass < 2; ++pass) {
		// try full name as given
		std::map<std::string,Font*>::iterator i = nameToFont.find(nameStr);
		if (i != nameToFont.end()) {
			font = i->second;
			if (font->opSizeInfo.designSize != 0)
				dsize = font->opSizeInfo.designSize;
			break;
		}

		// if there's a hyphen, split there and try Family-Style
		int	hyph = nameStr.find('-');
		if (hyph > 0 && hyph < nameStr.length() - 1) {
			std::string	family(nameStr.begin(), nameStr.begin() + hyph);
			std::map<std::string,Family*>::iterator	f = nameToFamily.find(family);
			if (f != nameToFamily.end()) {
				std::string	style(nameStr.begin() + hyph + 1, nameStr.end());
				i = f->second->styles->find(style);
				if (i != f->second->styles->end()) {
					font = i->second;
					if (font->opSizeInfo.designSize != 0)
						dsize = font->opSizeInfo.designSize;
					break;
				}
			}
		}
		
		// try as PostScript name
		i = psNameToFont.find(nameStr);
		if (i != psNameToFont.end()) {
			font = i->second;
			if (font->opSizeInfo.designSize != 0)
				dsize = font->opSizeInfo.designSize;
			break;
		}
		
		// try for the name as a family name
		std::map<std::string,Family*>::iterator	f = nameToFamily.find(nameStr);
		
		if (f != nameToFamily.end()) {
			// look for a family member with the "regular" bit set in OS/2
			int	regFonts = 0;
			for (i = f->second->styles->begin(); i != f->second->styles->end(); ++i)
				if (i->second->isReg) {
					if (regFonts == 0)
						font = i->second;
					++regFonts;
				}

			// families with Ornament or similar fonts may flag those as Regular,
			// which confuses the search above... so try some known names
			if (font == NULL || regFonts > 1) {
				// try for style "Regular", "Plain", "Normal", "Roman"
				i = f->second->styles->find("Regular");
				if (i != f->second->styles->end())
					font = i->second;
				else {
					i = f->second->styles->find("Plain");
					if (i != f->second->styles->end())
						font = i->second;
					else {
						i = f->second->styles->find("Normal");
						if (i != f->second->styles->end())
							font = i->second;
						else {
							i = f->second->styles->find("Roman");
							if (i != f->second->styles->end())
								font = i->second;
						}
					}
				}
			}
			
			if (font == NULL) {
				// look through the family for the (weight, width, slant) nearest to (80, 100, 0)
				font = bestMatchFromFamily(f->second, 80, 100, 0);
			}
			
			if (font != NULL)
				break;
		}
	
		if (pass == 0) {
			// didn't find it in our caches, so do a platform search (may be relatively expensive);
			// this will update the caches with any fonts that seem to match the name given,
			// so that the second pass might find it
			searchForHostPlatformFonts(nameStr);
		}
	}
	
	if (font == NULL)
		return 0;
	
	Family*	parent = font->parent;
	
	// if there are variant requests, try to apply them
	// and delete B, I, and S=... codes from the string, just retain /engine option
	sReqEngine = 0;
	bool	reqBold = false;
	bool	reqItal = false;
	if (variant != NULL) {
		std::string	varString;
		char* cp = variant;
		while (*cp) {
			if (strncmp(cp, "AAT", 3) == 0) {
				sReqEngine = 'A';
				cp += 3;
				if (varString.length() > 0 && *(varString.end() - 1) != '/')
					varString.append("/");
				varString.append("AAT");
				goto skip_to_slash;
			}
			if (strncmp(cp, "ICU", 3) == 0) { // for backword compatability
				sReqEngine = 'O';
				cp += 3;
				if (varString.length() > 0 && *(varString.end() - 1) != '/')
					varString.append("/");
				varString.append("OT");
				goto skip_to_slash;
			}
			if (strncmp(cp, "OT", 2) == 0) {
				sReqEngine = 'O';
				cp += 2;
				if (varString.length() > 0 && *(varString.end() - 1) != '/')
					varString.append("/");
				varString.append("OT");
				goto skip_to_slash;
			}
			if (strncmp(cp, "GR", 2) == 0) {
				sReqEngine = 'G';
				cp += 2;
				if (varString.length() > 0 && *(varString.end() - 1) != '/')
					varString.append("/");
				varString.append("GR");
				goto skip_to_slash;
			}
			if (*cp == 'S') {
				char*	start = cp;
				++cp;
				if (*cp == '=')
					++cp;
				ptSize = 0.0;
				while (*cp >= '0' && *cp <= '9') {
					ptSize = ptSize * 10 + *cp - '0';
					++cp;
				}
				if (*cp == '.') {
					double	dec = 1.0;
					++cp;
					while (*cp >= '0' && *cp <= '9') {
						dec = dec * 10.0;
						ptSize = ptSize + (*cp - '0') / dec;
						++cp;
					}
				}
				goto skip_to_slash;
			}
			
			/* if the code is "B" or "I", we skip putting it in varString */
			while (1) {
				if (*cp == 'B') {
					reqBold = true;
					++cp;
					continue;
				}
				if (*cp == 'I') {
					reqItal = true;
					++cp;
					continue;
				}
				break;
			}
			
		skip_to_slash:
			while (*cp && *cp != '/')
				++cp;
			if (*cp == '/')
				++cp;
		}
		strcpy(variant, varString.c_str());
		
		std::map<std::string,Font*>::iterator i;
		if (reqItal) {
			Font*	bestMatch = font;
			if (font->slant < parent->maxSlant)
				// try for a face with more slant
				bestMatch = bestMatchFromFamily(parent, font->weight, font->width, parent->maxSlant);

			if (bestMatch == font && font->slant > parent->minSlant)
				// maybe the slant is negated, or maybe this was something like "Times-Italic/I"
				bestMatch = bestMatchFromFamily(parent, font->weight, font->width, parent->minSlant);

			if (parent->minWeight == parent->maxWeight && bestMatch->isBold != font->isBold) {
				// try again using the bold flag, as we can't trust weight values
				Font*	newBest = NULL;
				for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
					if (i->second->isBold == font->isBold) {
						if (newBest == NULL && i->second->isItalic != font->isItalic) {
							newBest = i->second;
							break;
						}
					}
				}
				if (newBest != NULL)
					bestMatch = newBest;
			}

			if (bestMatch == font) {
				// maybe slant values weren't present; try the style bits as a fallback
				bestMatch = NULL;
				for (i = parent->styles->begin(); i != parent->styles->end(); ++i)
					if (i->second->isItalic == !font->isItalic)
						if (parent->minWeight != parent->maxWeight) {
							// weight info was available, so try to match that
							if (bestMatch == NULL || weightAndWidthDiff(i->second, font) < weightAndWidthDiff(bestMatch, font))
								bestMatch = i->second;
						}
						else {
							// no weight info, so try matching style bits
							if (bestMatch == NULL && i->second->isBold == font->isBold) {
								bestMatch = i->second;
								break;	// found a match, no need to look further as we can't distinguish!
							}
						}
			}
			if (bestMatch != NULL)
				font = bestMatch;
		}

		if (reqBold) {
			// try for more boldness, with the same width and slant
			Font*	bestMatch = font;
			if (font->weight < parent->maxWeight) {
				// try to increase weight by 1/2 x (max - min), rounding up
				bestMatch = bestMatchFromFamily(parent,
					font->weight + (parent->maxWeight - parent->minWeight) / 2 + 1,
					font->width, font->slant);
				if (parent->minSlant == parent->maxSlant) {
					// double-check the italic flag, as we can't trust slant values
					Font*	newBest = NULL;
					for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
						if (i->second->isItalic == font->isItalic) {
							if (newBest == NULL || weightAndWidthDiff(i->second, bestMatch) < weightAndWidthDiff(newBest, bestMatch))
								newBest = i->second;
						}
					}
					if (newBest != NULL)
						bestMatch = newBest;
				}
			}
			if (bestMatch == font && !font->isBold) {
				for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
					if (i->second->isItalic == font->isItalic && i->second->isBold) {
						bestMatch = i->second;
						break;
					}
				}
			}
			font = bestMatch;
		}
	}

	// if there's optical size info, try to apply it
	if (ptSize < 0.0)
		ptSize = dsize / 10.0;
	if (font != NULL && font->opSizeInfo.subFamilyID != 0 && ptSize > 0.0) {
		ptSize = ptSize * 10.0;	// convert to decipoints for comparison with the opSize values
		double	bestMismatch = my_fmax(font->opSizeInfo.minSize - ptSize, ptSize - font->opSizeInfo.maxSize);
		if (bestMismatch > 0.0) {
			Font*	bestMatch = font;
			for (std::map<std::string,Font*>::iterator i = parent->styles->begin(); i != parent->styles->end(); ++i) {
				if (i->second->opSizeInfo.subFamilyID != font->opSizeInfo.subFamilyID)
					continue;
				double	mismatch = my_fmax(i->second->opSizeInfo.minSize - ptSize, ptSize - i->second->opSizeInfo.maxSize);
				if (mismatch < bestMismatch) {
					bestMatch = i->second;
					bestMismatch = mismatch;
				}
				if (bestMismatch <= 0.0)
					break;
			}
			font = bestMatch;
		}
	}
	
	if (font != NULL && font->opSizeInfo.designSize != 0)
		loadedfontdesignsize = (font->opSizeInfo.designSize << 16L) / 10;

	if (gettracingfontsstate() > 0) {
		begindiagnostic();
		zprintnl(' ');
		printcstring("-> ");
		printcstring(getPlatformFontDesc(font->fontRef).c_str());
		zenddiagnostic(0);
	}

	return font->fontRef;
}

const char*
XeTeXFontMgr::getFullName(PlatformFontRef font) const
{
	std::map<PlatformFontRef,Font*>::const_iterator	i = platformRefToFont.find(font);
	if (i == platformRefToFont.end())
		die("internal error %d in XeTeXFontMgr", 2);
	if (i->second->fullName != NULL)
		return i->second->fullName->c_str();
	else
		return i->second->psName->c_str();
}

void
XeTeXFontMgr::getNames(PlatformFontRef font, const char** psName,
	const char** famName, const char** styName) const
{
	std::map<PlatformFontRef,Font*>::const_iterator	i = platformRefToFont.find(font);
	if (i == platformRefToFont.end())
		die("internal error %d in XeTeXFontMgr", 3);
	*psName = i->second->psName->c_str();
	*famName = i->second->familyName->c_str();
	*styName = i->second->styleName->c_str();
}

int
XeTeXFontMgr::weightAndWidthDiff(const Font* a, const Font* b) const
{
	if (a->weight == 0 && a->width == 0) {
		// assume there was no OS/2 info
		if (a->isBold == b->isBold)
			return 0;
		else
			return 10000;
	}
	
	int	widDiff = labs(a->width - b->width);
	if (widDiff < 10)
		widDiff *= 50;
	
	return labs(a->weight - b->weight) + widDiff;
}

int
XeTeXFontMgr::styleDiff(const Font* a, int wt, int wd, int slant) const
{
	int	widDiff = labs(a->width - wd);
	if (widDiff < 10)
		widDiff *= 200;
	
	return labs(labs(a->slant) - labs(slant)) * 2 + labs(a->weight - wt) + widDiff;
}

XeTeXFontMgr::Font*
XeTeXFontMgr::bestMatchFromFamily(const Family* fam, int wt, int wd, int slant) const
{
	Font*	bestMatch = NULL;
	for (std::map<std::string,Font*>::iterator s = fam->styles->begin(); s != fam->styles->end(); ++s)
		if (bestMatch == NULL || styleDiff(s->second, wt, wd, slant) < styleDiff(bestMatch, wt, wd, slant))
			bestMatch = s->second;
	return bestMatch;
}

const XeTeXFontMgr::OpSizeRec*
XeTeXFontMgr::getOpSize(XeTeXFont font)
{
	hb_font_t* hbFont = ((XeTeXFontInst*)font)->getHbFont();
	if (hbFont != NULL) {
		hb_face_t* face = hb_font_get_face(hbFont);
		OpSizeRec* pSizeRec = (OpSizeRec*) xmalloc(sizeof(OpSizeRec));

		bool ok = hb_ot_layout_get_size_params(face,
				&pSizeRec->designSize,
				&pSizeRec->subFamilyID,
				&pSizeRec->nameCode,
				&pSizeRec->minSize,
				&pSizeRec->maxSize);

		if (ok)
			return pSizeRec;

		free(pSizeRec);
		return NULL;
	}

	return NULL;
}

double
XeTeXFontMgr::getDesignSize(XeTeXFont font)
{
	const OpSizeRec* pSizeRec = getOpSize(font);
	if (pSizeRec != NULL)
		return pSizeRec->designSize / 10.0;
	else
		return 10.0;
}

void
XeTeXFontMgr::getOpSizeRecAndStyleFlags(Font* theFont)
{
	XeTeXFont font = createFont(theFont->fontRef, 655360);
	XeTeXFontInst* fontInst = (XeTeXFontInst*) font;
	if (font != 0) {
		const OpSizeRec* pSizeRec = getOpSize(font);
		if (pSizeRec != NULL) {
			theFont->opSizeInfo.designSize = pSizeRec->designSize;
			if (pSizeRec->subFamilyID == 0
				&& pSizeRec->nameCode == 0
				&& pSizeRec->minSize == 0
				&& pSizeRec->maxSize == 0)
				goto done_size;	// feature is valid, but no 'size' range
			theFont->opSizeInfo.subFamilyID = pSizeRec->subFamilyID;
			theFont->opSizeInfo.nameCode = pSizeRec->nameCode;
			theFont->opSizeInfo.minSize = pSizeRec->minSize;
			theFont->opSizeInfo.maxSize = pSizeRec->maxSize;
		}
	done_size:

		const TT_OS2* os2Table = (TT_OS2*) fontInst->getFontTable(ft_sfnt_os2);
		if (os2Table != NULL) {
			theFont->weight = os2Table->usWeightClass;
			theFont->width = os2Table->usWidthClass;
			uint16_t sel = os2Table->fsSelection;
			theFont->isReg = (sel & (1 << 6)) != 0;
			theFont->isBold = (sel & (1 << 5)) != 0;
			theFont->isItalic = (sel & (1 << 0)) != 0;
		}

		const TT_Header* headTable = (TT_Header*) fontInst->getFontTable(ft_sfnt_head);
		if (headTable != NULL) {
			uint16_t ms = headTable->Mac_Style;
			if ((ms & (1 << 0)) != 0)
				theFont->isBold = true;
			if ((ms & (1 << 1)) != 0)
				theFont->isItalic = true;
		}

		const TT_Postscript* postTable = (const TT_Postscript*) fontInst->getFontTable(ft_sfnt_post);
		if (postTable != NULL) {
			theFont->slant = (int)(1000 * (tan(Fix2D(-postTable->italicAngle) * M_PI / 180.0)));
		}
		deleteFont(font);
	}
}

// append a name but only if it's not already in the list
void
XeTeXFontMgr::appendToList(std::list<std::string>* list, const char* str)
{
	for (std::list<std::string>::const_iterator i = list->begin(); i != list->end(); ++i)
		if (*i == str)
			return;
	list->push_back(str);
}

// prepend a name, removing it from later in the list if present
void
XeTeXFontMgr::prependToList(std::list<std::string>* list, const char* str)
{
	for (std::list<std::string>::iterator i = list->begin(); i != list->end(); ++i)
		if (*i == str) {
			list->erase(i);
			break;
		}
	list->push_front(str);
}

void
XeTeXFontMgr::addToMaps(PlatformFontRef platformFont, const NameCollection* names)
{
	if (platformRefToFont.find(platformFont) != platformRefToFont.end())
		return; // this font has already been cached

	if (names->psName.length() == 0)
		return;	// can't use a font that lacks a PostScript name

	if (psNameToFont.find(names->psName) != psNameToFont.end())
		return;	// duplicates an earlier PS name, so skip

	Font*	thisFont = new Font(platformFont);
	thisFont->psName = new std::string(names->psName);
	getOpSizeRecAndStyleFlags(thisFont);

	psNameToFont[names->psName] = thisFont;
	platformRefToFont[platformFont] = thisFont;

	if (names->fullNames.size() > 0)
		thisFont->fullName = new std::string(*(names->fullNames.begin()));

	if (names->familyNames.size() > 0)
		thisFont->familyName = new std::string(*(names->familyNames.begin()));
	else
		thisFont->familyName = new std::string(names->psName);

	if (names->styleNames.size() > 0)
		thisFont->styleName = new std::string(*(names->styleNames.begin()));
	else
		thisFont->styleName = new std::string;

	std::list<std::string>::const_iterator i;
	for (i = names->familyNames.begin(); i != names->familyNames.end(); ++i) {
		std::map<std::string,Family*>::iterator	iFam = nameToFamily.find(*i);
		Family*	family;
		if (iFam == nameToFamily.end()) {
			family = new Family;
			nameToFamily[*i] = family;
			family->minWeight = thisFont->weight;
			family->maxWeight = thisFont->weight;
			family->minWidth = thisFont->width;
			family->maxWidth = thisFont->width;
			family->minSlant = thisFont->slant;
			family->maxSlant = thisFont->slant;
		} else {
			family = iFam->second;
			if (thisFont->weight < family->minWeight)
				family->minWeight = thisFont->weight;
			if (thisFont->weight > family->maxWeight)
				family->maxWeight = thisFont->weight;
			if (thisFont->width < family->minWidth)
				family->minWidth = thisFont->width;
			if (thisFont->width > family->maxWidth)
				family->maxWidth = thisFont->width;
			if (thisFont->slant < family->minSlant)
				family->minSlant = thisFont->slant;
			if (thisFont->slant > family->maxSlant)
				family->maxSlant = thisFont->slant;
		}

		if (thisFont->parent == NULL)
			thisFont->parent = family;
				
		// ensure all style names in the family point to thisFont
		for (std::list<std::string>::const_iterator j = names->styleNames.begin(); j != names->styleNames.end(); ++j) {
			std::map<std::string,Font*>::iterator iFont = family->styles->find(*j);
			if (iFont == family->styles->end())
				(*family->styles)[*j] = thisFont;
/*
			else if (iFont->second != thisFont)
				fprintf(stderr, "# Font name warning: ambiguous Style \"%s\" in Family \"%s\" (PSNames \"%s\" and \"%s\")\n",
							j->c_str(), i->c_str(), iFont->second->psName->c_str(), thisFont->psName->c_str());
*/
		}
	}
	
	for (i = names->fullNames.begin(); i != names->fullNames.end(); ++i) {
		std::map<std::string,Font*>::iterator iFont = nameToFont.find(*i);
		if (iFont == nameToFont.end())
			nameToFont[*i] = thisFont;
/*
		else if (iFont->second != thisFont)
			fprintf(stderr, "# Font name warning: ambiguous FullName \"%s\" (PSNames \"%s\" and \"%s\")\n",
						i->c_str(), iFont->second->psName->c_str(), thisFont->psName->c_str());
*/
	}
}

void
XeTeXFontMgr::die(const char*s, int i) const
{
	fprintf(stderr, s, i);
	fprintf(stderr, " - exiting\n");
	exit(3);
}

void
XeTeXFontMgr::terminate()
{
}

