// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$

/// @file font_file_lister_fontconfig.cpp
/// @brief Font Config-based font collector
/// @ingroup font_collector
///

#include "config.h"

#ifdef WITH_FONTCONFIG
#include "font_file_lister_fontconfig.h"

#include <fontconfig/fontconfig.h>

#include "compat.h"

#include <libaegisub/util.h>

#ifndef AGI_PRE
#include <wx/intl.h>
#endif

namespace {
	// In SSA/ASS fonts are sometimes referenced by their "full name",
	// which is usually a concatenation of family name and font
	// style (ex. Ottawa Bold). Full name is available from
	// FontConfig pattern element FC_FULLNAME, but it is never
	// used for font matching.
	// Therefore, I'm removing words from the end of the name one
	// by one, and adding shortened names to the pattern. It seems
	// that the first value (full name in this case) has
	// precedence in matching.
	// An alternative approach could be to reimplement FcFontSort
	// using FC_FULLNAME instead of FC_FAMILY.
	int add_families(FcPattern *pat, std::string family) {
		int family_cnt = 1;
		FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)family.c_str());

		for (std::string::reverse_iterator p = family.rbegin(); p != family.rend(); ++p) {
			if (*p == ' ' || *p == '-') {
				*p = '\0';
				FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)family.c_str());
				++family_cnt;
			}
		}
		return family_cnt;
	}

	int strcasecmp(std::string a, std::string b) {
		agi::util::str_lower(a);
		agi::util::str_lower(b);
		return a.compare(b);
	}
}

FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback cb)
: config(FcInitLoadConfig(), FcConfigDestroy)
{
	cb(_("Updating font cache\n"), 0);
	FcConfigBuildFonts(config);
}

FcFontSet *FontConfigFontFileLister::MatchFullname(const char *family, int weight, int slant) {
	FcFontSet *sets[2];
	FcFontSet *result = FcFontSetCreate();
	int nsets = 0;

	if ((sets[nsets] = FcConfigGetFonts(config, FcSetSystem)))
		nsets++;
	if ((sets[nsets] = FcConfigGetFonts(config, FcSetApplication)))
		nsets++;

	// Run over font sets and patterns and try to match against full name
	for (int i = 0; i < nsets; i++) {
		FcFontSet *set = sets[i];
		for (int fi = 0; fi < set->nfont; fi++) {
			FcPattern *pat = set->fonts[fi];
			char *fullname;
			int pi = 0, at;
			FcBool ol;
			while (FcPatternGetString(pat, FC_FULLNAME, pi++, (FcChar8 **)&fullname) == FcResultMatch) {
				if (FcPatternGetBool(pat, FC_OUTLINE, 0, &ol) != FcResultMatch || ol != FcTrue)
					continue;
				if (FcPatternGetInteger(pat, FC_SLANT, 0, &at) != FcResultMatch || at < slant)
					continue;
				if (FcPatternGetInteger(pat, FC_WEIGHT, 0, &at) != FcResultMatch || at < weight)
					continue;
				if (strcasecmp(fullname, family) == 0) {
					FcFontSetAdd(result, FcPatternDuplicate(pat));
					break;
				}
			}
		}
	}

	return result;
}

FontFileLister::CollectionResult FontConfigFontFileLister::GetFontPaths(wxString const& facename, int bold, bool italic, std::set<wxUniChar> const& characters) {
	CollectionResult ret;

	std::string family = STD_STR(facename);
	if (family[0] == '@')
		family.erase(0, 1);

	int weight = bold == 0 ? 80 :
	             bold == 1 ? 200 :
	                         bold;
	int slant  = italic ? 110 : 0;

	agi::scoped_holder<FcPattern*> pat(FcPatternCreate(), FcPatternDestroy);
	if (!pat) return ret;

	int family_cnt = add_families(pat, family);

	FcPatternAddBool(pat, FC_OUTLINE, true);
	FcPatternAddInteger(pat, FC_SLANT, slant);
	FcPatternAddInteger(pat, FC_WEIGHT, weight);

	FcDefaultSubstitute(pat);

	if (!FcConfigSubstitute(config, pat, FcMatchPattern)) return ret;

	FcResult result;
	agi::scoped_holder<FcFontSet*> fsorted(FcFontSort(config, pat, true, NULL, &result), FcFontSetDestroy);
	agi::scoped_holder<FcFontSet*> ffullname(MatchFullname(family.c_str(), weight, slant), FcFontSetDestroy);
	if (!fsorted || !ffullname) return ret;

	agi::scoped_holder<FcFontSet*> fset(FcFontSetCreate(), FcFontSetDestroy);
	for (int cur_font = 0; cur_font < ffullname->nfont; ++cur_font) {
		FcPattern *curp = ffullname->fonts[cur_font];
		FcPatternReference(curp);
		FcFontSetAdd(fset, curp);
	}
	for (int cur_font = 0; cur_font < fsorted->nfont; ++cur_font) {
		FcPattern *curp = fsorted->fonts[cur_font];
		FcPatternReference(curp);
		FcFontSetAdd(fset, curp);
	}

	int cur_font;
	for (cur_font = 0; cur_font < fset->nfont; ++cur_font) {
		FcBool outline;
		result = FcPatternGetBool(fset->fonts[cur_font], FC_OUTLINE, 0, &outline);
		if (result == FcResultMatch && outline) break;
	}

	if (cur_font >= fset->nfont) return ret;

	// Remove all extra family names from original pattern.
	// After this, FcFontRenderPrepare will select the most relevant family
	// name in case there are more than one of them.
	for (; family_cnt > 1; --family_cnt)
		FcPatternRemove(pat, FC_FAMILY, family_cnt - 1);

	agi::scoped_holder<FcPattern*> rpat(FcFontRenderPrepare(config, pat, fset->fonts[cur_font]), FcPatternDestroy);
	if (!rpat) return ret;

	FcChar8 *r_family;
	if (FcPatternGetString(rpat, FC_FAMILY, 0, &r_family) != FcResultMatch)
		return ret;

	FcChar8 *r_fullname;
	if (FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname) != FcResultMatch)
		return ret;

	if (strcasecmp(family, (char*)r_family) && strcasecmp(family, (char*)r_fullname))
		return ret;

	FcChar8 *file;
	if(FcPatternGetString(rpat, FC_FILE, 0, &file) != FcResultMatch)
		return ret;

	FcCharSet *charset;
	if (FcPatternGetCharSet(rpat, FC_CHARSET, 0, &charset) == FcResultMatch) {
		for (std::set<wxUniChar>::const_iterator it = characters.begin(); it != characters.end(); ++it) {
			if (!FcCharSetHasChar(charset, *it))
				ret.missing += *it;
		}
	}

	ret.paths.push_back((const char *)file);
	return ret;
}
#endif
