<cfscript>
/* Все глобальные функции экспортированы в request scope */
function plain2htm(s) {
	return replace(replace(s, chr(13),'',"ALL"),chr(10),'<br/>', "ALL"); 
}
request.plain2htm = plain2htm;

function htm2plain(s) {
	return replaceNoCase(s, '<br/>', '#chr(13)##chr(10)#', "ALL"); 
}
request.htm2plain = htm2plain;

function cleanHtm(s) {
	return replaceList(s, '<,>,"', '&lt;,&gt;,&quot;'); 
}
request.cleanHtm = cleanHtm;

function clean4CDATA(s) {
	LF = chr(10);
	return replaceNoCase(replaceList(s, '[,]', '&91;,&93;'),"<br>",LF,"ALL"); 
}
request.clean4CDATA = clean4CDATA;

function numFmt(num, decimalPlaces = 0){
	var sFormat = ",";
	if (decimalPlaces GT 0) {sFormat = sFormat & "." & repeatString("0", decimalPlaces);}
	return replace(NumberFormat(num, sFormat), ",", " ", "ALL");
}
request.numFmt = numFmt;

function nFmt(num, decimalPlaces = 2){
	if (num EQ 0) return "";
	return numFmt(num, decimalPlaces);
}
request.nFmt = nFmt;

function dateFmt(date, sFormat = 'YYYY-MM-DD'){
	return dateFormat(date, sFormat);
}
request.dateFmt = dateFmt;

function dateTimeFmt(date, sDateFormat = 'YYYY-MM-DD', sTimeFormat='HH:MM'){
	return dateFormat(date, sDateFormat) & ' ' & timeFormat(date, sTimeFormat);
}
request.dateTimeFmt = dateTimeFmt;

function timeFmt(date, sFormat = 'HH:MM'){
	return timeFormat(date, sFormat);
}
request.timeFmt = timeFmt;

//////////////////////////////////////////////////////
function usrAuthenticated() {
	return structKeyExists(request, "auth");
}
request.usrAuthenticated = usrAuthenticated;
/////////////////////////////////////////////////////

function filterOn(filter) {
	return (isStruct(filter) AND NOT structIsEmpty(filter)); 
}
request.filterOn = filterOn;

function stripWhiteSpace(sNumber) {
	return reReplace(sNumber, "[[:space:]]", "", "ALL");
}
request.stripWhiteSpace = stripWhiteSpace;
/*
// never used
function cleanNumber(sNumber) {
	var s = reReplace(sNumber, "[[:space:]]", "", "ALL");
	if ( find(",", s) )	{ //watch argument order!!!
		// assume Russian format with decimal comma and spaces for thousand separator
		return replace(s, ",", ".");
	} else {
		// assume English format with decimal point and commas for thousand separator
		return replace(s, ",", "", "ALL");
	}				
}
request.cleanNumber = cleanNumber;
*/

/*https://cflib.org/udf/queryColumnToArray*/
/**
 * Takes a selected column of data from a query and converts it into an array.
 * 
 * @param query      The query to scan. (Required)
 * @param column      The name of the column to return data from. (Required)
 * @return Returns an array. 
 * @author Peter J. Farrell (pjf@maestropublishing.com) 
 * @version 1, July 22, 2005 
 */
function queryColumnToArray(qry, column) {
    var arr = arrayNew(1);
    var ii = "";
    var loop_len = arguments.qry.recordcount;
    for (ii=1; ii lte loop_len; ii=ii+1) {
        arrayAppend(arr, arguments.qry[arguments.column][ii]);
    } 
    return arr;
}
request.queryColumnToArray = queryColumnToArray;

/* ************************************* */

function loginPasswordPolicyCheck(usr_id, login, passwd, passwd2) { 
	msg = "";
	// для старого логина длина не проверяется, все равно ничего не поделаешь
	if (NOT(usr_id GT 0) AND len(login) LE 3) { 
		msg="Слишком короткий логин (минимальная длина логина 3 символа)"; 
	} else {
		if (passwd NEQ passwd2) {
			msg="Пароль и подтверждение не совпадают";
		} else { 
			if (len(passwd) LT "8") msg="Слишком короткий пароль (минимальная длина пароля 8 символов)";
		}
	}
	return msg;
}
request.loginPasswordPolicyCheck = loginPasswordPolicyCheck;

