/****************************************************************************
** Leistungsorientierte Krankenanstaltenfinanzierung (LKF)                 **
** Scoring-C-Sourcen V2026.1                                               **
** (C) 2026 BMASGPK                                                        **
*****************************************************************************
** filter5.c - Basisfunktionen                                             **
**                                                                         **
**		 Alex.Holzer@chello.at                                             **
**       Bernhard.Pesec@dothealth.at                                       **
***************************************************************************/


struct LKF_FILTER {

	unsigned short fieldnr;
	char value[11];
	char compare_krit;
	struct LKF_FILTER *and, *or, *sub_ausdruck;

} ; /* Struktur für Filter-Möglichkeiten */

#include "lkfint5.h"
#include <ctype.h>

static const char *keywords[] = {0, 
								 "FLAG", "KANR", "AUFNR", "GEBDAT", "GESCHL",
								 "STAATB", "STAATW", "PLZW", "AUFART", "AUFDAT", 
								 "AUFFC", "ABTFC", "ENTART", "ENTDAT", "ENTFC", 
								 "HD", "ZD", "MEL", "KOST"};


#define LKF_FILTER_FLAG		1
#define LKF_FILTER_KANR     2
#define LKF_FILTER_AUFNR    3
#define LKF_FILTER_GEBDAT   4
#define LKF_FILTER_GESCHL   5
#define LKF_FILTER_STAATB   6
#define LKF_FILTER_STAATW   7
#define LKF_FILTER_PLZW     8
#define LKF_FILTER_AUFART   9
#define LKF_FILTER_AUFDAT  10
#define LKF_FILTER_AUFFC   11
#define LKF_FILTER_ABTFC   12
#define LKF_FILTER_ENTART  13
#define LKF_FILTER_ENTDAT  14
#define LKF_FILTER_ENTFC   15
#define LKF_FILTER_HD      16
#define LKF_FILTER_ZD      17
#define LKF_FILTER_MEL     18
#define LKF_FILTER_KOST    19

#define KRIT_EQUAL			0
#define KRIT_WILDCARD_EQUAL 1
#define KRIT_DIFF			2
#define KRIT_WILDCARD_DIFF  3
#define KRIT_LESS_EQUAL     4
#define KRIT_LESS           5
#define KRIT_GREATER		6
#define KRIT_GREATER_EQUAL  7
#define KRIT_SUBAUSDRUCK    8
#define KRIT_NEGATE         9

const char *string_krit[] = {"=", "=", "<>", "<>", "<=", "<", ">", ">=", "", "!"};

/*************************************************************************************
** Funktion lkf_filter_create: Liefert entsprechend dem mitgegebenen Text die       **
**    Filterstruktur zurück.                                                        **
*************************************************************************************/


static LKF_FILTER *create_single (LKF_CONTEXT *pContext, int nr)
{
	LKF_FILTER *pKrit;

	pKrit = (LKF_FILTER *) LKF_MALLOC(pContext, "create_single", sizeof (LKF_FILTER));
    if (!pKrit) return 0;
	pKrit->and = pKrit->or = pKrit->sub_ausdruck = 0;

	if (!nr) {
		pKrit->compare_krit = KRIT_SUBAUSDRUCK;
	}
	else if (nr == -1) {
		pKrit->compare_krit = KRIT_NEGATE;
	}
	else {
		pKrit->value[0] = 0;
		pKrit->compare_krit = KRIT_EQUAL;
		pKrit->fieldnr = (unsigned short) nr;
	}
	return pKrit;
}


static LKF_FILTER *parse_ausdruck (LKF_CONTEXT *pContext, const char **);

static void parse_blanks (const char **str)
{ /* erhöht str bis zum nächsten nicht-ignorierbaren Zeichen */
	char blank_chars[] = {' ', 10, 13, '\t', 0};
	while (**str && strchr (blank_chars, **str)) ++*str;
}

