#include "../chaining.h"
#include "common.h"

bool otfcc_chainingLookupIsContextualLookup(const otl_Lookup *lookup) {
	if (!(lookup->type == otl_type_gpos_chaining || lookup->type == otl_type_gsub_chaining))
		return false;
	bool isContextual = true;
	for (tableid_t j = 0; j < lookup->subtables.length; j++) {
		const subtable_chaining *subtable = &(lookup->subtables.items[j]->chaining);
		if (subtable->type == otl_chaining_classified) {
			for (tableid_t k = 0; k < subtable->rulesCount; k++) {
				otl_ChainingRule *rule = subtable->rules[k];
				tableid_t nBacktrack = rule->inputBegins;
				tableid_t nLookahead = rule->matchCount - rule->inputEnds;
				isContextual = isContextual && !nBacktrack && !nLookahead;
			}
		} else {
			const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
			tableid_t nBacktrack = rule->inputBegins;
			tableid_t nLookahead = rule->matchCount - rule->inputEnds;
			isContextual = isContextual && !nBacktrack && !nLookahead;
		}
	}
	return isContextual;
}

////// CHAINING

static caryll_Buffer *otfcc_build_chaining_coverage(const otl_Subtable *_subtable) {
	const subtable_chaining *subtable = &(_subtable->chaining);
	const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
	tableid_t nBacktrack = rule->inputBegins;
	tableid_t nInput = rule->inputEnds - rule->inputBegins;
	tableid_t nLookahead = rule->matchCount - rule->inputEnds;
	tableid_t nSubst = rule->applyCount;
	reverseBacktracks(rule);

	bk_Block *root = bk_new_Block(b16, 3, // format
	                              bkover);

	bk_push(root, b16, nBacktrack, bkover);
	for (tableid_t j = 0; j < rule->inputBegins; j++) {
		bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
	}
	bk_push(root, b16, nInput, bkover);
	for (tableid_t j = rule->inputBegins; j < rule->inputEnds; j++) {
		bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
	}
	bk_push(root, b16, nLookahead, bkover);
	for (tableid_t j = rule->inputEnds; j < rule->matchCount; j++) {
		bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
	}
	bk_push(root, b16, rule->applyCount, bkover);
	for (tableid_t j = 0; j < nSubst; j++) {
		bk_push(root, b16, rule->apply[j].index - nBacktrack, // position
		        b16, rule->apply[j].lookup.index,             // lookup
		        bkover);
	}

	return bk_build_Block(root);
}

static caryll_Buffer *otfcc_build_chaining_classes(const otl_Subtable *_subtable) {
	const subtable_chaining *subtable = &(_subtable->chaining);

	otl_Coverage *coverage;
	NEW(coverage);
	coverage->numGlyphs = subtable->ic->numGlyphs;
	coverage->glyphs = subtable->ic->glyphs;

	bk_Block *root =
	    bk_new_Block(b16, 2,                                                   // format
	                 p16, bk_newBlockFromBuffer(Coverage.build(coverage)),     // coverage
	                 p16, bk_newBlockFromBuffer(ClassDef.build(subtable->bc)), // BacktrackClassDef
	                 p16, bk_newBlockFromBuffer(ClassDef.build(subtable->ic)), // InputClassDef
	                 p16, bk_newBlockFromBuffer(ClassDef.build(subtable->fc)), // LookaheadClassDef
	                 b16, subtable->ic->maxclass + 1, // ChainSubClassSetCnt
	                 bkover);

	glyphclass_t *rcpg;
	NEW(rcpg, subtable->ic->maxclass + 1);
	for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
		rcpg[j] = 0;
	}
	for (tableid_t j = 0; j < subtable->rulesCount; j++) {
		tableid_t ib = subtable->rules[j]->inputBegins;
		tableid_t startClass = subtable->rules[j]->match[ib]->glyphs[0].index;
		if (startClass <= subtable->ic->maxclass) rcpg[startClass] += 1;
	}

	for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
		if (rcpg[j]) {
			bk_Block *cset = bk_new_Block(b16, rcpg[j], // ChainSubClassRuleCnt
			                              bkover);
			for (tableid_t k = 0; k < subtable->rulesCount; k++) {
				otl_ChainingRule *rule = subtable->rules[k];
				glyphclass_t startClass = rule->match[rule->inputBegins]->glyphs[0].index;
				if (startClass != j) { continue; }
				reverseBacktracks(rule);
				tableid_t nBacktrack = rule->inputBegins;
				tableid_t nInput = rule->inputEnds - rule->inputBegins;
				tableid_t nLookahead = rule->matchCount - rule->inputEnds;
				tableid_t nSubst = rule->applyCount;
				bk_Block *r = bk_new_Block(bkover);
				bk_push(r, b16, nBacktrack, bkover);
				for (tableid_t m = 0; m < rule->inputBegins; m++) {
					bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
				}
				bk_push(r, b16, nInput, bkover);
				for (tableid_t m = rule->inputBegins + 1; m < rule->inputEnds; m++) {
					bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
				}
				bk_push(r, b16, nLookahead, bkover);
				for (tableid_t m = rule->inputEnds; m < rule->matchCount; m++) {
					bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
				}
				bk_push(r, b16, nSubst, bkover);
				for (tableid_t m = 0; m < nSubst; m++) {
					bk_push(r, b16, rule->apply[m].index - nBacktrack, // position
					        b16, rule->apply[m].lookup.index,          // lookup index
					        bkover);
				}
				bk_push(cset, p16, r, bkover);
			}
			bk_push(root, p16, cset, bkover);
		} else {
			bk_push(root, p16, NULL, bkover);
		}
	}

	FREE(coverage);
	FREE(rcpg);
	return bk_build_Block(root);
}