function i18(label,label_en=label) {
	//примитивная 2-язычная версия
	if (request.language EQ 'en') {
		return label_en;
	} else {
		return label;
	};  
}
request.i18 = i18;

function passwordHashCreate(password) {
	return GenerateArgon2Hash('#ARGUMENTS.password#', 'argon2id', 1, 16000, 2); 
}
request.passwordHashCreate = passwordHashCreate;

function passwordHashCheck(password, passwordHash) {
	return Argon2CheckHash('#ARGUMENTS.password#', ARGUMENTS.passwordHash, 'argon2id'); 
}
request.passwordHashCheck = passwordHashCheck;
/*
function skuCode(area_code="", abstract_service_code="") {
	var s="#arguments[1]#.#arguments[2]#";
	if (arrayLen(arguments) GE 3) {
		if (len(arguments[3]) GT 0) {
			s="#s#.#arguments[3]#";
		} else {
			s="#s#.0";
		};
	};
	if (arrayLen(arguments) GE 4) {
		if (len(arguments[4]) GT 0) {
			s="#s#.#arguments[4]#"
		} 
	};
	if (arrayLen(arguments) GE 5) { 
		var pricingModelSuffix=arguments[5];		
		s="#s#-#pricingModelSuffix#";
	} 
	return s;
}
request.skuCode = skuCode;
*/
function roundSafe(a, precision) {
	return (isNumeric(a) AND isNumeric(precision)) ? round(a,precision) : a;
}
request.roundSafe = roundSafe;

function timeUnitLabel(n, value=0, locale="ru") { 
	var labels = ["сек.","мин.","ч.","д.","мес."];
	if (n LE 5 AND n GE 1) {return labels[n];}
	else if (n EQ 6) { // в зависимости от числа - "год" г. или "лет" л.
		var m = value MOD 100;
		if (m GE 11 AND m LE 20) {
			return "л.";
		}
		m = m MOD 10;
		if (m GE 1 AND m LE 4) {
			return "г.";
		}
		return "л.";
	} 
	throw (message="timeUnitLabelRu: index out of bounds", detail="Index must be from 1 to 6");
}
request.timeUnitLabel = timeUnitLabel;

function dateDiffVerbose(dateFrom, dateTo, maxParts=6, maxPrecision=1, locale="ru") {
// returns formatted datetime interval duration, e.g. 1 hour 5 minutes or 3 years 0 months 12 days 
//(currently, only Russian supported)
//
//maxParts specifies, how many parts to display
//maxPrecision starts froms seconds: 1=seconds, 2=minutes, 3=hours, 4=days, 5=months, 6=years
//if maxPrecision specified is greater than given time interval, output is nothing 
//locale=ru just for interface, not used
//attention: truncates the tail, not rounds
//example: request.dateDiffVerbose(Now(),Now()+373.1234567, 3, 1) produces 1 г. 0 мес. 8 д.
//example: request.dateDiffVerbose(Now(),Now()+373.1234567, 6, 1) produces 1 г. 0 мес. 8 д. 2 ч. 57 мин. 46 сек.
//intended common usage: request.dateDiffVerbose(dateFrom, dateTo, 2, 1) - prints 2 senior parts of the interval, 
//with presision not better than second
//
// this is not the same task as converting number of seconds to days-hours-minutes-seconds, 
// because months contain different number of days, and days number of february depends on year
	try {
		if (NOT (isDate(dateFrom) AND isDate(dateTo))) return "";
		var UNIT_CODES = ["s","n","h","d","m","yyyy"];
		var start = dateFrom;
		var finish = dateTo;
		
		var sOut = "";
		var nUnits = 0;	
		var nPrintedPositions = 0;
		var printStart = false;
		
		for (i=ArrayLen(UNIT_CODES); i GT 0; i=i-1) {	
			nUnits = dateDiff(UNIT_CODES[i], start, finish);
			start = dateAdd(UNIT_CODES[i], nUnits, start);
			if (nPrintedPositions GT 0) {nPrintedPositions=nPrintedPositions+1;} //print started, so increment position
			if (nPrintedPositions GT maxParts) {break;}
			if (i LT maxPrecision) {break;}
			if (nUnits NEQ 0 AND nPrintedPositions EQ 0) {nPrintedPositions=1;}
			if (nPrintedPositions GT 0) {
				if (len(sOut)) {sOut=sOut & " ";}
				sOut=sOut & nUnits & " " & request.timeUnitLabel(i, nUnits, "ru");
			}
		}	
		return sOut;
	} catch(e) {
		return "";
	}
}
request.dateDiffVerbose = dateDiffVerbose;

