/**
 * Класс для работы с Synergy by yandexphp
 */
String.prototype.encodeURICyrillic = function(){
	return this.replace(/[А-Я]/ig,function(s){return encodeURIComponent(""+s);});
}
function __classSynergy(){

    const __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__) return;
            else if(!dataUUID) return;
            if(arguments[2] && arguments[2] == __SERVER){
                var data = __SERVER__.api('asforms/data/' + dataUUID + (version ? '?version=' + version : ''));
                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;
            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;
                }
            }
            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()) return;
            else if(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 == 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) {
                                if(bItem.type == 'numericinput'){
                                    defKey = 'key';
                                    args.v = args.v.toString().replace(/\s*/g,'');
                                }
                                if (typeof(args.v) === 'object')
                                    for (var aItem in args.v) 
                                        (args.o && args.o == __SERVER) ? bItem[aItem] = args.v[aItem] : bItem[aItem][args.o] = args.v[aItem];
                                else bItem[(args.o && args.o != __SERVER) ? args.o : defKey] = args.v;
                                return;
                            }
                        });
                        return;
                    }
                });
            } else {
                __DATA__.forEach(function(item) {
                    if (item.id == args.c) {
                        if(item.type == 'numericinput'){
                            defKey = 'key';
                            args.v = args.v.replace(/\s*/g,'');
                        }
                        if (typeof(args.vt) === 'object')
                            for (var aItem in args.vt)
                                (args.ob && args.ob == __SERVER) ? item[aItem] = args.vt[aItem] : item[aItem][args.ob] = args.vt[aItem];
                        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()) return;
            else if(!tableID) return;
            var arr = [];
            var b;
            var data = (arguments[1] && arguments[1] == __SERVER ? __CLIENT__.getValue(tableID,'data',__SERVER) : __CLIENT__.getValue(tableID,'data'));
            data.forEach(function(item) {
                if (item.id.indexOf('-b') !== -1 && (b = item.id.match(/(.+)\-b(\d+)/))) {
                    if ((b[2] in arr) === false) arr[b[2]] = {};
                    arr[b[2]][b[1]] = item;
                    return;
                }
            });
            for (var 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()) return;
            else if(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()) return;
            else if(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()) return;
            else if(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,key) {
                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()) return;
                else if(!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__) return;
            else if(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.splice.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.splice.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__) return;
            else if(arguments.length < 1) return;
            method = method.replace(/(.*rest\/api\/)/,'');
            type = (!options || !options.type ? 'GET' : options.type);
            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]);
                    var api = new org.apache.commons.httpclient.methods.GetMethod(host + method + _p.join('&amp'));
                    api.setRequestHeader('Content-type','application/json');
                    client.executeMethod(api);
                    var result = api.getResponseBodyAsString();
                    break;
                case 'POST':
                    var 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');
                    var 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':'not verified'
        },
        version: 'Version: 0.5.0 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(){
        __CLASS__.destroy();
    })();

    return __CLASS__;
}

/**
 * Создаем класс jSynergy
 */
let jSynergy = new __classSynergy();