// Copyright (C) 2010-2023 Combodo SARL
//
//   This file is part of iTop.
//
//   iTop is free software; you can redistribute it and/or modify
//   it under the terms of the GNU Affero General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   iTop 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 Affero General Public License for more details.
//
//   You should have received a copy of the GNU Affero General Public License
//   along with iTop. If not, see <http://www.gnu.org/licenses/>

// ID of the (hidden) form field used to store the JSON representation of the
// object being edited in this page
var sJsonFieldId = 'json_object';

// The memory representation of the object
var oObj = {};

// Mapping between the fields of the form and the attribute of the current object
// If aFieldsMap[2] contains 'foo' it means that oObj.foo corresponds to the field
// of Id 'att_2' in the form 
var aFieldsMap = new Array;

window.bInSubmit = false; // For handling form cancellation via OnBeforeUnload events

// Update the whole object from the form and also update its
// JSON (serialized) representation in the (hidden) field
function UpdateObjectFromForm(aFieldsMap, oObj)
{
	for(i=0; i<aFieldsMap.length; i++)
	{
		var oElement = document.getElementById('att_'+i);
		var sFieldName = aFieldsMap[i];
		oObj['m_aCurrValues'][sFieldName] = oElement.value;
		sJSON = JSON.stringify(oObj);
		var oJSON = document.getElementById(sJsonFieldId);
		oJSON.value = sJSON;
	}
	return oObj;
}

// Update the specified field from the current object
function UpdateFieldFromObject(idField, aFieldsMap, oObj)
{
	var oElement = document.getElementById('att_'+idField);
	oElement.value = oObj['m_aCurrValues'][aFieldsMap[idField]];
}
// Update all the fields of the Form from the current object
function UpdateFormFromObject(aFieldsMap, oObj)
{
	for(i=0; i<aFieldsMap.length; i++)
	{
		UpdateFieldFromForm(i, aFieldsMap, oObj);
	}
}

// This function is meant to be called from the AJAX page
// It reloads the object (oObj) from the JSON representation
// and also updates the form field that contains the JSON
// representation of the object
function ReloadObjectFromServer(sJSON)
{
	//console.log('JSON value:', sJSON);
	var oJSON = document.getElementById(sJsonFieldId);
	oJSON.value = sJSON;
	oObj = JSON.parse( '(' + sJSON + ')' );
	return oObj;	
}

function GoToStep(iCurrentStep, iNextStep)
{
	var oCurrentStep = document.getElementById('wizStep'+iCurrentStep);
	if (iNextStep > iCurrentStep)
	{
		// Check the values when moving forward
		if (CheckFields('wizStep'+iCurrentStep, true))
		{
			oCurrentStep.style.display = 'none';
			ActivateStep(iNextStep);
		}
	}
	else
	{
		oCurrentStep.style.display = 'none';
		ActivateStep(iNextStep);
	}
}

function ActivateStep(iTargetStep)
{
	UpdateObjectFromForm(aFieldsMap, oObj);
	var oNextStep = document.getElementById('wizStep'+(iTargetStep));
	window.location.href='#step'+iTargetStep;
	// If a handler for entering this step exists, call it
	if (typeof(this['OnEnterStep'+iTargetStep]) == 'function')
	{
		eval( 'OnEnterStep'+iTargetStep+'();');
	}
	oNextStep.style.display = '';
	G_iCurrentStep = iTargetStep;
	//$('#wizStep'+(iTargetStep)).block({ message: null });
}

function OnUnload(sTransactionId, sObjClass, iObjKey, sToken)
{
	if (!window.bInSubmit)
	{
		// If it's not a submit, then it's a "cancel" (Pressing the Cancel button, closing the window, using the back button...)
		var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php';
		var oFormData = new FormData();
		oFormData.append('operation', 'on_form_cancel');
		oFormData.append('transaction_id', sTransactionId);
		oFormData.append('obj_class', sObjClass);
		oFormData.append('obj_key', iObjKey);
		oFormData.append('token', sToken);
		navigator.sendBeacon(sUrl, oFormData);
	}
}

