/*-
 * ~~~~~~licensing~~~~~~
 * entaxy-management-plugin
 * ==========
 * Copyright (C) 2020 - 2023 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */

var Entaxy;
(function (Entaxy) {
    Entaxy._module.factory('entaxyService', ['$q', 'workspace', 'jolokia', 'jolokiaService', 'operationsService', '$uibModal',
        function($q, workspace, jolokia, jolokiaService, operationsService, $uibModal) {
            return {
                getDomainFolder: function () {
                    return getDomainFolder();
                },
                makeFormField: function (field, objectId, properties, mode, profileName) {
                    return makeFormField(field, objectId, properties, mode, profileName);
                },
                getArguments: function (fields, factories) {
                    return getArguments(fields, factories);
                },
                getDependentFormFields: function (formFields) {
                    return getDependentFormFields(formFields);
                },
                getDefiningFormFields: function (dependentFormFields, formFields) {
                    return getDefiningFormFields(dependentFormFields, formFields);
                },
                processDependencies: function (dependentFormFields, definingFormFields, currentDefiningFormFieldName, newValue, oldValue) {
                    return processDependencies(dependentFormFields, definingFormFields, currentDefiningFormFieldName, newValue, oldValue);
                },
                validateFields: function (fields, selectedFactory, itemType, profileMBeanName) {
                    return validateFields(fields, selectedFactory, itemType, profileMBeanName);
                },
                openAddItemModalAndProcessResults: function (resolve, updateFn) {
                    return openAddItemModalAndProcessResults(resolve, updateFn);
                },
                openEditItemModalAndProcessResults: function (mbeanName, itemType, itemName, mode) {
                    return openEditItemModalAndProcessResults(mbeanName, itemType, itemName, mode);
                },
                requestConfirmationForSavingIfNeededAndProceed: function (isConfirmationNeeded, saveFn) {
                    return requestConfirmationForSavingIfNeededAndProceed(isConfirmationNeeded, saveFn);
                },
                saveItem: function (args, updateFn) {
                    return saveItem(args, updateFn);
                },
                processTableOperation: function (mbeanName, operation, selectedItemName, updateTableFn) {
                    return processTableOperation(mbeanName, operation, selectedItemName, updateTableFn);
                },
                getAllChildMBeansByRuntimeType: function (folder, runtimeType) {
                    return getAllChildMBeansByRuntimeType(folder, runtimeType);
                },
                openConfirmationWindow: function (title, message) {
                    return openConfirmationWindow(title, message);
                },
                getActiveTabIndex: function () {
                    return getActiveTabIndex();
                },
                updateTabSelection: function (targetTabIndex) {
                    return updateTabSelection(targetTabIndex);
                }
            };

            function getDomainFolder() {
                let entaxyJmxDomain = localStorage['entaxyJmxDomain'] || "ru.entaxy.esb";
                return workspace.tree.get(entaxyJmxDomain);
            }

            function makeFormField(field, objectId, properties, mode, profileName) {

                let formField;

                if (field['@TYPEINFO']) {
                    if (field['@TYPEINFO'].dependsOn) {
                        field.dependsOn = field['@TYPEINFO'].dependsOn;
                        delete field['@TYPEINFO'].dependsOn;
                    }
                    if (field['@TYPEINFO'].validation) {
                        field.validation = field['@TYPEINFO'].validation;
                        delete field['@TYPEINFO'].validation;
                    }
                    if (Object.keys(field['@TYPEINFO']).length == 0) {
                        field['@TYPEINFO'] = undefined;
                    }
                }

                if (field['@TYPEINFO'] && mode !== Entaxy.MODAL_MODES.VIEW && field['@TYPEINFO'].type === 'enum' && field.required) {
                    field['@TYPEINFO'].isEmptyIncluded = field['@TYPEINFO'].values.length == 1 ? false : undefined;
                }

                if (mode !== Entaxy.MODAL_MODES.ADD) {

                    if (field.name === 'objectId') {
                        field.value = objectId;
                    }

                    let suitableProperty = properties.find(property => property.name === field.name);

                    if (suitableProperty) {
                        if (field.type.startsWith('entaxy.runtime.') && field.immutable) {
                            field.type = 'string';

                            if (field['@TYPEINFO']) {
                                delete field['@TYPEINFO'];
                            }
                        }

                        if (field.defaultValue && field.defaultValue['@RESOURCE'] && field.defaultValue['@RESOURCE'].format === 'base64') {
                            suitableProperty.value = atob(suitableProperty.value);
                        }

                        field.value = suitableProperty.value;
                    }

                    if (!field.isHidden && field.defaultValue !== undefined && field.value === undefined) {
                        field.displayName = field.displayName ? ('* ' + field.displayName) : ('* ' + field.name);
                    }

                    if (field['@FACADE'] && field['@FACADE'].useFacade) {
                        field.type = field['@FACADE'].type;
                        field.value = field['@FACADE'].value;
                        field.defaultValue = undefined;
                        field.immutable = true;
                    }

                    if (field['@INTERNAL'] === true || (field.isHidden && field.defaultValue && field.value)) {
                        formField = undefined;
                    } else {
                        formField = {
                            label: field.displayName ? field.displayName : field.name,
                            name: field.name,
                            type: field.type,
                            description: field.description,
                            value: field.value !== undefined ? field.value : field.defaultValue,
                            isRef: field.isRef,
                            isBackRef: field.isBackRef,
                            isHidden: field.isHidden,
                            group: field.group ? field.group : 'general',
                            tag: field.label,
                            readOnly: mode === Entaxy.MODAL_MODES.VIEW ? true : field.immutable,
                            required: mode === Entaxy.MODAL_MODES.EDIT ? field.required : undefined,
                            typeInfo: mode === Entaxy.MODAL_MODES.EDIT ? field['@TYPEINFO'] : undefined,
                            isFromResource: field.defaultValue && field.defaultValue['@RESOURCE'],
                            defaultValue: field.defaultValue,
                            useFacade: (field['@FACADE'] && field['@FACADE'].useFacade) ? field['@FACADE'].useFacade : undefined,
                            dependsOn: field.dependsOn,
                            validation: field.validation
                        };
                    }
                } else {

                    if (field.defaultValue) {
                        if (field.defaultValue['@RESOURCE']) {
                            field.isFromResource = true;
                        } else if (JSON.stringify(field.defaultValue).indexOf('@CALCULATED') > -1) {
                            field.isCalculated = true;
                            field.placeholder = 'has calculated default value';
                        }
                    }

                    formField = {
                        label: field.displayName ? field.displayName : field.name,
                        name: field.name,
                        type: field.type,
                        typeInfo: field['@TYPEINFO'],
                        description: field.description,
                        required: field.required,
                        value: (field.isCalculated || field.isFromResource) ? undefined : field.defaultValue,
                        defaultValue: field.defaultValue,
                        conditional: field.conditional,
                        isRef: field.isRef,
                        isBackRef: field.isBackRef,
                        group: field.group ? field.group : 'general',
                        isHidden: field.isHidden,
                        filter: field.filter ? field.filter.label : undefined,
                        placeholder: field.placeholder,
                        isCalculated: field.isCalculated,
                        isFromResource: field.isFromResource,
                        tag: field.label,
                        uniquenessCheckedProperties: field['@UNIQUE'] ? field['@UNIQUE'].filterProperties : undefined,
                        dependsOn: field.dependsOn,
                        validation: field.validation
                    };

                    if (formField.isFromResource && !formField.dependsOn) {
                        let resourcePromise = getResource(getLocationFromResourceInfo(formField.defaultValue['@RESOURCE']));

                        if (resourcePromise) {
                            resourcePromise
                            .then((response) => { formField.value = response; })
                            .catch((error) => {
                                Core.notification('danger', 'An error occurred while loading the resource', 5000);
                                Entaxy.log.error(error);
                            });
                        } else {
                            Core.notification('danger', 'Resource service is not found', 5000);
                        }
                    }

                    if (formField.name === '__entaxyContainerId') {
                        if (profileName) {
                            formField.value = profileName;
                        } else {
                            let selectedMbean = workspace.getSelectedMBean();

                            jolokiaService.getAttributes(selectedMbean.objectName, ['RuntimeType', 'Name'])
                            .then((response) => {
                                if (!response['RuntimeType'] || response['RuntimeType'].startsWith('ERROR')) {
                                    Entaxy.log.error('There is no runtime type')
                                } else {
                                    formField.value = response['Name'];
                                }
                            });
                        }
                    }
                }

                return formField;
            }

            function getResource(location) {
                return executeOperationOnResource('getResource', [location]);
            }

            function getResourceMetadata(location) {
                return executeOperationOnResource('getResourceMetadata', [location]);
            }

            function executeOperationOnResource(operationName, properties) {
                let resourceObjectName = getResourceCategoryObjectName();

                if (resourceObjectName) {
                    return operationsService.executeOperation(
                        resourceObjectName,
                        { name: operationName },
                        properties
                    );
                }
            }

            function getLocationFromResourceInfo(resourceInfo) {
                return resourceInfo.provider + ':' + resourceInfo.location;
            }

            function getResourceCategoryObjectName() {
                let resource = getDomainFolder().findDescendant(child => {
                    let childObjectName = child.objectName;

                    if (childObjectName && childObjectName.endsWith('category=resource')) {
                        return child;
                    }
                });

                return resource ? resource.objectName : undefined;
            }

            function getArguments(fields, factories) {

                let factoryIdName = fields.shift().value;

                return args = {
                    factoryId: factories.find((factory) => factory.name === factoryIdName),
                    fields: fields.map(field => {
                        if (field.isFromResource && field.defaultValue['@RESOURCE'].format === 'base64') {
                            field.value = btoa(field.value);
                        }

                        if (field.useFacade) {
                            delete field.defaultValue;
                            delete field.value;
                        }

                        if (field.validation && field.validation.doFix === true && field.value) {
                            field.value = fixFromRules(field.validation.fixRules, field.value);
                        }

                        if (!field.required && typeof field.value === 'string' && field.value.trim().length === 0) {
                            delete field.value;
                        }

                        return {
                            name: field.name,
                            value: (field.defaultValue !== undefined && field.defaultValue === field.value) ? undefined : field.value
                        };
                    })
                };
            }

            function fixFromRules(rules, value) {
                rules.forEach((rule) => {
                    if (rule.forbid) {
                        let symbols = rule.forbid.symbols;
                        if (symbols) {
                            angular.forEach(symbols, (symbol) => {
                                if (rule.forbid.start === true && value.startsWith(symbol)) {
                                    value = value.substring(1, value.length);
                                }
                            });
                        }
                    }
                });
                return value;
            }

            function getDependentFormFields(formFields) {
                let dependentFormFields = formFields.filter(formField => formField.dependsOn);
                dependentFormFields.forEach(formField => {
                    formField.readOnly = (formField.typeInfo && formField.typeInfo.type !== 'String') ? true : undefined;
                });
                return dependentFormFields;
            }

            function getDefiningFormFields(dependentFormFields, formFields) {
                let listOfDefiningNames = dependentFormFields.map(field => field.dependsOn);
                let definingNames = [];
                listOfDefiningNames.forEach(names => { definingNames = definingNames.concat(names); });
                definingNames = [...(new Set(definingNames))];

                return formFields.filter(formField => definingNames.includes(formField.name));
            }

            function processDependencies(dependentFormFields, definingFormFields, currentDefiningFormFieldName, newValue, oldValue, index) {

                if (oldValue) {
                    dependentFormFields.forEach(formField => {
                        if (formField.dependsOn.includes(currentDefiningFormFieldName)) {
                            formField.readyToCompute = false;
                            if (formField.typeInfo.type !== 'String') {
                                formField.readOnly = true;
                            }

                            if (formField.typeInfo && formField.typeInfo.type === 'list') {
                                formField.typeInfo.values = undefined;
                                formField.typeInfo.isEmptyIncluded = undefined;
                            }
                            formField.value = undefined;
                        }
                    });
                }

                if (newValue) {
                    dependentFormFields.forEach(formField => {
                        if (formField.dependsOn.includes(currentDefiningFormFieldName)) {
                            let readyToCompute = true;
                            formField.dependsOn.forEach(name => {
                                if (!definingFormFields.find(field => field.name === name).value) {
                                    readyToCompute = false;
                                }
                            });
                            formField.readyToCompute = readyToCompute;
                            if (formField.typeInfo.type !== 'String') {
                                formField.readOnly = !readyToCompute;
                            }

                            if (formField.readyToCompute) {
                                if (formField.typeInfo && (formField.typeInfo.type === 'list' || formField.typeInfo.type === 'String')) {
                                    let sourcePropertyName = formField.typeInfo.source.property;
                                    let sourceType = formField.typeInfo.source.sourceType;
                                    let sourcePath = formField.typeInfo.source.path;

                                    // we assume that if the source type is resource
                                    // then source property contains its location
                                    if (sourceType === 'resource') {
                                        let promise = computeResourcePropertyValue(sourcePropertyName, sourcePath, definingFormFields);
                                        if (promise) {
                                            promise
                                            .then(result => {
                                                if (formField.typeInfo.type === 'list') {
                                                    formField.typeInfo.values = result;
                                                    if (formField.required) {
                                                        formField.typeInfo.isEmptyIncluded = (result.length == 1) ? false : undefined;
                                                    }
                                                } else if (formField.typeInfo.type === 'String') {
                                                    formField.value = result;
                                                }
                                            })
                                            .catch((error) => {
                                                Core.notification('danger', error, 5000);
                                                Entaxy.log.error(error);
                                            });
                                        }
                                    }
                                } else if (formField.type === 'xml:route') {
                                    let resourceProvider = formField.defaultValue['@RESOURCE'].provider;
                                    let resourceLocation = formField.typeInfo.location;
                                    let splitResourceLocation = resourceLocation.split('/');
                                    let regex = /\${[\w#.-]+}/g;
                                    for (let i = 0; i < splitResourceLocation.length; i++) {
                                        if (splitResourceLocation[i].match(regex)) {
                                            let property = splitResourceLocation[i].substring(2, splitResourceLocation[i].length - 1);
                                            if (formField.typeInfo[property]) {
                                                let sourcePropertyName = formField.typeInfo[property].property;
                                                let sourceType = formField.typeInfo[property].sourceType;
                                                let sourcePath = formField.typeInfo[property].path;

                                                // we assume that if the source type is resource
                                                // then source property contains its location
                                                if (sourceType === 'resource') {
                                                    splitResourceLocation[i] = { promise: computeResourcePropertyValue(sourcePropertyName, sourcePath, definingFormFields) };
                                                }
                                            } else {
                                                splitResourceLocation[i] = definingFormFields.find(field => field.name === property).value;
                                            }
                                        }
                                    }

                                    let promises = splitResourceLocation
                                        .filter(chunk => chunk.promise)
                                        .map(chunk => chunk.promise);

                                    $q.all(promises)
                                    .then(results => {
                                        results.forEach(result => {
                                            let index = splitResourceLocation.findIndex(chunk => chunk.promise);
                                            splitResourceLocation[index] = result;
                                        });

                                        let computedResourceLocation = splitResourceLocation.join('/');

                                        let promise = getResource(resourceProvider + ':' + computedResourceLocation);
                                        if (promise) {
                                            promise
                                            .then(response => { formField.value = response; })
                                            .catch((error) => {
                                                Core.notification('danger', 'An error occurred while loading the resource', 5000);
                                                Entaxy.log.error(error);
                                            });;
                                        } else {
                                            Core.notification('danger', 'Resource service is not found', 5000);
                                        }
                                    })
                                    .catch((error) => {
                                        Core.notification('danger', error, 5000);
                                        Entaxy.log.error(error);
                                    });
                                }
                            }
                        }
                    });
                }
            }

            function computeResourcePropertyValue(sourcePropertyName, sourcePath, definingFormFields) {
                let deferred = $q.defer();

                let splitSourcePath = sourcePath.split('.');
                let sourcePropertyValue = definingFormFields.find(field => field.name === sourcePropertyName).value;

                // for now we assume there is always #metadata#
                if (splitSourcePath[0] === '#metadata#') {
                    let promise = getResourceMetadata(sourcePropertyValue);

                    if (promise) {
                        promise
                        .then(response => {
                            let metadata = JSON.parse(response);
                            let section = JSON.parse(metadata[splitSourcePath[1]]);

                            let regex = /\${[\w#.-]+}/g;
                            let result = section;
                            for (let i = 2; i < splitSourcePath.length; i++) {
                                if (!splitSourcePath[i].match(regex)) {
                                    result = result[splitSourcePath[i]];
                                } else {
                                    let fieldName = splitSourcePath[i].substring(2, splitSourcePath[i].length - 1);
                                    let value = definingFormFields.find(field => field.name === fieldName).value;
                                    result = result[value];
                                }
                            }
                            deferred.resolve(result);
                        });
                    } else {
                        deferred.reject('Resource service is not found');
                    }
                }
                return deferred.promise;
            }

            function validateFields(fields, selectedFactory, itemType, profileMBeanName) {
                let errors = {};

                _.forEach(fields, (field) => {
                    if (!field.isBackRef && !field.isHidden && !field.isCalculated) {
                        if (field.required && (field.value === undefined ||
                                    (typeof field.value === 'string' ? field.value.trim().length === 0 : false))) {
                            errors[field.name] = 'Please fill out this field';
                        }

                        if (!errors[field.name] && field.validation && field.validation.rules) {
                            validateByRules(field, field.validation.rules, errors);
                        }
                    }

                    if (field.uniquenessCheckedProperties && !errors[field.name]) {
                        errors[field.name] = checkUniqueness(field.name, field.value, field.uniquenessCheckedProperties,
                            fields, selectedFactory, itemType, profileMBeanName);
                    }
                });

                let deferred = $q.defer();

                if (Object.keys(errors).length !== 0) {
                    let errorArrayWithPromises = [];

                    Object.keys(errors).forEach(key => {
                        if (typeof errors[key] !== 'string') {
                            errorArrayWithPromises.push({ name: key, promise: errors[key] });
                        }
                    });

                    if (errorArrayWithPromises.length !== 0) {

                        $q.all(errorArrayWithPromises.map(obj => { return obj.promise; }))
                        .then(result => {
                            for (let i = 0; i < errorArrayWithPromises.length; i++) {
                                if (result[i]) {
                                    errors[errorArrayWithPromises[i].name] = result[i];
                                } else {
                                    delete errors[errorArrayWithPromises[i].name];
                                }
                            }

                            deferred.resolve(errors);
                        });
                    } else {
                        deferred.resolve(errors);
                    }
                } else {
                    deferred.resolve(errors);
                }

                return deferred.promise;
            }

            function validateByRules(field, rules, errors) {
                for (let i = 0; i < rules.length; i++) {
                    validateByRule(field, rules[i], errors);
                    if (errors[field.name]) {
                        break;
                    }
                }
            }

            function validateByRule(field, rule, errors) {
                if (rule.length) {
                    if (rule.length.min) {
                        if (field.value.trim().length < rule.length.min) {
                            errors[field.name] = 'Value must contain at least ' + rule.length.min + ' characters';
                        }
                    } else if (rule.length.max) {
                        if (field.value.trim().length > rule.length.max) {
                            errors[field.name] = 'Value must not contain greater then ' + rule.length.max + ' characters';
                        }
                    }
                } else if (rule.content) {
                    if (rule.content.regex) {
                        let regex = new RegExp(rule.content.regex);
                        if (!field.value.match(regex)) {
                            errors[field.name] = rule.content.errorMessage ?
                                rule.content.errorMessage : 'Value contains invalid characters';
                        }
                    }
                }
            }

            function checkUniqueness(fieldName, fieldValue, properties, fields, selectedFactory, itemType, profileMBeanName) {
                let message = 'Value must be unique';
                let isInvalid = false;
                let promises = [];

                properties.unshift(fieldName);
                properties = properties.map(property => {
                    return Entaxy.capitalize(property);
                });

                let profileMBean;
                if (profileMBeanName) {
                    profileMBean = getDomainFolder().findDescendant(child => child.objectName === profileMBeanName);
                } else {
                    profileMBean = workspace.getSelectedMBean();
                }

                if (profileMBean && profileMBean.isFolder) {
                    let children = getAllChildMBeansByRuntimeType(profileMBean, 'entaxy.runtime.' + itemType.toLowerCase());

                    let childrenMbeanNames = children
                    .map(child => { return child.objectName; })
                    .filter(child => child !== null);

                    childrenMbeanNames.forEach(mbeanName => {
                        promises.push(jolokiaService.getAttributes(mbeanName, properties)
                        .then((response) => {
                            if (fieldValue === response[properties[0]]) {
                                for (let i = 1; i < properties.length; i++) {
                                    let checkedField = fields.find(field => field.name === Entaxy.uncapitalize(properties[i]));

                                    if (checkedField) {
                                        if (checkedField.value === response[properties[i]]) {
                                            isInvalid = true;
                                        }
                                    } else {
                                        return jolokiaService.getAttribute(selectedFactory.mbeanName, 'TypeInfo')
                                        .then((typeInfo) => {
                                            let typeInfoDesiredValue = typeInfo[Entaxy.uncapitalize(properties[i])];

                                            if (typeInfoDesiredValue === response[properties[i]]) {
                                                isInvalid = true;
                                            }
                                        });
                                    }
                                }
                            }
                        }));
                    });
                }

                return $q.all(promises).then(() => isInvalid ? message : '');
            }

            function openAddItemModalAndProcessResults(resolve, updateFn) {
                let factories = [];
                let promises = [];

                let factoryFolder = getDomainFolder()
                    .findDescendant(child => child.title === 'entaxy.runtime.' + resolve.itemType().toLowerCase());

                if (factoryFolder && factoryFolder.children) {
                    factoryFolder.children.forEach((child) => {
                        promises.push(jolokiaService.getAttributes(child.objectName, ['Abstract', 'DisplayName', 'Label', 'Description', 'Deprecated'])
                        .then((result) => {
                            if (!result['Abstract']) {
                                factories.push({
                                    name: child.title,
                                    displayName: result['DisplayName'] ? result['DisplayName'] : child.title,
                                    label: result['Label'],
                                    mbeanName: child.objectName,
                                    description: result['Description'] ? result['Description'] : 'There is no description for this factory.',
                                    additionalInfo: result['Deprecated'] ? ['[DEPRECATED]'] : undefined
                                });
                            }
                        }));
                    });

                    $q.all(promises).then(() => {
                        resolve.factories = () => factories;
                        openModalAndProcessResults(resolve, updateFn);
                    });
                }
            }

            function openEditItemModalAndProcessResults(mbeanName, itemType, itemName, mode) {

                let factoryFolder = getDomainFolder()
                    .findDescendant(child => child.title === 'entaxy.runtime.' + itemType.toLowerCase());

                operationsService
                .executeOperation(mbeanName, { name: 'get' + itemType + 'Config' }, [ itemName ])
                .then(result => {
                    let itemInfo = JSON.parse(result);

                    let factory = factoryFolder.children.find(child => child.title === itemInfo.factoryId);
                    factory = { name: factory.title, mbeanName: factory.objectName };

                    openModalAndProcessResults({
                        itemName: () => itemName,
                        itemType: () => itemType,
                        mode: () => mode,
                        info: () => itemInfo,
                        factories: () => [ factory ]
                    });
                }).catch(error => {
                    Core.notification('danger', 'Unable to view configuration of selected item.', 5000);
                    Entaxy.log.error(error);
                });
            }

            function openModalAndProcessResults(resolve, updateFn) {
                openEntaxyModal(resolve)
                .then(args => {
                    saveItem(args, updateFn);
                },
                reason => {
                    if (reason) {
                        Core.notification('danger', reason, 5000);
                    }
                });
            }

            function openEntaxyModal(resolve) {
                return $uibModal.open({
                    component: 'entaxyModal',
                    resolve: resolve,
                    size: 'xl',
                    backdrop: 'static'
                }).result;
            }

            function requestConfirmationForSavingIfNeededAndProceed(isConfirmationNeeded, saveFn) {
                if (isConfirmationNeeded) {
                    let title = 'Confirm Saving';
                    let message = 'There are no changes to this object. ' +
                        'Do you want to continue and recreate object with the same properties?';
                    openConfirmationWindow(title, message).then(() => {
                        saveFn();
                    });
                } else {
                    saveFn();
                }
            }

            function saveItem(args, updateFn) {
                let objectId = args.fields.find((field) => field.name === 'objectId').value;

                let fields = objectId ? args.fields.filter((field) => field.name !== 'objectId') : args.fields;

                let mbeanName = args.factoryId.mbeanName;
                let properties = fields.reduce((obj, cur) => ({ ...obj, [cur.name] : cur.value }), {});
                let instructions = {"@LIFECYCLE":["general"]};

                operationsService.executeOperation(mbeanName, { name: 'createObjectByInstructions' }, [ objectId, 'public', instructions, properties ] )
                .then(result => {
                    let tableHtml = Entaxy.createTableFromResponse(result);
                    Entaxy.notification('success', tableHtml, 10000);

                    if (updateFn) {
                        updateFn(properties, objectId);
                    }
                }).catch(error => {
                    Core.notification('danger', error, 5000);
                    Entaxy.log.error(error);
                });
            }

            function processTableOperation(mbeanName, operation, selectedItemName, updateTableFn) {
                operationsService.executeOperation(mbeanName, operation, [ selectedItemName ])
                    .then(result => {
                        Core.notification('success', result, 3000);
                        if (updateTableFn) {
                            updateTableFn();
                        }
                    }).catch(error => {
                    Core.notification('danger', error, 5000);
                    Entaxy.log.error(error);
                });
            }

            function getAllChildMBeansByRuntimeType(folder, runtimeType) {
                let mbeans = [];
                if (folder.children) {
                    getAllChildMBeansByRuntimeTypeRecursive(folder.children, runtimeType, mbeans);
                }
                return mbeans;
            }

            function getAllChildMBeansByRuntimeTypeRecursive(children, runtimeType, mbeans) {
                children.forEach(child => {
                    if (child.objectName) {
                        let attributes = jolokia.getAttribute(child.objectName);
                        if (attributes.RuntimeType === runtimeType) {
                            mbeans.push(child);
                        }
                    }
                    if (child.children) {
                        getAllChildMBeansByRuntimeTypeRecursive(child.children, runtimeType, mbeans);
                    }
                });
            }

            function openConfirmationWindow(title, message) {
                return $uibModal.open({
                    component: 'entaxyConfirmationModal',
                    resolve: {
                        title: () => title,
                        message: () => message
                    },
                    size: 'sm-custom',
                    backdrop: 'static',
                    windowTopClass: 'modal-top-margin-center-override'
                }).result;
            }

            function getActiveTabIndex() {
                let hawtioTabs = document.getElementsByClassName("hawtio-tab");

                for (let i = 0; i < hawtioTabs.length; i++) {
                    if (hawtioTabs[i].classList.contains('active')) {
                        return i;
                    }
                }

                return -1;
            }

            function updateTabSelection(targetTabIndex) {
                let currentActiveTabIndex = getActiveTabIndex();

                if (targetTabIndex !== currentActiveTabIndex) {
                    document.getElementsByClassName("hawtio-tab")[targetTabIndex].children[0].click();
                }
            }
        }
    ])
})(Entaxy || (Entaxy = {}));
