/* OTGrammar_ex_metrics.c
 *
 * Copyright (C) 2001-2004 Paul Boersma
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * pb 2001/11/25
 * pb 2002/07/16 GPL
 * pb 2003/03/31 removeConstraint
 * pb 2003/05/09 added Peripheral and MainNonfinal
 * pb 2004/01/14 added HeadNonfinal
 * pb 2004/01/25 added overtFormsHaveSecondaryStress
 */

#include "OTGrammar.h"

#define WSP  1
#define FtNonfinal  2
#define Iambic  3
#define Parse  4
#define FootBin  5
#define WFL  6
#define WFR  7
#define Main_L  8
#define Main_R  9
#define AFL  10
#define AFR  11
#define Nonfinal  12

#define Trochaic  13
#define FootBimoraic  14
#define FootBisyllabic  15
#define Peripheral  16
#define MainNonfinal  17
#define HeadNonfinal  18

#define NUMBER_OF_CONSTRAINTS  18

static char *constraintNames [1+NUMBER_OF_CONSTRAINTS] = { 0,
	"WSP", "FtNonfinal", "Iambic", "Parse", "FootBin", "WFL", "WFR", "Main-L",
	"Main-R", "AFL", "AFR", "Nonfinal", "Trochaic", "FtBimor", "FtBisyl", "Peripheral", "MainNonfinal", "HeadNonfinal" };

