/**********************************************************************
	"Copyright 1990-2026 Brian MacWhinney. Use is subject to Gnu Public License
	as stated in the attached "gpl.txt" file."
*/


#define CHAT_MODE 2

#include "cu.h"
#include "check.h"

#if !defined(UNX)
#define _main flo_main
#define call flo_call
#define getflag flo_getflag
#define init flo_init
#define usage flo_usage
#endif

#define IS_WIN_MODE FALSE
#include "mul.h"

#define FLO_UTT struct flo_utterance /* main utterance structure             */
FLO_UTT {
    char speaker[SPEAKERLEN];		/* code descriptor field of the turn	*/
    AttTYPE attSp[SPEAKERLEN];		/* Attributes assiciated with speaker name	*/
    char line[UTTLINELEN+1];		/* text field of the turn		*/ // found uttlinelen
    AttTYPE attLine[UTTLINELEN+1];	/* Attributes assiciated with text*/
    char tuttline[UTTLINELEN+1];	/* working copy of the text of the turn	*/
    FLO_UTT *nextutt;
} ;

extern char OverWriteFile;
extern struct tier *defheadtier;
extern char GExt[];

static char isFirstTime;
static char isMorFlo, isRFlo, isJFlo, isMFAFlo, isSpeakerSpecified, isAntConc, isKeepBullets;
static char leave_AT;
static char substitute_flag;	/* Flo line will be output in */
								/* addition to original main line */

void usage() {
	puts("FLO produces a %flo line");
	printf("Usage: flo [a cm cr d %s] filename(s)\n", mainflgs());
	puts("+a : do not remove @.. suffixes from words (default: change \"word@s\" to \"word\").");
	puts("+cm: filter main tier as \"mor\" does.");
	puts("+cr: filter main tier and remove speaker codes and utterance delimiters.");
	puts("+cjC: filter main tier for JSON comparison. (no C- speaker words, p- POS, s-Stem)");
	puts("+ca: create output for MFA aligner");
	puts("+cb: keep bullets in the output");
	puts("+d:  replaces the main tier with the simplified %flo tier in the output");
	puts("+d1: create FAVE formatted file");
	puts("+d2: create AntConc formatted .txt file with BOM");
	mainusage(TRUE);
}

void init(char first) {
	int i;

    if (first) {
		isFirstTime = TRUE;
		isMorFlo = FALSE;
		isRFlo = FALSE;
		isKeepBullets = FALSE;
		leave_AT = FALSE;
		isSpeakerSpecified = FALSE;
		substitute_flag = 0;
		stout = FALSE;
		onlydata = TRUE;
// defined in "mmaininit" and "globinit" - nomap = TRUE;
		if (isAntConc) {
			if (defheadtier != NULL) {
				if (defheadtier->nexttier != NULL)
					free(defheadtier->nexttier);
				free(defheadtier);
				defheadtier = NULL;
			}
		} else if (isJFlo == 2 || isJFlo == 3 || isJFlo == 4) {
			addword('\0','\0',"+beg|beg");
			addword('\0','\0',"+end|end");
			addword('\0','\0',"+cm|*");
			addword('\0','\0',"+unk|xxx");
			addword('\0','\0',"+unk|yyy");
			addword('\0','\0',"+*|www");
			addword('\0','\0',"++*");
		} else {
			if (isMFAFlo == FALSE) {
				addword('\0','\0',"+</?>");
				addword('\0','\0',"+</->");
				addword('\0','\0',"+<///>");
				addword('\0','\0',"+<//>");
				addword('\0','\0',"+</>");  // list R6 in mmaininit funtion in cutt.c
				addword('\0','\0',"+&*");
			}
			addword('\0','\0',"+xxx");
			addword('\0','\0',"+yyy");
			addword('\0','\0',"+www");
			addword('\0','\0',"+xxx@*");
			addword('\0','\0',"+yyy@*");
			addword('\0','\0',"+www@*");
			addword('\0','\0',"+-*");
			addword('\0','\0',"+#*");
			addword('\0','\0',"+(*.*)");
			mor_initwords();
		}
		OverWriteFile = TRUE;
    } else {
		if (isFirstTime) {
			isFirstTime = FALSE;
			if (isAntConc) {
				strcpy(GExt, ".txt");
				AddCEXExtension = "";
			} else {
				if (isRFlo || isJFlo == 1 || isMFAFlo) {
					if (!isSpeakerSpecified) {
						fputs("\nPlease specify at least one speaker tier code with \"+t\" option\n", stderr);
						cutt_exit(0);
					}
					defheadtier->nexttier = NEW(struct tier);
					if (defheadtier->nexttier == NULL) {
						fputs("ERROR: Out of memory.\n",stderr);
						cutt_exit(0);
					}
					strcpy(defheadtier->nexttier->tcode,"%");
					defheadtier->nexttier->include = FALSE;
					defheadtier->nexttier->pat_match = FALSE;
					defheadtier->nexttier->nexttier = NULL;
					substitute_flag = 1;
				}
				if (isMORSearch())
					linkMain2Mor = TRUE;
				if (isMorFlo) {
					addword('\0','\0',"+0");
					addword('\0','\0',"+<e>");
					for (i=0; GlobalPunctuation[i]; ) {
						if (GlobalPunctuation[i] == '!' ||
							GlobalPunctuation[i] == '?' ||
							GlobalPunctuation[i] == '.') 
							strcpy(GlobalPunctuation+i,GlobalPunctuation+i+1);
						else
							i++;
					}
				} else if (isJFlo != 2 && isJFlo != 3 && isJFlo != 4) {
					addword('\0','\0',"+0*");
					if (!linkMain2Mor)
						addword('\0','\0',"++*");
					addword('\0','\0',"+<#>");
					addword('\0','\0',"+unk|xxx");
					addword('\0','\0',"+unk|yyy");
					addword('\0','\0',"+*|www");
				}
			}
		}
	}
}