function OnSubmit(sFormId)
{
	if($('#'+sFormId).attr('data-form-state') === 'onsubmit')
	{
		return false;
	}
	
	$('#'+sFormId).attr('data-form-state','onsubmit');

	window.bInSubmit=true; // This is a submit, make sure that when the page gets unloaded we don't cancel the action

	if ($('#'+sFormId).data('force_submit')) {
		return true;
	}

	var bResult = CheckFields(sFormId, true);
	if (!bResult)
	{
		window.bInSubmit = false; // Submit is/will be canceled
		$('#'+sFormId).attr('data-form-state', 'default');
	}
	return bResult;
}

// Store the result of the form validation... there may be several forms per page, beware
var oFormErrors = { err_form0: 0 };

function CheckFields(sFormId, bDisplayAlert)
{
// if some fields are in wait, no submit is allowed
	if ($('#'+sFormId+' .blockMsg').length>0)
	{
		CombodoModal.OpenWarningModal(Dict.S('UI:Button:Wait'));
		return false;
	}

	$('#'+sFormId+' :submit').prop('disable', true);
	$('#'+sFormId+' :button[type=submit]').prop('disable', true);
	firstErrorId = '';
	
	// The two 'fields' below will be updated when the 'validate' event is processed
	oFormErrors['err_'+sFormId] = 0;		// Number of errors encountered when validating the form
	oFormErrors['input_'+sFormId] = null;	// First 'input' with an error, to set the focus to it
	$('#'+sFormId+' :input').each( function()
	{
		// this is synchronous !
		// each field should register this event to launch ValidateField() if needed
		validateEventResult = $(this).trigger('validate', sFormId);
	}
	);
	if(oFormErrors['err_'+sFormId] > 0)
	{
		if (bDisplayAlert)
		{
			activateFirstTabWithError(sFormId);
			CombodoModal.OpenErrorModal(Dict.S('UI:FillAllMandatoryFields'));
		}
		$('#'+sFormId+' :submit').prop('disable', false);
		$('#'+sFormId+' :button[type=submit]').prop('disable', false);
		if (oFormErrors['input_'+sFormId] != null)
		{
			$('#'+oFormErrors['input_'+sFormId]).focus();
		}
	}

	return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form
}

function activateFirstTabWithError(sFormId) {
	var $form = $("#"+sFormId),
		$tabsContainer = $form.find(".ui-widget.ui-widget-content"),
		$tabs = $tabsContainer.find(".ui-tabs-panel");

	$tabs.each(function (index, element) {
		var $fieldsWithError = $(element).find(".form_validation");
		if ($fieldsWithError.length > 0 && ($tabsContainer.tabs("instance") !== undefined))
		{
			$tabsContainer.tabs("option", "active", index);
			return false;
		}
	});
}

function ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain)
{
	if (bValid)
	{
		// Visual feedback - none when it's Ok
		$('#field_'+sFieldId+' .ibo-input-wrapper').removeClass('is-error')
		$('#v_'+sFieldId).html('');
		$('#'+sFieldId+'[data-validate*="dependencies"]').trigger('change.dependencies').removeAttr('data-validate');
	}
	else
	{
		// Report the error...
		oFormErrors['err_'+sFormId]++;
		if (oFormErrors['input_'+sFormId] == null)
		{
			// Let's remember the first input with an error, so that we can put back the focus on it later
			oFormErrors['input_'+sFormId] = sFieldId;
		}

		if($('#field_'+sFieldId+' .ibo-input-wrapper').attr('data-validation') === 'untouched') {
			$('#field_'+sFieldId+' .ibo-input-wrapper').removeAttr('data-validation');
		}
		else{
			$('#field_'+sFieldId+' .ibo-input-wrapper').addClass('is-error');
		}
		
		if ($('#v_'+sFieldId).text() == '')
		{
			$('#v_'+sFieldId).html(sExplain);
		}
	}
}

/**
 * To be launched on each field from normal event (click, change, ...) and 'validate' event for form submission.
 * Calls ReportFieldValidationStatus() to update global vars containing fields status
 * @param sFieldId
 * @param sPattern
 * @param bMandatory
 * @param sFormId
 * @param nullValue
 * @param originalValue
 * @returns {boolean}
 * @constructor
 */