static void path (OTGrammar me, OTGrammarTableau tableau, long numberOfSyllables, int stress [],
	int startingSyllable, int footedToTheLeft_in [], int footedToTheRight_in [], unsigned long weightPattern,
	int parsing, int overtFormsHaveSecondaryStress)
{
	static char *syllable [] = { "L", "L1", "L2", "H", "H1", "H2" };
	static char *syllableWithoutSecondaryStress [] = { "L", "L1", "L", "H", "H1", "H" };
	int isyll, footedToTheLeft [10], footedToTheRight [10];
	/* Localize all arguments. */
	for (isyll = 1; isyll <= startingSyllable; isyll ++) {
		footedToTheLeft [isyll] = footedToTheLeft_in [isyll];
		footedToTheRight [isyll] = footedToTheRight_in [isyll];
	}
	for (isyll = startingSyllable + 1; isyll <= numberOfSyllables; isyll ++)
		footedToTheLeft [isyll] = footedToTheRight [isyll] = 0;
	if (startingSyllable > numberOfSyllables) {
		char output [100];
		if (parsing) {
			output [0] = '\0';
		} else {
			strcpy (output, "[");
			for (isyll = 1; isyll <= numberOfSyllables; isyll ++) {
				if (isyll > 1) strcat (output, " ");
				strcat (output, ( overtFormsHaveSecondaryStress ? syllable : syllableWithoutSecondaryStress )
						[stress [isyll] + ( (weightPattern & (1 << (numberOfSyllables - isyll))) != 0 ? 3 : 0 )]);
			}
			strcat (output, "] \\-> ");
		}
		strcat (output, "/");
		for (isyll = 1; isyll <= numberOfSyllables; isyll ++) {
			if (isyll > 1) strcat (output, " ");
			if (footedToTheRight [isyll] || ! footedToTheLeft [isyll] && stress [isyll] != 0) strcat (output, "(");
			strcat (output, syllable [stress [isyll] + ( (weightPattern & (1 << (numberOfSyllables - isyll))) != 0 ? 3 : 0 )]);
			if (footedToTheLeft [isyll] || ! footedToTheRight [isyll] && stress [isyll] != 0) strcat (output, ")");
		}
		strcat (output, "/");
		tableau -> candidates [++ tableau -> numberOfCandidates]. output = Melder_strdup (output); cherror
	} else {
		path (me, tableau, numberOfSyllables, stress, startingSyllable + 1,
			footedToTheLeft, footedToTheRight, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
		if (stress [startingSyllable] == 0 && startingSyllable < numberOfSyllables && stress [startingSyllable + 1] != 0) {
			footedToTheLeft [startingSyllable + 1] = TRUE;
			footedToTheRight [startingSyllable] = TRUE;
			path (me, tableau, numberOfSyllables, stress, startingSyllable + 1,
				footedToTheLeft, footedToTheRight, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
			footedToTheLeft [startingSyllable + 1] = FALSE;
			footedToTheRight [startingSyllable] = FALSE;
		}
		if (stress [startingSyllable] == 0 && startingSyllable > 1 && stress [startingSyllable - 1] != 0
		    && ! footedToTheLeft [startingSyllable - 1])
		{
			footedToTheRight [startingSyllable - 1] = TRUE;
			footedToTheLeft [startingSyllable] = TRUE;
			path (me, tableau, numberOfSyllables, stress, startingSyllable + 1,
				footedToTheLeft, footedToTheRight, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
		}
	}
end:
	return;
}

static void createOvertPatternTableau (OTGrammar me, long numberOfSyllables, int stress [], unsigned long weightPattern,
	int parsing, int overtFormsHaveSecondaryStress)
{
	OTGrammarTableau tableau;
	char input [100];
	int isyll, footedToTheLeft [10], footedToTheRight [10];
	if (parsing) {
		tableau = & my tableaus [++ my numberOfTableaus];
		strcpy (input, "[");
		for (isyll = 1; isyll <= numberOfSyllables; isyll ++) {
			static char *syllable [] = { "L", "L1", "L2", "H", "H1", "H2" };
			static char *syllableWithoutSecondaryStress [] = { "L", "L1", "L", "H", "H1", "H" };
			if (isyll > 1) strcat (input, " ");
			strcat (input, ( overtFormsHaveSecondaryStress ? syllable : syllableWithoutSecondaryStress ) 
					[stress [isyll] + ( (weightPattern & (1 << (numberOfSyllables - isyll))) != 0 ? 3 : 0 )]);
		}
		strcat (input, "]");
		tableau -> input = Melder_strdup (input);
		tableau -> candidates = NUMstructvector (OTGrammarCandidate, 1, 21); cherror
	} else {
		if (stress [1] == 1 && stress [2] == 0 && (numberOfSyllables < 3 || stress [3] == 0)
		    && (numberOfSyllables < 4 || stress [4] == 0) && (numberOfSyllables < 5 || stress [5] == 0)
		    && (numberOfSyllables < 6 || stress [6] == 0) && (numberOfSyllables < 7 || stress [7] == 0))
		{
			tableau = & my tableaus [++ my numberOfTableaus];
			strcpy (input, "|");
			for (isyll = 1; isyll <= numberOfSyllables; isyll ++) {
				static char *syllable [] = { "L", "H" };
				if (isyll > 1) strcat (input, " ");
				strcat (input, syllable [(weightPattern & (1 << (numberOfSyllables - isyll))) != 0 ? 1 : 0]);
			}
			strcat (input, "|");
			tableau -> input = Melder_strdup (input);
			tableau -> candidates = NUMstructvector (OTGrammarCandidate, 1, 3136); cherror
		} else {
			tableau = & my tableaus [my numberOfTableaus];
		}
	}
	for (isyll = 1; isyll <= numberOfSyllables; isyll ++)
		footedToTheLeft [isyll] = footedToTheRight [isyll] = 0;
	path (me, tableau, numberOfSyllables, stress, 1, footedToTheLeft, footedToTheRight, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
end:
	return;
}

OTGrammar OTGrammar_create_metrics (int parsing, int equal_footForm_wsp, int trochaicityConstraint, int includeFootBimoraic, int includeFootBisyllabic,
	int includePeripheral, int nonfinalityConstraint, int overtFormsHaveSecondaryStress)
{
	long icons, itab, icand;
	int numberOfSyllables, mainStressed, secondary1, secondary2, secondary3, secondary4, secondary5, secondary6;
	OTGrammar me = new (OTGrammar); cherror
	my constraints = NUMstructvector (OTGrammarConstraint, 1, my numberOfConstraints = NUMBER_OF_CONSTRAINTS); cherror
	for (icons = 1; icons <= NUMBER_OF_CONSTRAINTS; icons ++) {
		OTGrammarConstraint constraint = & my constraints [icons];
		constraint -> name = Melder_strdup (constraintNames [icons]); cherror
		constraint -> ranking = 100.0;
	}
	if (equal_footForm_wsp >= 2) {
		/* Foot form constraints high. */
		my constraints [FtNonfinal]. ranking = 101.0;
		my constraints [Iambic]. ranking = 101.0;
		my constraints [Trochaic]. ranking = -1e9;
	}
	if (equal_footForm_wsp == 3) {
		/* Quantity sensitivity high, foot form constraints in the second stratum. */
		my constraints [WSP]. ranking = 102.0;
	}
	my tableaus = NUMstructvector (OTGrammarTableau, 1, parsing ? 3824 : 62); cherror
	for (numberOfSyllables = 2; numberOfSyllables <= 7; numberOfSyllables ++) {
		unsigned long weightPattern, maximumWeightPattern = numberOfSyllables <= 5 ? (1 << numberOfSyllables) - 1 : 0;
		for (weightPattern = 0; weightPattern <= maximumWeightPattern; weightPattern ++) {
			for (mainStressed = 1; mainStressed <= numberOfSyllables; mainStressed ++) {
				int stress [10];
				stress [mainStressed] = 1;
				for (secondary1 = FALSE; secondary1 <= TRUE; secondary1 ++) {
					stress [mainStressed <= 1 ? 2 : 1] = secondary1 ? 2 : 0;
					if (numberOfSyllables == 2) {
						createOvertPatternTableau (me, 2, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
					} else for (secondary2 = FALSE; secondary2 <= TRUE; secondary2 ++) {
						stress [mainStressed <= 2 ? 3 : 2] = secondary2 ? 2 : 0;
						if (numberOfSyllables == 3) {
							createOvertPatternTableau (me, 3, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
						} else for (secondary3 = FALSE; secondary3 <= TRUE; secondary3 ++) {
							stress [mainStressed <= 3 ? 4 : 3] = secondary3 ? 2 : 0;
							if (numberOfSyllables == 4) {
								createOvertPatternTableau (me, 4, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
							} else for (secondary4 = FALSE; secondary4 <= TRUE; secondary4 ++) {
								stress [mainStressed <= 4 ? 5 : 4] = secondary4 ? 2 : 0;
								if (numberOfSyllables == 5) {
									createOvertPatternTableau (me, 5, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
								} else for (secondary5 = FALSE; secondary5 <= TRUE; secondary5 ++) {
									stress [mainStressed <= 5 ? 6 : 5] = secondary5 ? 2 : 0;
									if (numberOfSyllables == 6) {
										createOvertPatternTableau (me, 6, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
									} else for (secondary6 = FALSE; secondary6 <= TRUE; secondary6 ++) {
										stress [mainStressed <= 6 ? 7 : 6] = secondary6 ? 2 : 0;
										createOvertPatternTableau (me, 7, stress, weightPattern, parsing, overtFormsHaveSecondaryStress); cherror
									}
								}
							}
						}
					}
				}
			}
		}
	}
	/* Compute violation marks. */
	for (itab = 1; itab <= my numberOfTableaus; itab ++) {
		OTGrammarTableau tableau = & my tableaus [itab];
		for (icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
			OTGrammarCandidate candidate = & tableau -> candidates [icand];
			int depth;
			char *firstSlash = strchr (candidate -> output, '/');
			char *lastSlash = & candidate -> output [strlen (candidate -> output) - 1];
			char *p, *q;
			candidate -> marks = NUMivector (1, candidate -> numberOfConstraints = NUMBER_OF_CONSTRAINTS); cherror
			/* Violations of WSP: count all H not followed by 1 or 2. */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == 'H' && ! (p [1] == '1' || p [1] == '2'))
					candidate -> marks [WSP] ++;
			}
			/* Violations of FtNonfinal: count all heads followed by ). */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if ((p [0] == '1' || p [0] == '2') && p [1] == ')')
					candidate -> marks [FtNonfinal] ++;
			}
			/* Violations of Iambic: count all heads not followed by ). */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if ((p [0] == '1' || p [0] == '2') && p [1] != ')')
					candidate -> marks [Iambic] ++;
			}
			/* Violations of Parse and Peripheral: count all syllables not between (). */
			depth = 0;
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == '(') depth ++;
				else if (p [0] == ')') depth --;
				else if ((p [0] == 'H' || p [0] == 'L') && depth != 1) {
					candidate -> marks [Parse] ++;
					if (p != firstSlash + 1 && p != lastSlash - 1)
						candidate -> marks [Peripheral] ++;
				}
			}
			/* Violations of FootBin: count all (L1) and (L2). */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == 'L' && p [-1] == '(' && (p [1] == '1' || p [1] == '2') && p [2] == ')')
					candidate -> marks [FootBin] ++;
			}
			/* Violations of WFL: count all initial / not followed by (. */
			if (firstSlash [1] != '(')
				candidate -> marks [WFL] = 1;
			/* Violations of WFR: count all final / not preceded by ). */
			if (lastSlash [-1] != ')')
				candidate -> marks [WFR] = 1;
			/* Violations of Main_L: count syllables from foot containing X1 to left edge. */
			for (p = strchr (firstSlash, '1'); *p != '('; p --) { }
			for (; p != firstSlash; p --) {
				if (p [0] == 'H' || p [0] == 'L')
					candidate -> marks [Main_L] ++;
			}
			/* Violations of Main_R: count syllables from foot containing X1 to right edge. */
			for (p = strchr (firstSlash, '1'); *p != ')'; p ++) { }
			for (; p != lastSlash; p ++) {
				if (p [0] == 'H' || p [0] == 'L')
					candidate -> marks [Main_R] ++;
			}
			/* Violations of AFL: count syllables from every foot to left edge. */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == '(') {
					for (q = p; q != firstSlash; q --) {
						if (q [0] == 'H' || q [0] == 'L')
							candidate -> marks [AFL] ++;
					}
				}
			}
			/* Violations of AFR: count syllables from every foot to right edge. */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == ')') {
					for (q = p; q != lastSlash; q ++) {
						if (q [0] == 'H' || q [0] == 'L')
							candidate -> marks [AFR] ++;
					}
				}
			}
			/* Violations of Nonfinal: count all final / preceded by ). */
			if (lastSlash [-1] == ')')
				candidate -> marks [Nonfinal] = 1;
			/* Violations of Trochaic: count all heads not preceded by (. */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if ((p [0] == '1' || p [0] == '2') && p [-2] != '(')
					candidate -> marks [Trochaic] ++;
			}
			/* Violations of FootBimoraic: count weight between (). */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if (p [0] == '(') {
					int weight = 0;
					for (p ++; p [0] != ')'; p ++) {
						if (p [0] == 'H') weight += 2;
						else if (p [0] == 'L') weight += 1;
					}
					if (weight != 2) candidate -> marks [FootBimoraic] ++;
				}
			}
			/* Violations of FootBisyllabic: count all (L1) and (L2) and (H1) and (H2). */
			for (p = firstSlash + 1; p != lastSlash; p ++) {
				if ((p [0] == 'L' || p [0] == 'H') && p [-1] == '(' && (p [1] == '1' || p [1] == '2') && p [2] == ')')
					candidate -> marks [FootBisyllabic] ++;
			}
			/* Violations of MainNonfinal: count all final / preceded by ) preceded by 1 in the same foot. */
			if (lastSlash [-1] == ')') {
			    for (p = lastSlash - 2; ; p --) {
			    	if (p [0] == '2') break;
			    	if (p [0] == '1') {
						candidate -> marks [MainNonfinal] = 1;
						break;
					}
				}
			}
			/* Violations of HeadNonfinal: count all final / preceded by ) directly preceded by 1, plus MainNonfinal. */
			if (lastSlash [-1] == ')') {
				if (lastSlash [-2] == '1') {
					candidate -> marks [HeadNonfinal] = 2;
				} else {
				    for (p = lastSlash - 2; ; p --) {
				    	if (p [0] == '2') break;
				    	if (p [0] == '1') {
							candidate -> marks [HeadNonfinal] = 1;
							break;
						}
					}
				}
			}
		}
	}
	OTGrammar_checkIndex (me);
	OTGrammar_newDisharmonies (me, 0.0);
	if (trochaicityConstraint == 1) {
		OTGrammar_removeConstraint (me, "Trochaic");
	} else {
		OTGrammar_removeConstraint (me, "FtNonfinal");
	}
	if (! includeFootBimoraic) OTGrammar_removeConstraint (me, "FtBimor");
	if (! includeFootBisyllabic) OTGrammar_removeConstraint (me, "FtBisyl");
	if (! includePeripheral) OTGrammar_removeConstraint (me, "Peripheral");
	if (nonfinalityConstraint == 1) {
		OTGrammar_removeConstraint (me, "MainNonfinal");
		OTGrammar_removeConstraint (me, "HeadNonfinal");
	} else if (nonfinalityConstraint == 2) {
		OTGrammar_removeConstraint (me, "HeadNonfinal");
		OTGrammar_removeConstraint (me, "Nonfinal");
	} else {
		OTGrammar_removeConstraint (me, "MainNonfinal");
		OTGrammar_removeConstraint (me, "Nonfinal");
	}
end:
	iferror forget (me);
	return me;
}

/* End of file OTGrammar_ex_metrics.c */