static LKF_FILTER *parse_single (LKF_CONTEXT *pContext, const char **str)
{ /* <single> ::= (<ausdruck>) | <fieldnr>|<flagname> =|<>|>|>=|<|<= <value> */

	unsigned int field_nr;
	char compare_krit;
	char value[11];
	unsigned int i;
	int wildcards;
	LKF_FILTER *pKrit;
	char *endptr;

	parse_blanks (str);

	if (**str == '(') {
		pKrit = create_single (pContext, 0);
        if (!pKrit) return 0;
		++*str;
		parse_blanks (str);
		pKrit->sub_ausdruck = parse_ausdruck (pContext, str);

		if (pKrit->sub_ausdruck) {
			parse_blanks (str);
			if (**str != ')') {
				lkf_filter_drop (pKrit);
				return 0;
			}
			++*str;
			return pKrit;
		}
		else {
			lkf_filter_drop (pKrit);
			return 0;
		}
	}

	/* Parse Feldnummer */
	if (**str >= '0' && **str <= '9') {
		field_nr = (unsigned int) strtoul (*str, &endptr, 10);
		*str = endptr;
	}

	/* Parse Feldbezeichnung */
	else if ((**str >= 'A' && **str <= 'Z') || (**str >= 'a' && **str <= 'z')) {

		const char *keyword_start = *str;
		i = 0;

		while ((**str >= 'A' && **str <= 'Z') || (**str >= 'a' && **str <= 'z')) {
		
			if (i > 8) {
				*str = keyword_start;
				return 0;
			}

			if (**str >= 'a' && **str <= 'z') value[i++] = (char) toupper (**str);
			else value[i++] = **str;
			++*str;
		}
		value[i] = 0;
		field_nr = 0;
		
		for (i = 1; !field_nr && i < 20; i++) 
			if (keywords[i] && !strcmp (keywords[i], value)) field_nr = i;

		if (!field_nr) {
			*str = keyword_start;
			return 0;
		}
	}
	else return 0;

	parse_blanks (str);

	/* Parse Vergleichskriterium */
	if (**str == '<') {
		++*str;
		if (**str == '>') {
			compare_krit = KRIT_DIFF;
			++*str;
		}
		else if (**str == '=') {
			compare_krit = KRIT_LESS_EQUAL;
			++*str;
		}
		else compare_krit = KRIT_LESS;
	}
	else if (**str == '>') {
		++*str;
		if (**str == '=') {
			++*str;
			compare_krit = KRIT_GREATER_EQUAL;
		}
		else compare_krit = KRIT_GREATER;
	}
	else if (**str == '=') {
		++*str;
		compare_krit = KRIT_EQUAL;
	}
	else return 0;

	parse_blanks (str);

	/* Parse Vergleichswert */
	wildcards = 0;

	i = 0;

	while ( (**str >= 'A' && **str <= 'Z') ||
			(**str >= 'a' && **str <= 'z') ||
			(**str >= '0' && **str <= '9') ||
			(**str == '*' || **str == '%')) {
	
		if (i >= 10) return 0;

		if (**str == '*' || **str == '%') {
			if (compare_krit == KRIT_EQUAL || compare_krit == KRIT_DIFF) wildcards = 1;
			else return 0;
		}

		if (**str >= 'a' && **str <= 'z') value[i++] = (char) toupper (**str);
		else							value[i++] = **str;
		++*str;
	}
	value[i] = 0;

	if (wildcards) {
		if (compare_krit == KRIT_EQUAL) compare_krit = KRIT_WILDCARD_EQUAL;
		if (compare_krit == KRIT_DIFF)  compare_krit = KRIT_WILDCARD_DIFF;
	}

	pKrit = create_single (pContext, (int) field_nr);
	if (pKrit==0) return 0;
	pKrit->compare_krit = compare_krit;

	if (!strcmp (value, "EMPTY")) value[0] = 0;
	else strcpy (pKrit->value, value);

	return pKrit;
}

static LKF_FILTER *parse_undausdruck (LKF_CONTEXT *pContext, const char **str)
{ /* <undausdruck> ::= <single> [ & <undausdruck>] */
	LKF_FILTER *pKrit;
	int negate;

	if (**str == '!') {
		++*str;
		parse_blanks (str);
		negate = 1;
	}
	else negate = 0;

	pKrit = parse_single (pContext, str);
	if (!pKrit) return 0;

	if (negate) {
		LKF_FILTER *pNegationKrit;
		pNegationKrit = create_single (pContext, -1);
		pNegationKrit->sub_ausdruck = pKrit;
		pKrit = pNegationKrit;
	}

	parse_blanks (str);

	if (**str == '&') {
		++*str;
		parse_blanks (str);
		pKrit->and = parse_undausdruck (pContext, str);
		if (pKrit->and) return pKrit;
		else {
			lkf_filter_drop (pKrit);
			return 0;
		}
	}
	else return pKrit;
}

static LKF_FILTER *parse_ausdruck (LKF_CONTEXT *pContext, const char **str)
{ /* <ausdruck> ::= <undausdruck> [+ <ausdruck>] */
	LKF_FILTER *pKrit;

	pKrit = parse_undausdruck (pContext, str);
	if (!pKrit) return 0;

	parse_blanks (str);

	if (**str == '+') {
		++*str;
		pKrit->or = parse_ausdruck (pContext, str);
		if (pKrit->or) return pKrit;
		else {
			lkf_filter_drop (pKrit);
			return 0;
		}
	}
	return pKrit;
}

_SCORING_EXPORT LKF_FILTER *lkf_filter_create2 (LKF_CONTEXT *pContext, const char **str)
{ /* <filter> ::= <ausdruck>. */
	LKF_FILTER *pKrit;
	pKrit = parse_ausdruck (pContext, str);
	if (!pKrit) return 0;

	parse_blanks (str);

	if (**str != '.') {
		lkf_filter_drop (pKrit);
		return 0;
	}
	++*str;
	return pKrit;
}

