/**
 * Класс для работы с Synergy by yandexphp
 */
function __classSynergy() {
    let __SERVER = '__SERVER__';
    let __CLASS__;
    let __EXECUTE__;
    let __LOADFORM__;
    let __SERVER__;
    let __CLIENT__;
    let __MULTIFORMSDATA__;
    let __FORMDATA__;
    let __CONNECT_LIST__;
    let __TYPE_CONNECTED__;
    let __DATAUUID__;
    let __DOCUMENT_ID__;
    let __NODE_UUID__;
    let __FORM_UUID__;
    let __FORM_VERSION__;
    let __DOCUMENT_CHANGE__;
    let __OLD_DATAUUID__;
    let __DATA__;
    let __FULL_DATA__;
    let __HOST__;
    let __LOGIN__;
    let __PASSWORD__;
    let __PROTOCOL__;
    let __HOSTNAME__;
    let __HOST_ORIGIN__;
    let __BASIC_AUTH__;

    let __checkAccess = function () {
        return !!__EXECUTE__;
    }

    this.setData = function (data) {
        __DATA__ = data;
    };

    /**
     * Установка подключения к контейнеру
     * @param {String} host     С каким хостом будем работать. Пример: https://site.arta.pro/Synergy/
     * @param {String} login    Логин или $session или $key пользователя с кем будем работать
     * @param {String} password Пароль или sso_hash или access_token пользователя
     */
    this.setConnection = function (host, login, password) {
        if (arguments.length < 3) return;
        __HOST__ = (/(.+?)\/\w+\//i.test(host) ? host.toLowerCase().replace(/(\w)(\/.+)/, '$1/Synergy/') : null);
        __PROTOCOL__ = __HOST__ ? __HOST__.match(/(https?)/i)[1] : null;
        __HOSTNAME__ = __HOST__ ? __HOST__.replace(/(https?|:|\/\/|\/.+)/gi, '') : null;
        __HOST_ORIGIN__ = __HOST__ ? __HOST__.match(/(.+?)\/\w+\//i)[1] : null;
        __LOGIN__ = (login = "" + login).encodeURICyrillic();
        __PASSWORD__ = (password = "" + password).encodeURICyrillic();
        __TYPE_CONNECTED__ = (typeof btoa === 'undefined' || !__HOST__ ? __SERVER__ : __CLIENT__);
        __BASIC_AUTH__ = (typeof btoa === 'undefined' ? __LOGIN__ + ':' + __PASSWORD__ : btoa(encodeURIComponent(__LOGIN__ + ':' + __PASSWORD__)));
        __LOADFORM__ = true;
        var connectToList = {
            host: __HOST__,
            protocol: __PROTOCOL__,
            hostname: __HOSTNAME__,
            origin: __HOST_ORIGIN__,
            login: __LOGIN__,
            password: __PASSWORD__,
            basic: __BASIC_AUTH__
        };
        var rewrite = __CONNECT_LIST__[__HOSTNAME__] && JSON.stringify(__CONNECT_LIST__[__HOSTNAME__]).replace(/"index".*?,/, '') != JSON.stringify(connectToList);
        if (!__CONNECT_LIST__[__HOSTNAME__] || rewrite) {
            connectToList.index = (Object.keys(__CONNECT_LIST__).length + (rewrite ? 0 : 1));
            __CONNECT_LIST__[__HOSTNAME__] = connectToList;
        }
        return __TYPE_CONNECTED__;
    }

    /**
     * Лист всех подключенных контейнеров
     */
    this.getListConnection = function () {
        return __CONNECT_LIST__;
    }

    /**
     * Изменить подключение на указанное из существующих
     * @param   {Int}   index   Индекс контейнера из листа контейнеров
     */
    this.selectConnection = function (index) {
        if (!__CONNECT_LIST__.length || !__CONNECT_LIST__[index]) return __TYPE_CONNECTED__;
        __HOST__ = __CONNECT_LIST__[index].host;
        __PROTOCOL__ = __CONNECT_LIST__[index].protocol;
        __HOSTNAME__ = __CONNECT_LIST__[index].hostname;
        __HOST_ORIGIN__ = __CONNECT_LIST__[index].origin;
        __LOGIN__ = __CONNECT_LIST__[index].login;
        __PASSWORD__ = __CONNECT_LIST__[index].password;
        __BASIC_AUTH__ = __CONNECT_LIST__[index].basic;
        return __TYPE_CONNECTED__;
    }

    /**
     * Получения данных о текущем подключении к контейнеру
     */
    this.getConnection = function () {
        if (!__LOADFORM__) return;
        return {
            host: __HOST__,
            protocol: __PROTOCOL__,
            hostname: __HOSTNAME__,
            origin: __HOST_ORIGIN__,
            login: decodeURIComponent(__LOGIN__),
            password: decodeURIComponent(__PASSWORD__),
            basic: __BASIC_AUTH__
        };
    }

    /**
     * Провести проверку подключения к контейнеру
     */
    this.testConnect = function () {
        var connectData = __CLASS__.getConnection();
        if (connectData && connectData.login && connectData.password) {
            try {
                return __TYPE_CONNECTED__.api('person/auth').hasOwnProperty('userid');
            } catch (err) {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Получение данных по форме после подгрузки формы
     */
    this.getFormData = function () {
        if (!__EXECUTE__) return;
        return __DATA__;
    }

    /**
     * Получение `dataUUID` после подгрузки формы
     */
    this.getDataUUID = function () {
        if (!__EXECUTE__) return;
        return __DATAUUID__;
    }

    /**
     * Получение `nodeUUID` после подгрузки формы
     */
    this.getNodeUUID = function () {
        if (!__EXECUTE__) return;
        return __NODE_UUID__;
    }

    /**
     * Получение `formUUID` после подгрузки формы
     */
    this.getFormUUID = function () {
        if (!__EXECUTE__) return;
        return __FORM_UUID__;
    }

    /**
     * Получение `form_version` после подгрузки формы
     */
    this.getFormVersion = function () {
        if (!__EXECUTE__) return;
        return __FORM_VERSION__;
    }

    /**
     * Получение `documentID` после подгрузки формы
     */
    this.getDocumentID = function () {
        if (!__EXECUTE__) return;
        return __DOCUMENT_ID__;
    }

    /**
     * UTF-8 encode
     * @param   {String}    str     строка
     */
    this.utf8_encode = function (str) {
        return unescape(encodeURIComponent(str));
    }

    /**
     * UTF-8 decode
     * @param   {String}    str     строка
     */
    this.utf8_decode = function (str) {
        return decodeURIComponent(escape(str));
    }

    /**
     * Склонения слова
     * @param {Array} arr массив слов
     * @param {Int} i число по которому будем склонять и выдавать 1 из N слов
     * Example: ['Кружк[у]','Кружк[И]','Круже[К]'],4 = 58 Круже[К]
     */
    this.getWordDeclination = function (arr, i) {
        var index = i % 10 == 1 && i % 100 != 11 ? 0 : (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 10 || i % 100 >= 20) ? 1 : 2);
        return {
            num: i || -1,
            index0: (arr[index] && index ? index : -1),
            index1: (arr[index] && index ? index + 1 : -1),
            value: (arr[index] ? '' + arr[index] : '')
        };
    }

    /**
     * Добавляет ноль в начало числа или строки
     * @param {Int | String} num число
     * @param {Int} len кол-во нулей по умолчанию `2`
     */
    this.strAddZero = function (num, len) {
        if (!len) len = 2;
        var s = '' + num;
        while (s.length < len) s = '0' + s;
        return s;
    }

    /**
     * Методы работ на стороне клиента
     */
    this.client = {
        /**
         * Метод загрузки формы
         * @param {String | Integer} dataUUID   Идентификатор данных по форме
         */
        load: function (dataUUID, version) {
            if (!__LOADFORM__ || !dataUUID) return;

            if (arguments[2] && arguments[2] == __SERVER) {
                var data = __SERVER__.api('asforms/data/' + dataUUID + (version ? '?version=' + version : ''));
                console.info('asforms/data/' + dataUUID + (version ? '?version=' + version : ''));
                console.info(JSON.stringify(data));
                if (data.hasOwnProperty('uuid') || data.hasOwnProperty('oldUuid') || data.hasOwnProperty('data')) {
                    __DATAUUID__ = data.uuid;
                    __OLD_DATAUUID__ = data.oldUuid;
                    __NODE_UUID__ = data.nodeUUID;
                    __FORM_UUID__ = data.form;
                    __DOCUMENT_CHANGE__ = data.modified;
                    __FORM_VERSION__ = data.version;
                    __DATA__ = data.data;
                    __FULL_DATA__ = data;
                    __DOCUMENT_ID__ = "" + (documentID ? documentID : __CLIENT__.ApiUtils.getDocumentID(dataUUID, __SERVER));
                    __EXECUTE__ = true;
                    __MULTIFORMSDATA__[__DATAUUID__] = __FULL_DATA__;
                    if (!__FORMDATA__) {
                        __FORMDATA__ = {
                            dataUUID: __DATAUUID__,
                            oldDataUUID: __OLD_DATAUUID__,
                            documentID: __DOCUMENT_ID__,
                            documentChange: __DOCUMENT_CHANGE__,
                            formVersion: __FORM_VERSION__,
                            fullData: __FULL_DATA__,
                            nodeUUID: __NODE_UUID__,
                            formUUID: __FORM_UUID__,
                            data: __DATA__
                        };
                    }
                } else {
                    __DATAUUID__ = __OLD_DATAUUID__ = __NODE_UUID__ = __FORM_UUID__ = __DOCUMENT_CHANGE__ = __FORM_VERSION__ = __DATA__ = __FULL_DATA__ = __DOCUMENT_ID__ = null;
                    __EXECUTE__ = false;
                }
                return __SERVER__;
            } else {
                __CLIENT__.api('asforms/data/' + dataUUID, function (data) {
                    __DATAUUID__ = data.uuid;
                    __OLD_DATAUUID__ = data.oldUuid;
                    __NODE_UUID__ = data.nodeUUID;
                    __FORM_UUID__ = data.form;
                    __DOCUMENT_CHANGE__ = data.modified;
                    __FORM_VERSION__ = data.version;
                    __DATA__ = data.data;
                    __FULL_DATA__ = data;
                    __DOCUMENT_ID__ = "" + __CLIENT__.ApiUtils.getDocumentID(dataUUID);
                    __EXECUTE__ = true;
                    __MULTIFORMSDATA__[__DATAUUID__] = __FULL_DATA__;
                    if (!__FORMDATA__) {
                        __FORMDATA__ = {
                            dataUUID: __DATAUUID__,
                            oldDataUUID: __OLD_DATAUUID__,
                            documentID: __DOCUMENT_ID__,
                            documentChange: __DOCUMENT_CHANGE__,
                            formVersion: __FORM_VERSION__,
                            fullData: __FULL_DATA__,
                            nodeUUID: __NODE_UUID__,
                            formUUID: __FORM_UUID__,
                            data: __DATA__
                        };
                    }
                }, {
                    async: false
                }, function (err) {
                    __DATAUUID__ = __OLD_DATAUUID__ = __NODE_UUID__ = __FORM_UUID__ = __DOCUMENT_CHANGE__ = __FORM_VERSION__ = __DATA__ = __FULL_DATA__ = __DOCUMENT_ID__ = null;
                    __EXECUTE__ = false;
                });
                return __CLIENT__;
            }
        },

        /**
         * Метод загрузки нескольких форм
         * @param {Array | String | Integer} dataUUIDs   идентификатор(ы) данных по форме
         */
        multiLoad: function (dataUUIDs) {
            if (!__LOADFORM__) return;
            else if (!dataUUIDs) return;
            if (!Array.isArray(dataUUIDs)) dataUUIDs = [dataUUIDs];
            dataUUIDs.forEach(function (dataUUID) {
                if (arguments[1] && arguments[1] == __SERVER) {
                    var data = __SERVER__.api('asforms/data/' + dataUUID);
                    if (data.hasOwnProperty('uuid') || data.hasOwnProperty('oldUuid') || data.hasOwnProperty('data')) {
                        data.documentID = (!documentID ? __SERVER__.ApiUtils.getDocumentID(dataUUID) : documentID);
                        __MULTIFORMSDATA__[dataUUID] = data;
                    }
                } else {
                    __CLIENT__.api('asforms/data/' + dataUUID, function (data) {
                        data.documentID = __CLIENT__.ApiUtils.getDocumentID(dataUUID);
                        __MULTIFORMSDATA__[dataUUID] = data;
                    }, {
                        async: false
                    });
                }
            });
            return (arguments[1] && arguments[1] == __SERVER ? __SERVER__ : __CLIENT__);
        },

        /**
         * Получение данных загруженных форм
         */
        getFormsList: function () {
            return __MULTIFORMSDATA__;
        },

        /**
         * Выбрать с какой формой будем работать
         */
        selectForm: function (dataUUID) {
            if (!Object.keys(__MULTIFORMSDATA__).length || !__MULTIFORMSDATA__[dataUUID]) return;
            __DATAUUID__ = __MULTIFORMSDATA__[dataUUID].uuid;
            __OLD_DATAUUID__ = __MULTIFORMSDATA__[dataUUID].oldUuid;
            __NODE_UUID__ = __MULTIFORMSDATA__[dataUUID].nodeUUID;
            __FORM_UUID__ = __MULTIFORMSDATA__[dataUUID].form;
            __DOCUMENT_CHANGE__ = __MULTIFORMSDATA__[dataUUID].modified;
            __FORM_VERSION__ = __MULTIFORMSDATA__[dataUUID].version;
            __DATA__ = __MULTIFORMSDATA__[dataUUID].data;
            __FULL_DATA__ = __MULTIFORMSDATA__[dataUUID].data;
            __DOCUMENT_ID__ = "" + __TYPE_CONNECTED__.ApiUtils.getDocumentID(__DATAUUID__);
            __EXECUTE__ = true;
            return __TYPE_CONNECTED__;
        },

        /**
         * Вернуть форму загруженную не с мультизагрузки если такая была
         */
        regainForm: function () {
            if (!__FORMDATA__) return;
            __DATAUUID__ = __FORMDATA__.dataUUID;
            __OLD_DATAUUID__ = __FORMDATA__.oldDataUUID;
            __DOCUMENT_ID__ = __FORMDATA__.documentID;
            __DOCUMENT_CHANGE__ = __FORMDATA__.documentChange;
            __FORM_VERSION__ = __FORMDATA__.formVersion;
            __FULL_DATA__ = __FORMDATA__.fullData;
            __NODE_UUID__ = __FORMDATA__.nodeUUID;
            __FORM_UUID__ = __FORMDATA__.formUUID;
            __DATA__ = __FORMDATA__.data;
            return __TYPE_CONNECTED__;
        },

        /**
         * [GET] Метод получения значения компонента
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то ключ *не обязательно* иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то ключ иначе *не обязательно*
         */
        getValue: function () {
            if (!__checkAccess()) return;
            else if (arguments.length < 1) return;
            console.info('arguments {}', JSON.stringify(arguments));
            var data;
            var args = {c: arguments[0], ot: arguments[1], b: arguments[2], o: arguments[3]};
            if (typeof (args.c) === 'string' && typeof (args.b) === 'number') {
                args.b = +((+args.b) === 0 ? 1 : args.b);
                __DATA__.forEach(function (item) {
                    if (item.id == args.ot) {
                        if (!item.hasOwnProperty('data')) item.data = [];
                        item.data.forEach(function (bItem) {
                            if (bItem.id === args.c + '-b' + args.b) {
                                data = (args.o && args.o != __SERVER) ? (bItem[args.o] ? bItem[args.o] : undefined) : bItem;
                                return;
                            }
                        });
                        return;
                    }
                });
            } else {
                __DATA__.forEach(function (item) {
                    if (item.id == args.c) {
                        data = (args.ot && args.ot != __SERVER) ? (item[args.ot] ? item[args.ot] : undefined) : item;
                        return;
                    }
                });
            }
            if (data === undefined) {
                switch (args.ot) {
                    case 'data':
                        data = [];
                        break;
                    case 'value':
                    case 'key':
                        data = '';
                        break;
                    default:
                        data = false;
                }
            }
            console.info('data {}',JSON.stringify(data));
            return data;
        },

        /**
         * [SET] Метод установки нового значения компоненту
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то значение или объект иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы то ключ для значения *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то значение или объект иначе *не обязательно*
         * @param   {String}    arg5    Если компонент в дин таблицы то ключ для значения иначе *не обязательно*
         *
         * Пример:
         *      Вне дин.таблицы: setValue('component1','Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Вне дин.таблицы: setValue('component2',{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         *
         *
         *      Дин.таблица: setValue('component1','table',1,'Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Дин.таблица: setValue('component2','table',1,45000,'key');  - "45000" в скобках или без запишется в ключ "key"
         *      Дин.таблица: setValue('component3','table',1,{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         */
        setValue: function () {
            if (!__checkAccess() || arguments.length < 1) return;

            var defKey = 'value';
            var args = {c: arguments[0], vt: arguments[1], ob: arguments[2], v: arguments[3], o: arguments[4]};

            if (typeof (args.c) === 'string' && typeof (args.ob) === 'number') {
                if (args.ob.toString() === '0'){
                    args.ob = 1;
                }

                __DATA__.forEach(function (item) {
                    if (item.id == args.vt) {
                        if (!item.hasOwnProperty('data')) {
                            item.data = [];
                        }

                        item.data.forEach(function (bItem) {
                            if (bItem.id === args.c + '-b' + args.ob) {
                                switch (bItem.type) {
                                    case 'reglink':
                                        if (args.v && /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(args.v)) {
                                            if(args.o && args.o == __SERVER) {
                                                args.v = {
                                                    key: args.v,
                                                    value: __SERVER__.api('formPlayer/getDocMeaningContents?documentId=' + args.v)[0].meaning
                                                }
                                            }else{
                                                args.v = {
                                                    key: args.v,
                                                    value: __CLIENT__.api('formPlayer/getDocMeaningContents?documentId=' + args.v)[0].meaning
                                                }
                                            }
                                        }

                                        break;
                                    case 'numericinput':
                                        defKey = 'key';

                                        if (args.v && (typeof args.v === 'string' || typeof args.v === 'number')) {
                                            args.v = args.v.toString().replace(/\s*/g, '');
                                        }
                                        break;
                                }

                                if (typeof (args.v) === 'object') {
                                    bItem = Object.assign(bItem, args.v);
                                } else {
                                    bItem[(args.o && args.o != __SERVER) ? args.o : defKey] = args.v;
                                }
                                return;
                            }
                        });
                        return;
                    }
                });
            } else {
                __DATA__.forEach(function (item) {
                    if(item.id == args.c){
                        switch (item.type) {
                            case 'reglink':
                                if (args.vt && /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(args.vt)) {
                                    if(args.o && args.o == __SERVER) {
                                        args.vt = {
                                            key: args.vt,
                                            value: __SERVER__.api('formPlayer/getDocMeaningContents?documentId=' + args.vt)[0].meaning
                                        }
                                    }else{
                                        args.vt = {
                                            key: args.vt,
                                            value: __CLIENT__.api('formPlayer/getDocMeaningContents?documentId=' + args.vt)[0].meaning
                                        }
                                    }
                                }

                                break;
                            case 'numericinput':
                                defKey = 'key';

                                if (args.vt && (typeof args.vt === 'string' || typeof args.vt === 'number')) {
                                    args.vt = args.vt.toString().replace(/\s*/g, '');
                                }
                                break;
                        }

                        if (typeof (args.vt) === 'object') {
                            item = Object.assign(item, args.vt);
                        } else {
                            item[(args.ob && args.ob != __SERVER) ? args.ob : defKey] = args.vt;
                        }

                        return;
                    }
                });
            }
            return (arguments[arguments.length - 1] && arguments[arguments.length - 1] == __SERVER ? __SERVER__ : __CLIENT__);
        },

        /**
         * [GET] Метод получения конвертированной таблицы
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        converTable: function (tableID) {
            if (!__checkAccess() || !tableID){
                return;
            }

            let arr = [];
            let data = (arguments[1] && arguments[1] == __SERVER ? __CLIENT__.getValue(tableID, 'data', __SERVER) : __CLIENT__.getValue(tableID, 'data'));

            data.forEach(function (item) {
                let idx = item.id.lastIndexOf('-b');
                let bIndex = item.id.substr(idx + 2);
                let componentId = item.id.substr(0, idx);

                if (~item.id.lastIndexOf('-b') && bIndex && !isNaN(bIndex)) {
                    if ((bIndex in arr) === false){
                        arr[bIndex] = {};
                    }

                    arr[bIndex][componentId] = item;
                    return;
                }
            });

            for (let i = 0; i < arr.length; i++){
                if (arr[i] == null){
                    arr.splice(i--, 1);
                }
            }

            return arr;
        },

        /**
         * [GET] Метод получения всех компонентов в стиле листа
         */
        getAsfData: function () {
            if (!__checkAccess()){
                return;
            }

            var arr = [];
            __DATA__.forEach(function (item) {
                if (item.type == 'appendable_table') {
                    var tmp_data = JSON.parse(JSON.stringify((!item.data ? [] : item.data)));

                    tmp_data.forEach(function (item_tbl) {
                        var id = item_tbl.id.match(/(.+)\-b(\d+)/);
                        if (id) {
                            item_tbl.____parentID____ = item.id;
                            item_tbl.____parentType____ = item.type;
                            item_tbl.id = id[1];
                            item_tbl.____index____ = id[2];
                            arr.push(item_tbl);
                        }
                    });
                    return;
                }

                arr.push(item);
            });
            return arr;
        },

        /**
         * [SET] Метод добавляет данные в `dataList`
         * @param   {Object}    object    Объект или массив объектов
         */
        addAsfData: function (object) {
            if (!__checkAccess() || arguments.length < 1){
                return;
            }

            if (!Array.isArray(object)){
                object = [object];
            }

            var oldList = (arguments[1] && arguments[1] == __SERVER ? __CLIENT__.getAsfData(__SERVER) : __CLIENT__.getAsfData());

            object.forEach(function (item) {
                oldList.push(item);
            });

            if (arguments[1] && arguments[1] != __SERVER) {
                __CLIENT__.setAsfData(oldList);
                return __CLIENT__;
            } else {
                __CLIENT__.setAsfData(oldList, __SERVER);
                return __SERVER__;
            }
        },

        /**
         * [SET] Метод изменения всех компонентов из листа
         * @param   {Array}     newList    Массив - лист объектов
         */
        setAsfData: function (newList) {
            if (!__checkAccess() || arguments.length < 1){
                return;
            }

            var arr = [];
            var tmp = [];

            newList.forEach(function (item) {
                if (item.____parentType____ && item.____parentID____ && item.____index____) {
                    var tblIdx = tmp.indexOf(item.____parentID____);
                    var tmpItem = JSON.parse(JSON.stringify(item));

                    tmpItem.id += '-b' + item.____index____;

                    delete tmpItem.____parentType____;
                    delete tmpItem.____parentID____;
                    delete tmpItem.____index____;

                    if (tblIdx === -1) {
                        tmp.push(item.____parentID____);
                        arr.push({
                            id: item.____parentID____,
                            type: item.____parentType____,
                            data: [tmpItem]
                        });
                    }

                    if (arr[tblIdx]){
                        arr[tblIdx].data.push(tmpItem);
                    }

                    return;
                }

                arr.push(item);
                tmp.push(null);
            });

            __DATA__ = arr;
            return (arguments[1] && arguments[1] == __SERVER ? __SERVER__ : __CLIENT__);
        },

        /**
         * [Add] Метод добавления строк в дин.таблицу
         * @param   {String}    tableID     Идентификатор (id) таблицы
         * @param   {Int}       count       кол-во строк по умолчанию `1`
         * @param   {Boolean}   visual      Если данный параметр true то ряд(ы) не добавятся в таблицу по умолчанию `false`
         */
        addRowTable: function (tableID, count, visual) {
            if (!__checkAccess()) {
                return;
            }

            var defTableRow = false;

            if (!(+count) || !count) {
                count = 1;
            }

            var tableRowCount = (arguments[3] && arguments[3] == __SERVER ? __CLIENT__.getRowsCount(tableID, __SERVER) : __CLIENT__.getRowsCount(tableID));
            var defProps = (arguments[3] && arguments[3] == __SERVER ? __SERVER__.api('asforms/form/' + __CLASS__.getFormUUID()) : __CLIENT__.api('asforms/form/' + __CLASS__.getFormUUID()));
            var skips = ['config', 'style', 'properties', 'required', 'data', 'dataSource'];

            if (defProps.properties) {
                defProps.properties.forEach(function (item) {
                    if (item.id == tableID && item.config.appendRows && item.config.appendRows === true) {
                        if (!defTableRow) {
                            defTableRow = [];
                        }

                        for (var i = 0; i < count; i++) {
                            tableRowCount++;
                            item.properties.forEach(function (item_tbl) {
                                var nObj = {};
                                var tmp = JSON.parse(JSON.stringify(item_tbl));

                                tmp.____parentType____ = 'appendable_table';
                                tmp.____parentID____ = item.id;
                                tmp.____index____ = '' + tableRowCount;

                                for (var i in tmp) {
                                    if (skips.indexOf(i) !== -1) {
                                        continue;
                                    }

                                    nObj[i] = tmp[i];
                                }

                                defTableRow.push(nObj);
                            });
                        }

                        if (!visual && defTableRow && defTableRow.length > 0) {
                            (arguments[3] && arguments[3] == __SERVER ? __CLIENT__.addAsfData(defTableRow, __SERVER) : __CLIENT__.addAsfData(defTableRow));
                        }

                        return;
                    }
                });
            }

            return defTableRow;
        },

        /**
         * [Remove] Метод удаления строк из дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         * @param {Int | Array} index  номер строки
         */
        removeRowTable: function (tableID, index) {
            if (!__checkAccess() || arguments.length < 1) {
                return;
            }

            var dataList = (arguments[2] && arguments[2] == __SERVER ? __CLIENT__.getAsfData(__SERVER) : __CLIENT__.getAsfData());
            var newList = [];
            var visualRows = [];
            var tmp = {t1: [], t2: []};

            if (!Array.isArray(index)) {
                index = [index];
            }

            dataList.forEach(function (item) {
                if (item.____parentID____ == tableID && item.____index____ && (index.indexOf('' + item.____index____) !== -1 || index.indexOf(+item.____index____) !== -1)){
                    return;
                }

                newList.push(item);

                if (item.____parentID____ == tableID && item.____index____){
                    visualRows.push({
                        textIdx: +item.____index____,
                        idx: newList.length - 1
                    });
                }
            });

            visualRows
                .sort(function (i, j) {
                    return i.textIdx > j.textIdx;
                })
                .forEach(function (i) {
                    if (!tmp.t1[i.textIdx]){
                        tmp.t1[i.textIdx] = [];
                    }

                    tmp.t1[i.textIdx].push(i.idx);
                });

            tmp.t1.forEach(function (item) {
                tmp.t2.push(item);
            });

            tmp.t2.forEach(function (iArr, k) {
                iArr.forEach(function (i) {
                    newList[i].____index____ = '' + (k + 1);
                });
            });

            if (!arguments[2] && arguments[2] == __SERVER) {
                __CLIENT__.setAsfData(newList, __SERVER);
                return __SERVER__;
            } else {
                __CLIENT__.setAsfData(newList);
                return __CLIENT__;
            }
        },

        /**
         * [GET] Метод получения строк (рядов) в дин.таблице
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        getRowsCount: function (tableID) {
            if (!__checkAccess() || !tableID) {
                return;
            }

            if (!arguments[1] || arguments[1] != __SERVER) {
                return __CLIENT__.converTable(tableID).length;
            } else {
                return __CLIENT__.converTable(tableID, __SERVER).length;
            }
        },

        /**
         * Метод очищает содержимое дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         */
        clearTable: function (tableID) {
            __DATA__.forEach(function (item) {
                if (item.id == tableID) {
                    item.data = [];
                    return;
                }
            });

            return __TYPE_CONNECTED__;
        },

        /**
         * Метод выполнения API Synergy
         * @param {String}    method       Метод запроса /rest/api/asforms/data/404 или asforms/data/404
         * @param {Function}  callback     Функция возвращающая результат запроса
         *
         * @param {Object}    options      Это объект в который входят такие параметры как:
         *       @param {String}    type      Тип запроса: GET или POST по умолчанию `GET`
         *       @param {Object}    data      Объект в которой идет ключ и значение например { key: 'xxx', key2: 'xxx' }
         *       @param {Boolean}   async     Асинхронность функции по умолчанию `true`
         *       @param {String}    dataType  Тип данных по умолчанию `json`
         *       @param {String}    login     Логин пользователя (для выполнения метода от другого пользователя) *Не обязательно*
         *       @param {String}    password  Пароль пользователя *Не обязательно*
         *
         * @param {Function}  errorHandle  Функция возвращающая ошибку
         */
        api: function (method, callback, options, errorHandle) {
            if (!__LOADFORM__ || arguments.length < 1) {
                return;
            }

            try {
                return $.ajax({
                    url: __HOST_ORIGIN__ + '/Synergy/rest/api/' + method.replace(/(.*rest\/api\/)/, ''),
                    type: (!options || !options.hasOwnProperty('type') ? 'GET' : options.type),
                    async: (!options || !options.hasOwnProperty('async') ? false : options.async),
                    beforeSend: function (xhr) {
                        xhr.setRequestHeader('Authorization', 'Basic ' + btoa(
                            (!options || !options.login || !options.password ? __LOGIN__ + ':' + __PASSWORD__ : options.login + ':' + options.password)
                        ));
                    },
                    data: (!options || !options.hasOwnProperty('data') ? {} : options.data),
                    success: callback,
                    error: errorHandle,
                    dataType: (!options || !options.hasOwnProperty('dataType') ? 'json' : options.dataType)
                })[(options && options.hasOwnProperty('dataType') && options.dataType == 'text' ? 'responseText' : 'responseJSON')];
            } catch (err) {
                return err.message;
            }
        },

        /**
         * Метод сохранения формы
         * @param   {Func}   callback   Возврщает результат в асинхронносте по умолчанию `false`
         */
        save: function (callback) {
            if (!__LOADFORM__) {
                return;
            }

            if (!arguments[0] || arguments[0] != __SERVER) {
                var save = __CLIENT__.api('asforms/data/save', callback, {
                    async: (!callback ? false : true),
                    type: 'POST',
                    data: {
                        uuid: __CLASS__.getDataUUID(),
                        data: '"data":' + JSON.stringify(__DATA__)
                    }
                });

                return save;
            } else {
                var save = __SERVER__.api('asforms/data/save', {
                    type: 'POST',
                    data: {
                        uuid: __CLASS__.getDataUUID(),
                        data: '"data":' + JSON.stringify(__DATA__)
                    }
                });

                return {save: save, data: '"data":' + JSON.stringify(__DATA__)};
            }
        },

        ApiUtils: {
            /**
             * Возвращает `documentID` по dataUUID
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             * @param   {Func}   callback   Возврщает результат в асинхронносте по умолчанию `false`
             */
            getDocumentID: function (dataUUID, callback) {
                if (!dataUUID) {
                    return;
                }

                if (!arguments[1] || arguments[1] != __SERVER) {
                    return __CLIENT__.api('formPlayer/documentIdentifier?dataUUID=' + dataUUID, callback, {
                        async: (!callback ? false : true),
                        dataType: 'text'
                    });
                } else {
                    return __SERVER__.api('formPlayer/documentIdentifier?dataUUID=' + dataUUID, {
                        dataType: 'text'
                    });
                }
            },

            /**
             * Возвращает `dataUUID` по documentID
             * @param   {String}   documentID    Идентификатор документа
             * @param   {Func}   callback   Возврщает результат в асинхронносте по умолчанию `false`
             */
            getDataUUID: function (documentID, callback) {
                if (!documentID) {
                    return;
                }

                if (!arguments[1] || arguments[1] != __SERVER) {
                    return __CLIENT__.api('formPlayer/getAsfDataUUID?documentID=' + documentID, callback, {
                        async: (!callback ? false : true)
                    });
                } else {
                    return __SERVER__.api('formPlayer/getAsfDataUUID?documentID=' + documentID);
                }
            },

            /**
             * Возвращает ход выполнения документа
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             * @param   {Func}   callback   Возврщает результат в асинхронносте по умолчанию `false`
             */
            getProcessExecution: function (dataUUID, callback) {
                if (!dataUUID) {
                    return;
                }

                if (!arguments[1] || arguments[1] != __SERVER) {
                    return __CLIENT__.api('formPlayer/getProcessExecution?dataUUID=' + dataUUID, callback, {
                        async: (!callback ? false : true)
                    });
                } else {
                    return __SERVER__.api('formPlayer/getProcessExecution?dataUUID=' + dataUUID);
                }
            },

            /**
             * Возвращает список реестров (доступных пользователю)
             * @param   {Func}   callback   Возврщает результат в асинхронносте по умолчанию `false`
             */
            getRegistryList: function (callback) {
                if (!__checkAccess()) {
                    return;
                }

                if (!arguments[0] || arguments[0] != __SERVER) {
                    return __CLIENT__.api('registry/list', callback, {
                        async: (!callback ? false : true)
                    });
                } else {
                    return __SERVER__.api('registry/list');
                }
            }
        }
    }

    this.server = {
        /**
         * Метод загрузки формы
         * @param {String | Integer} dataUUID   Идентификатор данных по форме
         */
        load: function (dataUUID, version) {
            return __CLIENT__.load(dataUUID, version, __SERVER);
        },

        /**
         * Метод загрузки нескольких форм
         * @param {Array | String | Integer} dataUUIDs   идентификатор(ы) данных по форме
         */
        multiLoad: function (dataUUIDs) {
            return __CLIENT__.multiLoad(dataUUIDs, __SERVER);
        },

        /**
         * Получение данных загруженных форм
         */
        getFormsList: function () {
            return __MULTIFORMSDATA__;
        },

        /**
         * Выбрать с какой формой будем работать
         */
        selectForm: function (dataUUID) {
            return __CLIENT__.selectForm(dataUUID, __SERVER);
        },

        /**
         * Вернуть форму загруженную не с мультизагрузки если такая была
         */
        regainForm: function () {
            return __CLIENT__.regainForm();
        },

        /**
         * [GET] Метод получения значения компонента
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то ключ *не обязательно* иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то ключ иначе *не обязательно*
         */
        getValue: function () {
            var args = Array.prototype.slice.call(arguments, 0);
            args.push(__SERVER);

            return __CLIENT__.getValue.apply(null, args);
        },

        /**
         * [SET] Метод установки нового значения компоненту
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то значение или объект иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы то ключ для значения *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то значение или объект иначе *не обязательно*
         * @param   {String}    arg5    Если компонент в дин таблицы то ключ для значения иначе *не обязательно*
         *
         * Пример:
         *      Вне дин.таблицы: setValue('component1','Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Вне дин.таблицы: setValue('component2',{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         *
         *
         *      Дин.таблица: setValue('component1','table',1,'Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Дин.таблица: setValue('component2','table',1,45000,'key');  - "45000" в скобках или без запишется в ключ "key"
         *      Дин.таблица: setValue('component3','table',1,{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         */
        setValue: function () {
            var args = Array.prototype.slice.call(arguments, 0);
            args.push(__SERVER);

            return __CLIENT__.setValue.apply(null, args);
        },

        /**
         * [GET] Метод получения конвертированной таблицы
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        converTable: function (tableID) {
            return __CLIENT__.converTable(tableID, __SERVER);
        },

        /**
         * [GET] Метод получения всех компонентов в стиле листа
         */
        getAsfData: function () {
            return __CLIENT__.getAsfData(__SERVER);
        },

        /**
         * [SET] Метод добавляет данные в `dataList`
         * @param   {Object}    object    Объект или массив объектов
         */
        addAsfData: function (object) {
            return __CLIENT__.addAsfData(object, __SERVER);
        },

        /**
         * [SET] Метод изменения всех компонентов из листа
         * @param   {Array}     newList    Массив - лист объектов
         */
        setAsfData: function (newList) {
            return __CLIENT__.setAsfData(newList, __SERVER);
        },

        /**
         * [Add] Метод добавления строк в дин.таблицу
         * @param   {String}    tableID     Идентификатор (id) таблицы
         * @param   {Int}       count       кол-во строк по умолчанию `1`
         * @param   {Boolean}   visual      Если данный параметр true то ряд(ы) не добавятся в таблицу по умолчанию `false`
         */
        addRowTable: function (tableID, count, visual) {
            return __CLIENT__.addRowTable(tableID, count, visual, __SERVER);
        },

        /**
         * [Remove] Метод удаления строк из дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         * @param {Int | Array} index  номер строки
         */
        removeRowTable: function (tableID, index) {
            return __CLIENT__.removeRowTable(tableID, index, __SERVER);
        },

        /**
         * [GET] Метод получения строк (рядов) в дин.таблице
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        getRowsCount: function (tableID) {
            return __CLIENT__.getRowsCount(tableID, __SERVER);
        },

        /**
         * Метод очищает содержимое дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         */
        clearTable: function (tableID) {
            return __CLIENT__.clearTable(tableID, __SERVER);
        },

        /**
         * Метод сохранения формы
         */
        save: function () {
            return __CLIENT__.save(__SERVER);
        },

        /**
         * Методы работ на сервере платформы Synergy `Блок процессы`
         * @param   {String}    method      Метод запроса /rest/api/asforms/data/404 или asforms/data/404
         *       @param {String}    type        Тип запроса: GET или POST по умолчанию `GET`
         *       @param {Object}    data        Объект в которой идет ключ и значение например { key: 'xxx', key2: 'xxx' }
         *       @param {String}    dataType    Тип данных *text|json* по умолчанию `json`
         *       @param {String}    login       Логин пользователя (для выполнения метода от другого пользователя) *Не обязательно*
         *       @param {String}    password    Пароль пользователя *Не обязательно*
         */
        api: function (method, options) {
            if (!__LOADFORM__ || arguments.length < 1) {
                return;
            }

            method = method.replace(/(.*rest\/api\/)/, '');
            var type = (!options || !options.type ? 'GET' : options.type);
            var api;
            var result;

            var host = 'http://127.0.0.1:8080/Synergy/rest/api/';
            var client = new org.apache.commons.httpclient.HttpClient();
            var creds = new org.apache.commons.httpclient.UsernamePasswordCredentials((options && options.login ? options.login : __LOGIN__), (options && options.password ? options.password : __PASSWORD__));

            client.getParams().setAuthenticationPreemptive(true);
            client.getState().setCredentials(org.apache.commons.httpclient.auth.AuthScope.ANY, creds);

            switch (type) {
                case 'GET':
                    var _p = [];
                    if (options && options.data) {
                        for (var p in options.data) {
                            _p.push(p + '=' + options.data[p]);
                        }
                    }

                    api = new org.apache.commons.httpclient.methods.GetMethod(host + method + _p.join('&amp'));

                    api.setRequestHeader('Content-type', 'application/json');
                    client.executeMethod(api);

                    result = api.getResponseBodyAsString();
                    break;
                case 'POST':
                    api = new org.apache.commons.httpclient.methods.PostMethod(host + method);

                    if (options && options.data) {
                        for (var p in options.data) {
                            api.addParameter(p, options.data[p]);
                        }
                    }

                    api.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
                    result = client.executeMethod(api);
                    break;
                default:
            }

            api.releaseConnection();

            return (options && options.dataType == 'text' ? result : JSON.parse(result));
        },

        ApiUtils: {
            /**
             * Возвращает `documentID` по dataUUID
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             */
            getDocumentID: function (dataUUID) {
                return __CLIENT__.ApiUtils.getDocumentID(dataUUID, __SERVER);
            },

            /**
             * Возвращает `dataUUID` по documentID
             * @param   {String}   documentID    Идентификатор документа
             */
            getDataUUID: function (documentID) {
                return __CLIENT__.ApiUtils.getDataUUID(documentID, __SERVER);
            },

            /**
             * Возвращает ход выполнения документа
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             */
            getProcessExecution: function (dataUUID) {
                return __CLIENT__.ApiUtils.getProcessExecution(dataUUID, __SERVER);
            },

            /**
             * Возвращает список реестров (доступных пользователю)
             */
            getRegistryList: function () {
                return __CLIENT__.ApiUtils.getRegistryList(__SERVER);
            }
        }
    }

    /**
     * Информация о данном классе
     */
    this.about = {
        author: function () {
            return 'Telegram:\t@yandexphp\n';
        },
        platform_versions: {
            '3.12': '[Not support] - not verified',
            '3.14': '[Not support] - not verified',
            '3.15': '[Not support] - not verified',
            '4.0': true,
            '4.1': true
        },
        version: 'Version: 0.5.2 Realese',
        created: '17.10.2018'
    }

    /**
     * Очистить работу
     */
    this.destroy = function () {
        __PROTOCOL__ = __HOSTNAME__ = __HOST_ORIGIN__ = __BASIC_AUTH__ = __DOCUMENT_ID__ = __DATAUUID__ = __NODE_UUID__ =
            __FORM_UUID__ = __DATA__ = __FULL_DATA__ = __DOCUMENT_CHANGE__ = __FORM_VERSION__ = __OLD_DATAUUID__ = __HOST__ = __LOGIN__ = __PASSWORD__ = null;

        __EXECUTE__ = __LOADFORM__ = __FORMDATA__ = false;
        __CLIENT__ = __CLASS__.client;
        __SERVER__ = __CLASS__.server;
        __CONNECT_LIST__ = __MULTIFORMSDATA__ = {};
        __TYPE_CONNECTED__ = __CLASS__;
    }

    __CLASS__ = this;

    (function __constructor() {
        if(!Object.hasOwnProperty('assign')) {
            Object.prototype.assign = function () {
                let res = arguments[0];

                for (let i = 1; i < arguments.length; i++) {
                    let obj = arguments[i];
                    let keys = Object.keys(obj);

                    for (let j = 0; j < keys.length; j++) {
                        res[keys[j]] = obj[keys[j]];
                    }
                }

                return res;
            }
        }

        String.prototype.encodeURICyrillic = function () {
            return this.replace(/[А-Я]/ig, function (s) {
                return encodeURIComponent("" + s);
            });
        }

        __CLASS__.destroy();
    })();

    return __CLASS__;
}

/**
 * Создаем класс jSynergy
 */
let jSynergy = new __classSynergy();
jSynergy.setConnection(false, login, password);