function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
{
	var bValid = true;
	var sExplain = '';
	if ($('#'+sFieldId).prop('disabled'))
	{
		bValid = true; // disabled fields are not checked
	}
	else
	{
		var currentVal = $('#'+sFieldId).val();

		if (currentVal == '$$NULL$$') // Convention to indicate a non-valid value since it may have to be passed as text
		{
			bValid = false;
		}
		else if (bMandatory && (currentVal == nullValue))
		{
			bValid = false;
			sExplain = Dict.S('UI:ValueMustBeSet');
		}
		else if ((originalValue != undefined) && (currentVal == originalValue))
		{
			bValid = false;
			if (originalValue == nullValue)
			{
				sExplain = Dict.S('UI:ValueMustBeSet');
			}
			else
			{
				sExplain = Dict.S('UI:ValueMustBeChanged');
			}
		}
		else if (currentVal == nullValue)
		{
			// An empty field is Ok...
			bValid = true;
		}
		else if (sPattern != '')
		{
			re = new RegExp(sPattern);
			//console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern );
			bValid = re.test(currentVal);
			sExplain = Dict.S('UI:ValueInvalidFormat');
		}
	}
	ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
	//console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid );
	return true; // Do not stop propagation ??
}

function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
{
	if ($('#'+sFieldId).length === 0) {
		return;
	}

	var bValid;
	var sExplain = '';
	var sTextContent;

	if ($('#'+sFieldId).prop('disabled')) {
		bValid = true; // disabled fields are not checked
	} else {
		// Get the contents without the tags
		var oFormattedContents = $("#cke_"+sFieldId+" iframe");
		if (oFormattedContents.length == 0) {
			var oSourceContents = $("#cke_"+sFieldId+" textarea.cke_source");
			sTextContent = oSourceContents.val();
		} else {
			sTextContent = oFormattedContents.contents().find("body").text();

			if (sTextContent == '') {
				// No plain text, maybe there is just an image...
				var oImg = oFormattedContents.contents().find("body img");
				if (oImg.length != 0) {
					sTextContent = 'image';
				}
			}
		}

		// Get the original value without the tags
		var oFormattedOriginalContents = (originalValue !== undefined) ? $('<div></div>').html(originalValue) : undefined;
		var sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined;

		if (bMandatory && (sTextContent == nullValue)) {
			bValid = false;
			sExplain = Dict.S('UI:ValueMustBeSet');
		} else if ((sTextOriginalContents != undefined) && (sTextContent == sTextOriginalContents)) {
			bValid = false;
			if (sTextOriginalContents == nullValue) {
				sExplain = Dict.S('UI:ValueMustBeSet');
			} else {
				// Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value.
				sExplain = Dict.S('UI:ValueMustBeChanged');
			}
		} else {
			bValid = true;
		}
	}

	ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);

	if ($('#'+sFieldId).data('timeout_validate') == undefined) {
		// We need to check periodically as CKEditor doesn't trigger our events. More details in UIHTMLEditorWidget::Display() @ line 92
		var iTimeoutValidate = setInterval(function () {
			ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
		}, 500);
		$('#'+sFieldId).data('timeout_validate', iTimeoutValidate);
	}
}

function ResetPwd(id)
{
	// Reset the values of the password fields
	$('#'+id).val('*****');
	$('#'+id+'_confirm').val('*****');
	// And reset the flag, to tell it that the password remains unchanged
	$('#'+id+'_changed').val(0);
	// Visual feedback, None when it's Ok
	$('#v_'+id).html('');
}

// Called whenever the content of a one way encrypted password changes
function PasswordFieldChanged(id)
{
	// Set the flag, to tell that the password changed
	$('#'+id+'_changed').val(1);
}

// Special validation function for one way encrypted password fields
function ValidatePasswordField(id, sFormId)
{
	var bChanged = $('#'+id+'_changed').val();
	if (bChanged)
	{
		if ($('#'+id).val() != $('#'+id+'_confirm').val())
		{
			oFormErrors['err_'+sFormId]++;
			if (oFormErrors['input_'+sFormId] == null)
			{
				// Let's remember the first input with an error, so that we can put back the focus on it later
				oFormErrors['input_'+sFormId] = id;
			}
			// Visual feedback
			$('#v_'+id).html(Dict.S('UI:Component:Input:Password:DoesNotMatch'));
			$('#field_'+id +' .ibo-input-wrapper').addClass('is-error');

			return false;
		}
	}
	$('#v_'+id).html('');
	$('#field_'+id +' .ibo-input-wrapper').removeClass('is-error');
	return true;
}