_SCORING_EXPORT LKF_FILTER *lkf_filter_create (const char **str) {
    return lkf_filter_create2(0, str);
}

/***********************************************************************************
** Funktion lkf_filter_drop: Gibt den Kriterien-Baum frei.                        **
***********************************************************************************/

_SCORING_EXPORT void lkf_filter_drop (LKF_FILTER *krit)
{
	if (krit) {
		if (krit->and) lkf_filter_drop (krit->and);
		if (krit->or) lkf_filter_drop (krit->or);
		LKF_FREE (0, "lkf_filter_drop", krit);
	}
}

/*******************************************************************************
** Funktion lkf_auf_filter: Überprüft, ob ein Aufenthalt dem mitgeg. Filter   **
**    entspricht.                                                             **
*******************************************************************************/

static int filter_check (LKF_AUFENTHALT *pAufenthalt, const LKF_FILTER *krit)
{
	int match;
	unsigned int repetition_anz, i;
	const char *cand; 
    int cand_len;

	if (krit->compare_krit == KRIT_SUBAUSDRUCK) {
		if (!krit->sub_ausdruck) match = 0;
		else match = filter_check (pAufenthalt, krit->sub_ausdruck);
	}
	else if (krit->compare_krit == KRIT_NEGATE) {
		if (!krit->sub_ausdruck) match = 1;
		else match = !filter_check (pAufenthalt, krit->sub_ausdruck);
	}
	else {
	
		switch (krit->fieldnr) {
		
			case 12: repetition_anz = pAufenthalt->abt_anz; break;
			case 17: repetition_anz = pAufenthalt->diag_anz; break;
			case 19: repetition_anz = pAufenthalt->mel_anz; break;
			default: repetition_anz = 1;
		
		}

		if (repetition_anz) match = -1; /* undecided */
		else if (krit->compare_krit == KRIT_DIFF || krit->compare_krit == KRIT_WILDCARD_DIFF) match = 1;
		else match = 0;

		for (i = 0; match == -1 && i < repetition_anz; i++) {
		
			switch (krit->fieldnr) {
			
				case  1: cand = &pAufenthalt->FLAG;      cand_len =  1; break;
				case  2: cand = pAufenthalt->KANR;		 cand_len =  4; break;
				case  3: cand = pAufenthalt->UCASE_PATID;		 cand_len = 10; break;
				case  4: cand = pAufenthalt->GEBDAT;	 cand_len =  8; break;
				case  5: cand = &pAufenthalt->GESCHL;	 cand_len =  1; break;
				case  6: cand = pAufenthalt->STAATB;	 cand_len =  3; break;
				case  7: cand = pAufenthalt->STAATW;	 cand_len =  3; break;
				case  8: cand = pAufenthalt->PLZW;		 cand_len =  6; break;
				case  9: cand = &pAufenthalt->AUFART;	 cand_len =  1; break;
				case 10: cand = pAufenthalt->AUFDAT;	 cand_len =  8; break;
				case 11: if (pAufenthalt->abt_anz) cand = pAufenthalt->abt[0].ABTFC;
						 else cand = "        ";
						 cand_len =  8; break;
				case 12: cand = pAufenthalt->abt[i].ABTFC; cand_len = 8; break;
				case 13: cand = &pAufenthalt->AUFART;	 cand_len =  1; break;
				case 14: cand = pAufenthalt->ENTDAT;     cand_len =  8; break;
				case 15: if (pAufenthalt->abt_anz) cand = pAufenthalt->abt[pAufenthalt->abt_anz-1].ABTFC;
						 else cand = "        ";
						 cand_len = 8; break;
				case 16: cand = pAufenthalt->HD;		 cand_len = 6; break;
				case 17: cand = pAufenthalt->diag[i].DIAG; cand_len = 6; break;
				case 18: cand = pAufenthalt->mel[i].MEL; cand_len = 4; break;
				case 19: cand = pAufenthalt->KOST; cand_len = 2; break;
				default: cand = " "; cand_len = 1; break;
			}

			switch (krit->compare_krit) {
				case KRIT_GREATER_EQUAL:
				case KRIT_GREATER:
				case KRIT_LESS:
				case KRIT_LESS_EQUAL:
					match = TrimCompare (cand, cand_len, krit->value, krit->compare_krit);
					if (!match && i < repetition_anz - 1) match = -1;
					
					break;
				case KRIT_EQUAL:
					match = TrimEqual (cand, cand_len, krit->value);
					if (!match && i < repetition_anz - 1) match = -1;
					break;

				case KRIT_DIFF:
					match = !TrimEqual (cand, cand_len, krit->value);
					if (match && i < repetition_anz - 1) match = -1;
					break;
				
				case KRIT_WILDCARD_EQUAL:
					match = TrimWildcardEqual (cand, cand_len, krit->value);
					if (!match && i < repetition_anz - 1) match = -1;
					break;

				case KRIT_WILDCARD_DIFF:
					match = !TrimWildcardEqual (cand, cand_len, krit->value);
					if (match && i < repetition_anz - 1) match = -1;
					break;

				default: match = 0; break;
			
			}		
		}
	}

	if (match) {
		if (krit->and) {
			if (filter_check (pAufenthalt, krit->and)) return 1;
			else if (krit->or) return filter_check (pAufenthalt, krit->or);
			else return 0;
		}
		else return 1;
	}
	else if (krit->or) return filter_check (pAufenthalt, krit->or);
	else return 0;
} 