void getflag(char *f, char *f1, int *i) {
    f++;
    switch(*f++) {
		case 'a':
			leave_AT = TRUE;
			no_arg_option(f);
    		break;
		case 'c':
			if (*f == 'm' || *f == 'M')
				isMorFlo = TRUE;
			else if (*f == 'b' || *f == 'B')
				isKeepBullets = TRUE;
			else if (*f == 'r' || *f == 'R')
				isRFlo = TRUE;
			else if (*f == 'j' || *f == 'J') {
				if (*(f+1) == 'p' || *(f+1) == 'P') {
					isJFlo = 2;
					LocalTierSelect = TRUE;
				} else if (*(f+1) == 's' || *(f+1) == 'S') {
					isJFlo = 3;
					LocalTierSelect = TRUE;
				} else if (*(f+1) == 'h' || *(f+1) == 'H') {
					isJFlo = 4;
					LocalTierSelect = TRUE;
				} else if (*(f+1) == EOS ){
					isJFlo = 1;
				} else {
					fprintf(stderr,"Invalid argument for option (choose: 'p', 's' or 'h'): %s\n", f-2);
					cutt_exit(0);
				}
			} else if (*f == 'a' || *f == 'A') {
				isMFAFlo = TRUE;
				addword('s','i',"+&+*");
				addword('s','i',"+&-*");
			} else {
				fprintf(stderr,"Invalid argument for option (choose: 'm', 'r', 'j', 'a' or 'b'): %s\n", f-2);
				cutt_exit(0);
			}
    		break;
		case 'd':
			if (*f == '2') {
			} else if (*f == '1')
				substitute_flag = 2; /* Flo line will replace main line */
			else if (*f == EOS || *f == '0')
				substitute_flag = 1; /* Flo line will replace main line */
			else {
				fprintf(stderr,"Invalid argument for option %s\n", f-2);
				cutt_exit(0);
			}
    		break;
		case 't':
			if (*f == '*' || (*f != '%' && *f != '@'))
				isSpeakerSpecified = TRUE;
			if (uS.mStricmp(f, "%mor") == 0) {
				linkMain2Mor = TRUE;
			}
		default:
		maingetflag(f-2,f1,i);
		break;
    }
}

static FLO_UTT *freeUtts(FLO_UTT *p) {
	FLO_UTT *t;
	
	while (p != NULL) {
		t = p;
		p = p->nextutt;
		free(t);
	}
	return(NULL);
}