//Special validation function for case log fields, taking into account the history
// to determine if the field is empty or not
function ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue)
{
	var bValid = true;
	var sExplain = '';
	var sTextContent;
	
	if ($('#'+sFieldId).prop('disabled'))
	{
		bValid = true; // disabled fields are not checked
	}
	else
	{
		// Get the contents (with tags)
		// Note: For CaseLog we can't retrieve the formatted contents from CKEditor (unlike in ValidateCKEditorField() method) because of the place holder.
		sTextContent = $('#' + sFieldId).val();
		var count = $('#'+sFieldId+'_count').val();

		if (bMandatory && (count == 0) && (sTextContent == nullValue))
		{
			// No previous entry and no content typed
			bValid = false;
			sExplain = Dict.S('UI:ValueMustBeSet');
		}
		else if ((originalValue != undefined) && (sTextContent == originalValue))
		{
			bValid = false;
			sExplain = Dict.S('UI:ValueMustBeChanged');
		}
	}
	ReportFieldValidationStatus(sFieldId, sFormId, bValid, '' /* sExplain */);

	// We need to check periodically as CKEditor doesn't trigger our events. More details in UIHTMLEditorWidget::Display() @ line 92
	setTimeout(function(){ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);}, 500);
}

// Validate the inputs depending on the current setting
function ValidateRedundancySettings(sFieldId, sFormId)
{
	var bValid = true;
	var sExplain = '';

	$('#'+sFieldId+' :input[type="radio"]:checked').parent().find(':input[type="string"]').each(function (){
		var sValue = $(this).val().trim();
		if (sValue == '')
		{
			bValid = false;
			sExplain = Dict.S('UI:ValueMustBeSet');
		}
		else
		{
			// There is something... check if it is a number
			re = new RegExp('^[0-9]+$');
			bValid = re.test(sValue);
			if (bValid)
			{
				var iValue = parseInt(sValue , 10);
				if ($(this).hasClass('redundancy-min-up-percent'))
				{
					// A percentage
					if ((iValue < 0) || (iValue > 100))
					{
						bValid = false;
					}
				}
				else if ($(this).hasClass('redundancy-min-up-count'))
				{
					// A count
					if (iValue < 0)
					{
						bValid = false;
					}
				}

			}
			if (!bValid)
			{
				sExplain = Dict.S('UI:ValueInvalidFormat');
			}
		}
	});

	ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
	return bValid;
}

//Special validation function for custom fields
function ValidateCustomFields(sFieldId, sFormId)
{
	var oFieldSet = $('#'+sFieldId+'_console_form').console_form_handler('option', 'field_set');
    bValid = oFieldSet.triggerHandler('validate');
	ReportFieldValidationStatus(sFieldId, sFormId, bValid, '');
	return bValid;
}

// Manage a 'duration' field
function UpdateDuration(iId)
{
	var iDays = parseInt($('#'+iId+'_d').val(), 10);
	var iHours = parseInt($('#'+iId+'_h').val(), 10);
	var iMinutes = parseInt($('#'+iId+'_m').val(), 10);
	var iSeconds = parseInt($('#'+iId+'_s').val(), 10);
	
	var iDuration = (((iDays*24)+ iHours)*60+ iMinutes)*60 + iSeconds;
	$('#'+iId).val(iDuration);
	$('#'+iId).trigger('change');
	return true;
}

// Called when filling an autocomplete field
//deprecated in 2.8
function OnAutoComplete(id, event, data, formatted)
{
	if (data)
	{
		// A valid match was found: data[0] => label, data[1] => value
		if (data[1] != $('#'+id).val())
		{
			$('#'+id).val(data[1]);
			$('#'+id).trigger('change');
			$('#'+id).trigger('extkeychange');
		}
	}
	else
	{
		if ($('#label_'+id).val() == '')
		{
			$('#'+id).val(''); // Empty value
		}
		else
		{
			$('#'+id).val('$$NULL$$'); // Convention: not a valid value
		}
		$('#'+id).trigger('change');
	}
}
