/* glyphfilter.{cc,hh} -- define subsets of characters
 *
 * Copyright (c) 2004-2012 Eddie Kohler
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef WIN32
# define _USE_MATH_DEFINES
#endif
#include "glyphfilter.hh"
#include <lcdf/error.hh>
#include <lcdf/straccum.hh>
#include <ctype.h>
#include <algorithm>
#include "uniprop.hh"
#include "util.hh"

bool
GlyphFilter::allow(Efont::OpenType::Glyph glyph, const Vector<PermString>& glyph_names, uint32_t unicode, int ptype) const
{
    // out-of-range glyphs never match
    if (glyph < 0 || glyph >= glyph_names.size())
	return false;

    String glyph_name = glyph_names[glyph];
    int uniprop = -1;
    bool any_includes = false;
    bool included = false;

    // loop over patterns
    for (const Pattern* p = _patterns.begin(); p < _patterns.end(); p++) {
	// check pattern type
	if ((p->type & ~T_TYPEMASK) != ptype)
	    continue;
	// check include/exclude
	if ((p->type & T_EXCLUDE) == 0) {
	    if (included)
		continue;
	    any_includes = true;
	}
	// check if there's a match
	bool match;
	if (p->data == D_NAME)
	    match = glob_match(glyph_name, p->pattern);
	else if (p->data == D_UNIPROP) {
	    if (uniprop < 0)
		uniprop = UnicodeProperty::property(unicode);
	    match = ((uniprop & p->u.uniprop.mask) == p->u.uniprop.value);
	} else
	    match = (unicode >= p->u.unirange.low && unicode <= p->u.unirange.high);
	// act if match
	if (match == ((p->type & T_NEGATE) == 0)) {
	    if ((p->type & T_EXCLUDE) == 0)
		included = true;
	    else
		return false;
	}
    }

    return !any_includes || included;
}

GlyphFilter::Pattern::Pattern(uint16_t ptype)
    : type(ptype), data(D_NAME)
{
    // make sure that even unused data has a known value, to simplify
    // operator==
    u.unirange.low = u.unirange.high = 0;
}

int
GlyphFilter::Pattern::compare(const GlyphFilter::Pattern& a, const GlyphFilter::Pattern& b)
{
    int cmp = a.type - b.type;
    if (cmp == 0)
	cmp = a.data - b.data;
    if (cmp == 0)
	cmp = (int) (a.u.unirange.low - b.u.unirange.low);
    if (cmp == 0)
	cmp = (int) (a.u.unirange.high - b.u.unirange.high);
    if (cmp == 0)
	cmp = String::compare(a.pattern, b.pattern);
    return cmp;
}

void
GlyphFilter::add_pattern(const String& pattern, int ptype, ErrorHandler* errh)
{
    _sorted = false;

    const char* begin = pattern.begin();
    const char* end = pattern.end();
    while (begin < end && isspace((unsigned char) *begin))
	begin++;
    if (begin >= end)
	errh->error("missing pattern");

    while (begin < end) {
	const char* word = begin;
	while (word < end && !isspace((unsigned char) *word))
	    word++;
	bool negated = false;
	if (begin < word && begin[0] == '!')
	    negated = true, begin++;

	// actually parse clause
	Pattern p(ptype + (negated ? T_NEGATE : 0));

	// unicode property
	if (begin + 3 <= word && begin[0] == '<' && word[-1] == '>') {
	    p.data = D_UNIPROP;
	    if (UnicodeProperty::parse_property(pattern.substring(begin + 1, word - 1), p.u.uniprop.value, p.u.uniprop.mask))
		_patterns.push_back(p);
	    else if (errh)
		errh->error("unknown Unicode property %<%s%>", pattern.c_str());
	    goto next_clause;
	}

	// unicode values
	{
	    const char* dash = std::find(begin, word, '-');
	    if (parse_unicode_number(begin, dash, 2, p.u.unirange.low)) {
		if (dash == word)
		    p.u.unirange.high = p.u.unirange.low;
		else if (dash == word - 1)
		    p.u.unirange.high = 0xFFFFFFFFU;
		else if (parse_unicode_number(dash + 1, word, (begin[0] == 'U' ? 1 : 0), p.u.unirange.high))
		    /* do nothing */;
		else
		    goto name_pattern; // assume it's a name
		p.data = D_UNIRANGE;
		_patterns.push_back(p);
		goto next_clause;
	    }
	}

	// otherwise must be name pattern
    name_pattern:
	p.data = D_NAME;
	p.pattern = pattern.substring(begin, word);
	_patterns.push_back(p);

	// move to next clause
      next_clause:
	for (begin = word; begin < end && isspace((unsigned char) *begin); begin++)
	    /* nada */;
    }
}

void
GlyphFilter::add_substitution_filter(const String& s, bool is_exclude, ErrorHandler* errh)
{
    add_pattern(s, is_exclude ? T_SRC + T_EXCLUDE : T_SRC, errh);
}

void
GlyphFilter::add_alternate_filter(const String& s, bool is_exclude, ErrorHandler* errh)
{
    add_pattern(s, is_exclude ? T_DST + T_EXCLUDE : T_DST, errh);
}

GlyphFilter&
GlyphFilter::operator+=(const GlyphFilter& gf)
{
    // be careful about self-addition
    _patterns.reserve(gf._patterns.size());
    const Pattern* end = gf._patterns.end();
    for (const Pattern* p = gf._patterns.begin(); p < end; p++)
	_patterns.push_back(*p);
    return *this;
}

GlyphFilter
operator+(const GlyphFilter& a, const GlyphFilter& b)
{
    if (!b)
	return a;
    if (!a)
	return b;
    GlyphFilter x(a);
    x += b;
    return x;
}

bool
operator==(const GlyphFilter& a, const GlyphFilter& b)
{
    if (&a == &b)
	return true;
    if (a._patterns.size() != b._patterns.size())
	return false;
    const GlyphFilter::Pattern* pa = a._patterns.begin();
    const GlyphFilter::Pattern* pb = b._patterns.begin();
    for (; pa < a._patterns.end(); pa++, pb++)
	if (!(*pa == *pb))
	    return false;
    return true;
}

void
GlyphFilter::sort()
{
    if (!_sorted) {
	std::sort(_patterns.begin(), _patterns.end());
	Pattern* true_end = std::unique(_patterns.begin(), _patterns.end());
	_patterns.erase(true_end, _patterns.end());
	_sorted = true;
    }
}

void
GlyphFilter::unparse(StringAccum& sa) const
{
    for (const Pattern* p = _patterns.begin(); p < _patterns.end(); p++) {
	sa << (p->type & T_DST ? 'D' : 'S') << (p->type & T_NEGATE ? "!" : "") << (p->type & T_EXCLUDE ? "X" : "");
	if (p->data == D_NAME)
	    sa << '<' << p->pattern << '>';
	else if (p->data == D_UNIPROP)
	    sa << "[UNIPROP:" << p->u.uniprop.mask << '=' << p->u.uniprop.value << ']';
	else
	    sa.snprintf(20, "[U+%02x-U+%02x]", p->u.unirange.low, p->u.unirange.high);
	sa << ' ';
    }
    if (_patterns.size())
	sa.pop_back();
}