static FLO_UTT *add2Utts(FLO_UTT *root_utts) {
	char bf;
	int  i;
	FLO_UTT *utt;
	
	if (root_utts == NULL) {
		utt = NEW(FLO_UTT);
		if (utt == NULL) {
			fprintf(stderr,"Error: out of memory\n");
			root_utts = freeUtts(root_utts);
			cutt_exit(0);
		}
		root_utts = utt;
	} else {
		for (utt=root_utts; utt->nextutt != NULL; utt=utt->nextutt) ;
		if ((utt->nextutt=NEW(FLO_UTT)) == NULL) {
			fprintf(stderr,"Error: out of memory\n");
			root_utts = freeUtts(root_utts);
			cutt_exit(0);
		}
		utt = utt->nextutt;
	}
	utt->nextutt = NULL;
	if (isKeepBullets && substitute_flag) {
		bf = FALSE;
		for (i=0; utterance->line[i] != EOS; i++) {
			if (utterance->line[i] == HIDEN_C) {
				bf = !bf;
				if (!bf && isSpace(utterance->tuttline[i]))
					utterance->tuttline[i] = utterance->line[i];
			}
			if (bf && isSpace(utterance->tuttline[i]))
				utterance->tuttline[i] = utterance->line[i];
		}
	}
	att_cp(0L, utt->speaker, utterance->speaker, utt->attSp, utterance->attSp);
	att_cp(0L, utt->line, utterance->line, utt->attLine, utterance->attLine);
	strcpy(utt->tuttline, utterance->tuttline);
	return(root_utts);
}

static void filterAtSym(char *line) {
	long i, j;

	i = 0L;
	while (line[i] != EOS) {
		if (line[i] == '@') {
			if (isJFlo == 1)
				for (j=i; !uS.isskip(line,j,&dFnt,MBF) && line[j] != '+' && line[j] != EOS; j++) ;
			else if (isJFlo == 2)
				for (j=i; !uS.isskip(line,j,&dFnt,MBF) && line[j] != '+' && line[j] != EOS; j++) ;
			else if (isJFlo == 3)
				for (j=i; !uS.isskip(line,j,&dFnt,MBF) && line[j] != '+' && line[j] != EOS; j++) ;
			else if (isJFlo == 4)
				for (j=i; !uS.isskip(line,j,&dFnt,MBF) && line[j] != '+' && line[j] != EOS; j++) ;
			else
				for (j=i; !uS.isskip(line,j,&dFnt,MBF) && line[j] != '+' && line[j] != '-' && line[j] != EOS; j++) ;
			strcpy(line+i, line+j);
		} else
			i++;
	}
}

static void removeAngleBrackets(char *line) {
	long i;

	i = 0L;
	while (line[i] != EOS) {
		if ((i == 0 || line[i-1] != '+') && (line[i] == '<' || line[i] == '>')) {
			strcpy(line+i, line+i+1);
		} else
			i++;
	}
}

static void cleanupAnPercentFlo(char *line) {
	int i, j;

	i = 0;
	while (line[i] != EOS) {
		if (line[i] == '&' && (line[i+1] == '+' || line[i+1] == '-'))
			strcpy(line+i, line+i+2);
		else if (line[i] == '$' && isalnum(line[i-1])) {
			for (j=i+1; isalpha((int)line[j]); j++) ;
			if (j > i)
				strcpy(line+i, line+j);
			else
				i++;
		} else
			i++;
	}
}