/*
function secondsToDHMS(nSeconds,partsToKeep) {
//format seconds to days-hours-minutes-seconds and truncate to specified precision
 { 
        days = n / (24 * 3600); 
      
        n = n % (24 * 3600); 
        hours = n / 3600; 
      
        n %= 3600; 
        minutes = n / 60 ; 
      
        n %= 60; 
        seconds = n; 
          
        return days & " " & "days " 
			& hours & " " & "hours " 
			& minutes & " " & "minutes " 
			& seconds & " " & "seconds "); 
    }
}
*/
/**
 * Formats a JSON string with indents and new lines.
 * v1.0 by Ben Koshy (modified)
 * 
 * @param str      JSON string (Required)
 * @return Returns a string of indent-formated JSON 
 * @author Ben Koshy (cf@animex.com) 
 * @version 0, September 16, 2012 
 */
// formatJson() :: formats and indents JSON string
// based on blog post @ http://ketanjetty.com/coldfusion/javascript/format-json/
// modified for CFScript By Ben Koshy @animexcom
// usage: result = formatJson('STRING TO BE FORMATTED') OR result = formatJson(StringVariableToFormat);

// генерирует html разметку, чтобы не использовать <pre> и выровнять пробелы
// содержит ошибку: делает перевод строки после запятой в строке в кавычках
public string function formatJsonString(str) {
    var fjson = '';
    var pos = 0;
    var strLen = len(arguments.str);
    var indentStr = '&nbsp; '; // Adjust Indent Token If you Like (замыкающий пробел - для выравнивания при пробелах после запятой)
    var newLine = '<br/>'; // Adjust New Line Token If you Like <BR> 
    
    for (var i=1; i<=strLen; i++) { //fix: <=
        var char = mid(arguments.str,i,1);
        
        if (char == '}' || char == ']') {
            fjson &= newLine;
            pos = pos - 1;
            
            for (var j=1; j <= pos; j++) {
                fjson &= indentStr;
            }
        }
        
        fjson &= char;    
        
        if (char == '{' || char == '[' || char == ',') {
            fjson &= newLine;
            
            if (char == '{' || char == '[') {
                pos = pos + 1;
            }
            
            for (var k=1; k <= pos; k++) {
                fjson &= indentStr;
            }
        }
    }
    
    return fjson;
}
request.formatJsonString = formatJsonString;
//нужна еще функция, которая сериализует структуру как pretty printed json. См. https://github.com/bennadel/JsonSerializer.cfc (все не совсем просто, например, не заранее не определен формат даты)
</cfscript>

<cffunction name=structGetKey>
	<cfargument name="struct" type="struct"/>
	<cfargument name="key" type="string"/>
	<cfargument name="default" type="string" default=""/>
	
	<cftry>
		<cfif StructKeyExists(ARGUMENTS.struct, ARGUMENTS.key)>
			<cfreturn structFind(ARGUMENTS.struct, ARGUMENTS.key)/>
		<cfelse>
			<cfreturn ARGUMENTS.default/>
		</cfif>
		<cfcatch type="any">
			<cfreturn ARGUMENTS.default/>
		</cfcatch>
	</cftry>
	<!--- можно проще - просто обработать исключение. вряд ли будет сильно хуже --->
</cffunction>
<cfset request.structGetKey = structGetKey/>

<cffunction name="setError">
	<cfargument name="container" type="struct"/>
	<cfargument name="message" type="string"/>
	
	<cfset arguments.container.errorState=true/>
	<cfset arguments.container.errorMessage=arguments.message/>
</cffunction>
<cfset request.setError = setError/>

<cffunction name="snake2camel" 
	access="public" 
	returntype="any" 
	output="false"
	hint="convert snake style name to camel style name">	
	<cfargument name="snake" type="string" required="true" />
	<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>	
</cffunction>
<cfset request.snake2camel = snake2camel/>

<cffunction name="camel2snake" 
	access="public" 
	returntype="any" 
	output="false"
	hint="convert camel style name to snake style name">	
	<cfargument name="snake" type="string" required="true" />
	<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>	
</cffunction>
<cfset request.camel2snake = camel2snake/>

