// Copyright (C) 2010-2024 Combodo SAS
//
//   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]).trigger('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 EvaluateCKEditorValidation(oOptions)
{
	let oField = $('#'+oOptions.sFieldId);
	if (oField.length === 0) {
		return false;
	}

	let oCKEditor = CombodoCKEditorHandler.GetInstanceSynchronous('#'+oOptions.sFieldId);
	let bValid = true;
	let sExplain = '';
	let sTextContent;
	let sTextOriginalContents;

	if (oField.prop('disabled')) {
		bValid = true; // disabled fields are not checked
	} else {
		// If the CKEditor is not yet loaded, we need to wait for it to be ready
		// but as we need this function to be synchronous, we need to call it again when the CKEditor is ready
		if (oCKEditor === undefined){
			CombodoCKEditorHandler.GetInstance('#'+oOptions.sFieldId).then((oCKEditor) => {
				oOptions.onRetry();
			});
			return false;
		}

		let sFormattedContent = oCKEditor.getData();
		sTextContent = $(sFormattedContent).length > 0 ? $(sFormattedContent).text() : sFormattedContent;

		if (sTextContent === '')
		{
			// No plain text, maybe there is just an image
			let oImg = $(sFormattedContent).find('img');
			if (oImg.length !== 0)
			{
				sTextContent = 'image';
			}
		}

		let oFormattedOriginalContents = (oOptions.sOriginalValue !== undefined) ? $('<div></div>').html(oOptions.sOriginalValue) : undefined;
		sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined;

		if (oOptions.validate !== undefined) {
			let oValidation = oOptions.validate(sTextContent, sTextOriginalContents);
			bValid = oValidation.bValid;
			sExplain = oValidation.sExplain;
		}

		// Put an event to check the field when the content changes, remove the event right after as we'll call this same function again, and we don't want to call the event more than once (especially not ^2 times on each call)
		oCKEditor.model.document.once('change:data', (event) => {
			oOptions.onChange();
		});
	}

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

function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
{
	return EvaluateCKEditorValidation({
		sFieldId: sFieldId,
		sFormId: sFormId,
        sOriginalValue: originalValue,
		onRetry: function() {
			ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
		},
		onChange: function() {
			ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
		},
		validate: function(sTextContent, sTextOriginalContents) {
			var bValid;
			var sExplain = '';
			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;
			}
			return {bValid: bValid, sExplain: sExplain};
		}
	});
}
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)
{
	return EvaluateCKEditorValidation({
		sFieldId: sFieldId,
		sFormId: sFormId,
		sOriginalValue: originalValue,
		onRetry: function() {
			ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);
		},
		onChange: function() {
			ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);
		},
		validate: function(sTextContent, sTextOriginalContents) {
			var bValid;
			var sExplain = '';
			// CaseLog is special: history count matters when deciding if the field is empty
			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 ((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;
			}
			return {bValid: bValid, sExplain: sExplain};
		}
	});
}

// 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');
	}
}