static void outputUtts(FLO_UTT *root_utts) {
	int  i, j;
	char flo_tier_code[SPEAKERLEN+1];
	char isBulletFound;
	FNType Fname[FILENAME_MAX];
	long Beg, End;
	double DBeg, DEnd;
	FLO_UTT *utt;
	MORFEATS word_feats, *clitic, *feat;

	strcpy(flo_tier_code,"%flo:\t");
	if (linkMain2Mor) {
		spareTier1[0] = EOS;
		for (utt=root_utts; utt != NULL; utt=utt->nextutt) {
			if (uS.partcmp(utt->speaker, "%mor:", FALSE, TRUE)) {
				strcpy(spareTier1, utt->tuttline);
				if (!leave_AT)
					filterAtSym(spareTier1);
				removeAngleBrackets(spareTier1);
				removeDepTierItems(spareTier1);
				uS.remFrontAndBackBlanks(spareTier1);
				break;
			}
		}
		for (utt=root_utts; utt != NULL; utt=utt->nextutt) {
			if (utt->speaker[0] == '*') {
				if (!substitute_flag) { 
					printout(utt->speaker,utt->line,utt->attSp,utt->attLine,FALSE);
					if (spareTier1[0] != EOS)
						printout(flo_tier_code,spareTier1,NULL,NULL,TRUE);
				} else if (isRFlo || isJFlo || isMFAFlo) {
					for (i=0; spareTier1[i] != EOS; i++) {
						if (uS.IsUtteranceDel(spareTier1, i))
							spareTier1[i] = ' ';
					}
					uS.remFrontAndBackBlanks(spareTier1);
					if (isMFAFlo)
						cleanupAnPercentFlo(spareTier1);
					if (spareTier1[0] != EOS) {
						strcat(spareTier1, ".");
						printout(NULL,spareTier1,NULL,NULL,TRUE);
					}
				} else { 
					printout(utt->speaker,spareTier1,NULL,NULL,TRUE);
				}
			} else {
				if (uS.partcmp(utt->speaker, "%mor:", FALSE, TRUE)) {
					removeMainTierWords(utt->line);
					uS.remFrontAndBackBlanks(utt->line);
					printout(utt->speaker,utt->line,utt->attSp,NULL,TRUE);
				} else
					printout(utt->speaker,utt->line,utt->attSp,utt->attLine,FALSE);
			}
		}
	} else {
		for (utt=root_utts; utt != NULL; utt=utt->nextutt) {
			if (*utt->speaker == '*') {
				if (!leave_AT)
					filterAtSym(utt->tuttline);
				removeAngleBrackets(utt->tuttline);
				if (!substitute_flag) { 
					if (isAntConc) {
						i = 0;
						while (utt->line[i] != EOS) {
							if (utt->line[i] == HIDEN_C) {
								for (j=i+1; utt->line[j] != HIDEN_C && utt->line[j] != EOS; j++) ;
								if (utt->line[j] == HIDEN_C) {
									att_cp(0, utt->line+i, utt->line+j+1, utt->attLine+i, utt->attLine+j+1);
								} else
									i++;
							} else
								i++;
						}
						uS.remblanks(utt->line);
						fprintf(fpout, "%s", utt->speaker);
						fprintf(fpout, "%s\n", utt->line);
					} else {
						printout(utt->speaker,utt->line,utt->attSp,utt->attLine,FALSE);
						uS.remFrontAndBackBlanks(utt->tuttline);
						removeExtraSpace(utt->tuttline);
						printout(flo_tier_code,utt->tuttline,NULL,NULL,FALSE); // TRUE
					}
				} else if (isRFlo || isJFlo || isMFAFlo) {
					if (isRFlo == FALSE) {
						for (i=0; utt->tuttline[i] != EOS; i++) {
							if (uS.IsUtteranceDel(utt->tuttline, i) || utt->tuttline[i] == ',' || utt->tuttline[i] == ';')
								utt->tuttline[i] = ' ';
							else if (isJFlo && (utt->tuttline[i] == '\n' || utt->tuttline[i] == '\t'))
								utt->tuttline[i] = ' ';
						}
					}
					uS.remFrontAndBackBlanks(utt->tuttline);
					if (isMFAFlo)
						cleanupAnPercentFlo(utt->tuttline);
					if (utt->tuttline[0] != EOS) {
						if (isRFlo == FALSE && isJFlo == 0) {
							strcat(utt->tuttline, ".");
							removeExtraSpace(utt->tuttline);
							printout(NULL,utt->tuttline,NULL,NULL,FALSE); // TRUE
						} else {
							removeExtraSpace(utt->tuttline);
							printout(NULL,utt->tuttline,NULL,NULL,FALSE); // TRUE
						}
					} else if (isJFlo) {
						fprintf(fpout, "\n");
					}
				} else if (substitute_flag == 2) {
					for (i=strlen(utt->speaker)-1; (isSpace(utt->speaker[i]) || utt->speaker[i] == ':') && i >= 0; i--) ;
					utt->speaker[i+1] = EOS;
					fprintf(fpout, "%s\t%s\t", utt->speaker+1, utt->speaker+1);
					isBulletFound = FALSE;
					for (i=0; utt->line[i] != EOS; i++) {
						if (utt->line[i] == HIDEN_C) {
							Fname[0] = EOS;
							if (isdigit(utt->line[i+1])) {
								if (cMediaFileName[0] == EOS) {
									fprintf(stderr, "\nMissing %s header tier with media file name.\n\n", MEDIAHEADER);
									cutt_exit(0);
								}
								strcpy(Fname, cMediaFileName);
								if (getMediaTagInfo(utt->line+i, &Beg, &End)) {
									isBulletFound = TRUE;
								}
							} else {
								if (getOLDMediaTagInfo(utt->line+i, SOUNDTIER, Fname, &Beg, &End)) {
									isBulletFound = TRUE;
								} else if (getOLDMediaTagInfo(utt->line+i, REMOVEMOVIETAG, Fname, &Beg, &End)) {
									isBulletFound = TRUE;
								}
							}
						}
					}
					if (isBulletFound) {
						DBeg = (double)Beg;
						DEnd = (double)End;
						DBeg = DBeg / 1000.0000;
						DEnd = DEnd / 1000.0000;
						fprintf(fpout, "%.3lf\t%.3lf\t", DBeg, DEnd);
					} else {
						fprintf(stderr, "\nMissing bullet on tier:\n%s\n", utt->line);
						cutt_exit(0);
					}
					for (i=0; utt->tuttline[i] != EOS; i++) {
						if (utt->tuttline[i] == '\n')
							utt->tuttline[i] = ' ';
					}
					uS.remblanks(utt->tuttline);
					removeExtraSpace(utt->tuttline);
					fprintf(fpout, "%s\n", utt->tuttline);
				} else {
					uS.remFrontAndBackBlanks(utt->tuttline);
					printout(utt->speaker,utt->tuttline,utt->attSp,NULL,TRUE);
				}
			} else if (isJFlo == 2 || isJFlo == 3) {
				if (isRFlo == FALSE) {
					for (i=0; utt->tuttline[i] != EOS; i++) {
						if (uS.IsUtteranceDel(utt->tuttline, i) || utt->tuttline[i] == ',' || utt->tuttline[i] == ';')
							utt->tuttline[i] = ' ';
						else if (isJFlo && (utt->tuttline[i] == '\n' || utt->tuttline[i] == '\t'))
							utt->tuttline[i] = ' ';
					}
				}
				uS.remFrontAndBackBlanks(utt->tuttline);
//				removeExtraSpace(utt->tuttline);
//				printout(NULL,utt->tuttline,NULL,NULL,FALSE); // TRUE
				templineC3[0] = EOS;
				i = 0;
				while ((i=getword(utterance->speaker, utt->tuttline, templineC2, NULL, i))) {
					if (ParseWordMorElems(templineC2, &word_feats) != FALSE) {
						for (clitic=&word_feats; clitic != NULL; clitic=clitic->clitc) {
							if (clitic->compd != NULL) {
								for (feat=clitic; feat != NULL; feat=feat->compd) {
									if (templineC3[0] != EOS)
										strcat(templineC3, " ");
									if (isJFlo == 2) {
										if (feat->pos != NULL && feat->pos[0] != EOS)
											strcat(templineC3, feat->pos);
									} else {
										if (feat->stem != NULL && feat->stem[0] != EOS) {
											strcat(templineC3, feat->stem);
										} else
											strcat(templineC3,"_");
									}
								}
							} else {
								if (templineC3[0] != EOS)
									strcat(templineC3, " ");
								if (isJFlo == 2) {
									if (clitic->pos != NULL && clitic->pos[0] != EOS)
										strcat(templineC3, clitic->pos);
								} else {
									if (clitic->stem != NULL && clitic->stem[0] != EOS)
										strcat(templineC3, clitic->stem);
									else if (isJFlo == 2)
										strcat(templineC3,"_");
								}
							}
						}
						freeUpFeats(&word_feats);
					}
				}
				if (templineC3[0] != EOS) {
					printout(NULL,templineC3,NULL,NULL,FALSE); // TRUE
				} else
					fprintf(fpout, "\n");
			} else {
				if (isAntConc && utt->speaker[0] == '@') {
//					if (utt->line[0] == EOS)
//						uS.remblanks(utt->speaker);
					uS.remblanks(utt->line);
					uS.shiftright(utt->speaker, 1);
					utt->speaker[0] = '<';
					i = strlen(utt->line);
					utt->line[i++] = '>';
					utt->line[i++] = '\n';
					utt->line[i++] = EOS;
					fprintf(fpout, "%s", utt->speaker);
					fprintf(fpout, "%s", utt->line);
				} else
					printout(utt->speaker,utt->line,utt->attSp,utt->attLine,FALSE);
			}
		}
	}
}