_SCORING_EXPORT int lkf_auf_filter (LKF_AUFENTHALT *pAufenthalt, const LKF_FILTER *pFilter)
{
	return filter_check (pAufenthalt, pFilter);
}

/*******************************************************************************
** Funktion lkf_context_filter: Markiert die dem Filter entsprechenden Aufent-**
**    halte (flags=0: Filter anwenden, flags&1= Nur Aufenthalte hinzufügen,   **
**           flags&2: Nur Markierungen nicht passender Filter löschen,        **
**           flags&4: Negation - Aufenthalte, die nicht dem Filter ent-       **
**                    sprechen, sind zu markieren)                            **
*******************************************************************************/

_SCORING_EXPORT unsigned int lkf_context_filter (LKF_CONTEXT *pContext, const LKF_FILTER *pFilter, int flags)
{
	LKF_AUFENTHALT *pAufenthalt;
	unsigned int i, anz, count;
	int m;
	unsigned int ka_index;

	anz = pContext->patient_anz;
	count = 0;

	for (i = 0; i < anz; i++) {

		pAufenthalt = pContext->ppAufenthalt[i];

		if (pFilter) m = lkf_auf_filter (pAufenthalt, pFilter);
		else m = 1;

		if (flags & 4) m = 1 - m;
		if (m) ++count;	

		ka_index = lkf_search_kanr (pContext, pAufenthalt->KANR);
        if (ka_index == LKF_OUTOFMEMORY_UINTERROR) return 0;
        
		if (!m && (!(flags & 1))) {
			if (pAufenthalt->mark & 1) {
				pAufenthalt->mark &= ~1;
				--pContext->ka[ka_index].filter_anz;
			}
		}
		if (m && (!(flags & 2))) {
			if ((pAufenthalt->mark & 1) == 0) {
				pAufenthalt->mark |= 1;
				++pContext->ka[ka_index].filter_anz;
			}
		}

	}
	return count;
}

/*******************************************************************************
** Funktion lkf_context_get_marked_anz: Liefert die Anzahl markierter Aufent- **
**    halte.                                                                  **
*******************************************************************************/

_SCORING_EXPORT unsigned int lkf_context_get_marked_anz (const LKF_CONTEXT *pContext)
{
	unsigned int i, anz, count;

	anz = pContext->patient_anz;

	for (count = i = 0; i < anz; i++) {
		if (pContext->ppAufenthalt[i]->mark & 1) ++count;
	}
	
	return count;
}

_SCORING_EXPORT void lkf_context_mark_all (LKF_CONTEXT *pContext, int m)
{
	LKF_FILTER *pFilter;
	if (m) pFilter = create_single (pContext, -1);
	else pFilter = create_single (pContext, 0);

	lkf_context_filter (pContext, pFilter, 0);
	lkf_filter_drop (pFilter);
}

/******************************************************************************************************************
** Funktion lkf_context_getfiltertext: Liefert zu einem Filter den Text                                          **
******************************************************************************************************************/

static unsigned int filter_gettext (const LKF_FILTER *pFilter, char **ppBuf, unsigned int len)
{
	char this_criteria[1024];
	unsigned int this_len;
	if (pFilter->fieldnr < 20) {
		 sprintf (this_criteria, "%s%s%s", keywords[pFilter->fieldnr], string_krit[pFilter->compare_krit], pFilter->value);
	}
	else sprintf (this_criteria, "---");
	this_len = (unsigned int) strlen(this_criteria);
	if (this_len + 1< len) strcpy (*ppBuf, this_criteria);
	return this_len;
}

_SCORING_EXPORT unsigned int lkf_filter_gettext (const LKF_FILTER *pFilter, char *pBuf, unsigned int buflen)
{
	char *nextbuf = pBuf;
	unsigned int len = filter_gettext (pFilter, &nextbuf, buflen);
	if (len < buflen + 1) strcpy (pBuf + len, ".");
	len += 1;
	return len;
}