caryll_Buffer *otfcc_build_chaining(const otl_Subtable *_subtable) {
	if (_subtable->chaining.type == otl_chaining_classified) {
		return otfcc_build_chaining_classes(_subtable);
	} else {
		return otfcc_build_chaining_coverage(_subtable);
	}
}

////// CONTEXTUAL

static caryll_Buffer *otfcc_build_contextual_coverage(const otl_Subtable *_subtable) {
	const subtable_chaining *subtable = &(_subtable->chaining);
	const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
	tableid_t nInput = rule->inputEnds - rule->inputBegins;
	tableid_t nSubst = rule->applyCount;
	reverseBacktracks(rule);

	bk_Block *root = bk_new_Block(b16, 3, // format
	                              bkover);

	bk_push(root, b16, nInput, bkover);
	bk_push(root, b16, nSubst, bkover);
	for (tableid_t j = rule->inputBegins; j < rule->inputEnds; j++) {
		bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
	}
	for (tableid_t j = 0; j < nSubst; j++) {
		bk_push(root, b16, rule->apply[j].index,  // position
		        b16, rule->apply[j].lookup.index, // lookup
		        bkover);
	}

	return bk_build_Block(root);
}

static caryll_Buffer *otfcc_build_contextual_classes(const otl_Subtable *_subtable) {
	const subtable_chaining *subtable = &(_subtable->chaining);

	otl_Coverage *coverage;
	NEW(coverage);
	coverage->numGlyphs = subtable->ic->numGlyphs;
	coverage->glyphs = subtable->ic->glyphs;

	bk_Block *root =
	    bk_new_Block(b16, 2,                                                   // format
	                 p16, bk_newBlockFromBuffer(Coverage.build(coverage)),     // coverage
	                 p16, bk_newBlockFromBuffer(ClassDef.build(subtable->ic)), // InputClassDef
	                 b16, subtable->ic->maxclass + 1, // ChainSubClassSetCnt
	                 bkover);

	glyphclass_t *rcpg;
	NEW(rcpg, subtable->ic->maxclass + 1);
	for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
		rcpg[j] = 0;
	}
	for (tableid_t j = 0; j < subtable->rulesCount; j++) {
		tableid_t ib = subtable->rules[j]->inputBegins;
		tableid_t startClass = subtable->rules[j]->match[ib]->glyphs[0].index;
		if (startClass <= subtable->ic->maxclass) rcpg[startClass] += 1;
	}

	for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
		if (rcpg[j]) {
			bk_Block *cset = bk_new_Block(b16, rcpg[j], // ChainSubClassRuleCnt
			                              bkover);
			for (tableid_t k = 0; k < subtable->rulesCount; k++) {
				otl_ChainingRule *rule = subtable->rules[k];
				glyphclass_t startClass = rule->match[rule->inputBegins]->glyphs[0].index;
				if (startClass != j) { continue; }
				reverseBacktracks(rule);
				tableid_t nInput = rule->inputEnds - rule->inputBegins;
				tableid_t nSubst = rule->applyCount;
				bk_Block *r = bk_new_Block(bkover);

				bk_push(r, b16, nInput, bkover);
				bk_push(r, b16, nSubst, bkover);
				for (tableid_t m = rule->inputBegins + 1; m < rule->inputEnds; m++) {
					bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
				}

				for (tableid_t m = 0; m < nSubst; m++) {
					bk_push(r, b16, rule->apply[m].index,     // position
					        b16, rule->apply[m].lookup.index, // lookup index
					        bkover);
				}
				bk_push(cset, p16, r, bkover);
			}
			bk_push(root, p16, cset, bkover);
		} else {
			bk_push(root, p16, NULL, bkover);
		}
	}

	FREE(coverage);
	FREE(rcpg);
	return bk_build_Block(root);
}

caryll_Buffer *otfcc_build_contextual(const otl_Subtable *_subtable) {
	if (_subtable->chaining.type == otl_chaining_classified) {
		return otfcc_build_contextual_classes(_subtable);
	} else {
		return otfcc_build_contextual_coverage(_subtable);
	}
}