void call()
{
	char isMORFound, isPHOFound, isSPFound;
	FLO_UTT *root_utts;
	
	if (isAntConc)
		fprintf(fpout, "%c%c%c", 0xef, 0xbb, 0xbf); // BOM UTF8

	isSPFound = FALSE;
	isMORFound = FALSE;
	isPHOFound = FALSE;
	/* build %flo: with proper trailing spaces */
	root_utts = NULL;
	currentatt = 0;
	currentchar = (char)getc_cr(fpin, &currentatt);
	while (getwholeutter()) {
		if (uS.isUTF8(utterance->speaker) || uS.isInvisibleHeader(utterance->speaker)) {
			if (isAntConc)
				continue;
		}
		if (utterance->speaker[0] == '*') {
			if (root_utts != NULL) {
				outputUtts(root_utts);
				root_utts = freeUtts(root_utts);
			}
			if (isJFlo == 2 || isJFlo == 3) {
				if (isMORFound == FALSE && isSPFound == TRUE)
					fprintf(fpout, "\n");
				isMORFound = FALSE;
			} else if (isJFlo == 4) {
				if (isPHOFound == FALSE && isSPFound == TRUE)
					fprintf(fpout, "\n");
				isPHOFound = FALSE;
			} else
				root_utts = add2Utts(root_utts);
			isSPFound = TRUE;
		} else if (isJFlo == 2 || isJFlo == 3) {
			if (uS.partcmp(utterance->speaker,"%mor:",FALSE,FALSE)) {
				root_utts = add2Utts(root_utts);
				isMORFound = TRUE;
			}
		} else if (isJFlo == 4) {
			if (uS.partcmp(utterance->speaker,"%pho:",FALSE,FALSE)) {
				root_utts = add2Utts(root_utts);
				isPHOFound = TRUE;
			}
		} else {
			root_utts = add2Utts(root_utts);
		}
	}
	if (root_utts != NULL) {
		outputUtts(root_utts);
		root_utts = freeUtts(root_utts);
	} else if (isJFlo == 2 || isJFlo == 3) {
		if (isMORFound == FALSE && isSPFound == TRUE)
			fprintf(fpout, "\n");
	} else if (isJFlo == 4) {
		if (isPHOFound == FALSE && isSPFound == TRUE)
			fprintf(fpout, "\n");
	}
}

CLAN_MAIN_RETURN main(int argc, char *argv[]) {
	int i;

    isWinMode = IS_WIN_MODE;
    CLAN_PROG_NUM = FLO;
    chatmode = CHAT_MODE;
    OnlydataLimit = 0;
    UttlineEqUtterance = FALSE;
	isAntConc = FALSE;
	isMFAFlo = FALSE;
	isJFlo = 0;
	for (i=0; i < argc; i++) {
		if (argv[i][0] == '-' || argv[i][0] == '+') {
			if (argv[i][1] == 'd' && argv[i][2] == '2')
				isAntConc = TRUE;
			else if (argv[i][1] == 'c' && (argv[i][2] == 'a' || argv[i][2] == 'A'))
				isMFAFlo = TRUE;
			else if (argv[i][1] == 'c' && (argv[i][2] == 'j' || argv[i][2] == 'J')) {
				if (argv[i][3] == 'p' || argv[i][3] == 'P')
					isJFlo = 2;
				else if (argv[i][3] == 's' || argv[i][3] == 'S')
					isJFlo = 3;
				else if (argv[i][3] == 's' || argv[i][3] == 'H')
					isJFlo = 4;
			}
		}
	}
    bmain(argc,argv,NULL);
}
