Runner2k11 / Answers2

// ==UserScript==
// @name        Answers2
// @description Tooltip for questions
// @match       http://obispace.ru/obi_life/testing/my_tests/*
// @match       http://obispace.ru/obuchenie-i-razvitie-v-obi/testing/my_tests/*
// @match       https://obispace.ru/obi_life/testing/my_tests/*
// @match       https://obispace.ru/obuchenie-i-razvitie-v-obi/testing/my_tests/*
// @updateURL   https://openuserjs.org/meta/Runner2k11/Answers2.meta.js
// @downloadURL https://openuserjs.org/install/Runner2k11/Answers2.user.js
// @grant       none
// @version     2.05
// @license     MIT
// @run-at      document-end
// @copyright   2017, Runner2k11 (https://openuserjs.org/users/Runner2k11)
// ==/UserScript==


(function(){

const JSONfirst = 2;		// 1 - urlJSON download first, 2 - urlJSON2 download first
//QuestionsOrig - скаченные вопросы-ответы, QuestionsCurr - отвеченные в текущем тесте вопросы-ответы, Question - md5 текущего вопроса
var QuestionsOrig = {}, QuestionsCurr = {}, Question;
var localStorage;
/*
https://jsonbin.io/60645a7df2163e5ad3f698cf/1#mode=edit
var urlJSON = "https://api.jsonbin.io/v3/b/60645a7df2163e5ad3f698cf";
var urlJSONsc = "$2b$10$P29/PZweSp/Ft9FRAKGbIOQn6PA2w7ufsPjHD65ud2U.CESD.j/1G";
*/
//https://jsonbin.it/bins/ttppUpgC
var urlJSON = "https://api.jsonbin.it/bins/ttppUpgC";
var urlJSON2URL = "https://jsonblob.com/f8b01e36-87d4-11eb-96ff-fb776697762b";
var urlJSON2 = "https://jsonblob.com/api/jsonBlob/f8b01e36-87d4-11eb-96ff-fb776697762b";
var bQALoaded = false;

const WaitForAutoAnswer = 20000;
const cNOk = 100;
const cOk = 255;

//const SearchURL = "http://www.google.ru/search?q=";
const SearchURL = "https://yandex.ru/search/?text=";


if (!String.prototype.trim) {
	(function() {
		// Вырезаем BOM и неразрывный пробел
		String.prototype.trim = function() {
		return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,'');
		};
	})();
}
if (!String.prototype.prepare) {
	(function() {
		// Общая подготовка строки
		String.prototype.prepare = function() {
		return this.toLowerCase().replace(/ /g,' ').replace(/\s+/g,' ').trim();
		};
	})();
}


var md5 = new function() {
  var l='length',
  h=[
   '0123456789abcdef',0x0F,0x80,0xFFFF,
    0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476
  ],
  x=[
    [0,1,[7,12,17,22]],
    [1,5,[5, 9,14,20]],
    [5,3,[4,11,16,23]],
    [0,7,[6,10,15,21]]
  ],
  A=function(x,y,z){
    return(((x>>16)+(y>>16)+((z=(x&h[3])+(y&h[3]))>>16))<<16)|(z&h[3])
  },
  B=function(s){
    var n=((s[l]+8)>>6)+1,b=new Array(1+n*16).join('0').split('');
    for(var i=0;i<s[l];i++)b[i>>2]|=s.charCodeAt(i)<<((i%4)*8);
    return(b[i>>2]|=h[2]<<((i%4)*8),b[n*16-2]=s[l]*8,b)
  },
  R=function(n,c){return(n<<c)|(n>>>(32-c))},
  C=function(q,a,b,x,s,t){return A(R(A(A(a,q),A(x,t)),s),b)},
  F=function(a,b,c,d,x,s,t){return C((b&c)|((~b)&d),a,b,x,s,t)},
  G=function(a,b,c,d,x,s,t){return C((b&d)|(c&(~d)),a,b,x,s,t)},
  H=function(a,b,c,d,x,s,t){return C(b^c^d,a,b,x,s,t)},
  I=function(a,b,c,d,x,s,t){return C(c^(b|(~d)),a,b,x,s,t)},
  _=[F,G,H,I],
  S=(function(){
    with(Math)for(var i=0,a=[],x=pow(2,32);i<64;a[i]=floor(abs(sin(++i))*x));
    return a
  })(),
  X=function (n){
    for(var j=0,s='';j<4;j++)
      s+=h[0].charAt((n>>(j*8+4))&h[1])+h[0].charAt((n>>(j*8))&h[1]);
    return s
  };
  return function(s){
    var $=B(''+s),a=[0,1,2,3],b=[0,3,2,1],v=[h[4],h[5],h[6],h[7]];
    for(var i,j,k,N=0,J=0,o=[].concat(v);N<$[l];N+=16,o=[].concat(v),J=0){
      for(i=0;i<4;i++)
        for(j=0;j<4;j++)
          for(k=0;k<4;k++,a.unshift(a.pop()))
            v[b[k]]=_[i](
              v[a[0]],
              v[a[1]],
              v[a[2]],
              v[a[3]],
              $[N+(((j*4+k)*x[i][1]+x[i][0])%16)],
              x[i][2][k],
              S[J++]
            );
      for(i=0;i<4;i++)
        v[i]=A(v[i],o[i]);
    };return X(v[0])+X(v[1])+X(v[2])+X(v[3]);
}};


function addCss(cssString) {
    var head = document.getElementsByTagName('head')[0];
    var newCss = document.createElement('style');
    newCss.type = "text/css";
    newCss.innerHTML = cssString;
    head.appendChild(newCss);
}

addCss(
'.toast { line-height: 12px; background-color: #A0D0F0; ' +
		'box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5), 0 3px 6px -3px rgba(0,0,0,0.25); ' +
		'text-shadow: 0 2px 2px rgba(255,255,255,1); ' +
		'border: 1px solid #6090B0; border-radius: 7px; padding: 3px; font-size: 10px }' +
'.toast:not(:last-child) { margin-bottom: 0.75rem; }' +
'.toast_show { display: block; }'
);


var Toast = function (element, config) {
  var
    _this = this,
    _element = element,
    _config = {
      autohide: true,
      delay: 5000
    };
  for (var prop in config) {
    _config[prop] = config[prop];
  }
  Object.defineProperty(this, 'element', {
    get: function () {
      return _element;
    }
  });
  Object.defineProperty(this, 'config', {
    get: function () {
      return _config;
    }
  });
}

Toast.prototype = {
  show: function () {
    var _this = this;
    this.element.classList.add('toast_show');
    if (this.config.autohide) {
      setTimeout(function () {
        _this.hide();
      }, this.config.delay)
    }
  },
  hide: function () {
    var event = new CustomEvent('hidden.toast', { detail: { toast: this.element } });
    this.element.classList.remove('toast_show');
    document.dispatchEvent(event);
  }
};

Toast.create = function (text, color) {
  var
    fragment = document.createDocumentFragment(),
    toast = document.createElement('div');
  toast.classList.add('toast');
  toast.textContent = text;
  fragment.appendChild(toast);
  return fragment;
};

Toast.add = function (params) {
  var config = {
    text: 'Текст сообщения...',
    autohide: true,
    delay: 10000
  };
  if (params !== undefined) {
    for (var item in params) {
      config[item] = params[item];
    }
  }
  if (!document.querySelector('.toasts')) {
    var container = document.createElement('div');
    container.classList.add('toasts');
    //container.style.cssText = 'position: fixed; bottom: 50px; right: 1px; width: 190px';
    container.style.cssText = 'position: fixed; bottom: 60px; right: 1px; width: 190px';
    document.body.appendChild(container);
  }
  document.querySelector('.toasts').appendChild(Toast.create(config.text, config.color));
  var toasts = document.querySelectorAll('.toast');
  var toast = new Toast(toasts[toasts.length - 1], { autohide: config.autohide, delay: config.delay });
  toast.show();
  return toast;
}

document.addEventListener('hidden.toast', function (e) {
  var element = e.detail.toast;
  if (element) {
    element.parentNode.removeChild(element);
  }
});


function MergeAnswers(a1, a2) {
    //a1 берём за основу
    Object.keys(a1).forEach(function(key, id) {
        if ( (key in a2) && (JSON.stringify(a1[key]) != JSON.stringify(a2[key])) ) {
            //если в a2 есть такой вопрос и содержимое разное
            var n = a2[key].length;
            for (var i = 1; i < n; i += 3) {
                var PosPresent = a1[key].indexOf(a2[key][i]);
                if ( PosPresent == -1 ) {
                    //не было такого ответа - добавляем его полностью: текст, md5, статус
                    a1[key] = a1[key].concat(a2[key].slice(i,i+3));
                    console.log('add answer to key: ' + key + ' < ' + a2[key].slice(i,i+3));
                } else {
                    //такой ответ был
                    if (a1[key][PosPresent+1] < cNOk) {
                        //и статус у него не cOk или сNOk
                        if (a2[key][i+1]>a1[key][PosPresent+1]) {
                            //то оставляем ответ с максимальным статусом: по числу попыток, cOk или CNOk
                            a1[key][PosPresent+1] = a2[key][i+1];
                            console.log('change answer in key: ' + key + ' < ' + a2[key].slice(i,i+3));
                        }
                    }
                }
            }
            //сортировка результата по статусу от большего к меньшему
            n = a1[key].length;
            for (i = 2; i < n - 3; i += 3) {
                var min = i;
                for (var j = i + 3; j < n; j += 3) {
                    if (a1[key][min] < a1[key][j]) { min = j; }
                }
                if (min != i) {
                    [a1[key][i-1], a1[key][min-1]] = [a1[key][min-1], a1[key][i-1]];
                    [a1[key][i], a1[key][min]] = [a1[key][min], a1[key][i]];
                    [a1[key][i+1], a1[key][min+1]] = [a1[key][min+1], a1[key][i+1]];
                }
            }
        }
    });
    //осталось добавить в базу вопросы-ответы, которых не было в a1
    Object.keys(a2).forEach(function(key, id) {
        if ( !(key in a1) ) {
            a1[key] = a2[key];
            console.log('add new key: ' + key);
        }
    });
    return a1;
}


function LoadAA(callback) {
    function Finishing(req) {
        document.getElementById('svgAnswersCounter').style.display='none';
        if (req.status == 200) {
            QuestionsOrig = ( typeof(req.response)=='string' ? JSON.parse(req.response) : req.response );
            if (!(localStorage)) {
                window.localStorage.setItem('Answers2', JSON.stringify(QuestionsOrig));
            }
        } else {
            if (localStorage) {
                QuestionsOrig = localStorage;
            } else {
                Toast.add({text: 'Data loading failed!'});
                return false;
            }
        }
        if ((req.status == 200)&&(localStorage)) {
            if (QuestionsOrig.ID > localStorage.ID) {
                QuestionsOrig = MergeAnswers(QuestionsOrig, localStorage);
            } else {
                QuestionsOrig = MergeAnswers(localStorage, QuestionsOrig);
            }
        }
        callback();
    }
	function getJSON1() {
	    var req = new XMLHttpRequest();
        req.timeout = 10000;
        req.open('GET', urlJSON, true);
        req.onreadystatechange = function() {
            if (req.readyState == 4) {
                console.log(urlJSON + ' - ' + (req.status == 200 ? 'Ok' : 'Fail'));
                Toast.add({text: 'DB1 download - ' + (req.status == 200 ? 'Ok' : 'Fail')});
                if((req.status != 200)&&(JSONfirst==1)) {
                    getJSON2();
                } else {
                    Finishing(req);
                }
            }
        };
        req.send();
	}
	function getJSON2() {
	    var req = new XMLHttpRequest();
	    req.timeout = 10000;
        req.open('GET', urlJSON2, true);
        req.onreadystatechange = function() {
            if (req.readyState == 4) {
                console.log(urlJSON2 + ' - ' + (req.status == 200 ? 'Ok' : 'Fail'));
                Toast.add({text: 'DB2 download - ' + (req.status == 200 ? 'Ok' : 'Fail')});
                if((req.status != 200)&&(JSONfirst==2)) {
                    getJSON1();
                } else {
                    Finishing(req);
                }
            }
        };
        req.send();
	}
	ShowAnswersCount();
	document.getElementById('svgAnswersCounter').style.display='block';
    localStorage = JSON.parse(window.localStorage.getItem('Answers2'));
    Toast.add({text: 'LocalStorage download - ' + (localStorage ? 'Ok' : 'Fail')});
	if (JSONfirst==1) {
		getJSON1();
	} else {
		getJSON2();
	}
}


function LoadQA() {
	function LoadQA_cb() {
        sessionStorage.setItem('QuestionsOrig', JSON.stringify(QuestionsOrig));
		sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
        bQALoaded = true;
		ShowAnswersCount();
	}
	if (1 == 2) {
		sessionStorage.removeItem('QuestionsOrig');
		sessionStorage.removeItem('QuestionsCurr');
	}
	if ( sessionStorage.getItem('QuestionsOrig') && sessionStorage.getItem('QuestionsCurr') ) {
		QuestionsOrig = JSON.parse(sessionStorage.getItem('QuestionsOrig'));
		QuestionsCurr = JSON.parse(sessionStorage.getItem('QuestionsCurr'));
        bQALoaded = true;
		ShowAnswersCount();
	} else {
		LoadAA(LoadQA_cb);
	}
}


function AppendSaveJSON() {
    if (QuestionsOrig) {
        var blob = new Blob([JSON.stringify(QuestionsOrig)], {type: 'application/json'})
        var url = window.URL.createObjectURL(blob)
        var link = document.createElement('a')
        link.href = url;
        link.setAttribute('download', "Answers2.json");
        link.innerHTML = '&#8595;';
        link.title='Download Answers JSON';
        document.getElementById('AnswersDL').appendChild(link);
    }
}

function AppendLoadJSON() {
    function readFile_cb() {
        sessionStorage.setItem('QuestionsOrig', JSON.stringify(QuestionsOrig));
        sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
        bQALoaded = true;
        ShowAnswersCount();
    }
    function readFile(object){
        var file = object.target.files[0];
        var reader = new FileReader();
        reader.onload = function() {
            var json = JSON.parse(reader.result);
            json.ID = [Date.now()];
            window.localStorage.setItem('Answers2', JSON.stringify(json));
            LoadAA(readFile_cb);
        }
        reader.readAsText(file);
    }
    var fileInput = document.createElement("input");
    fileInput.type='file';
    fileInput.id='getFile';
    fileInput.style='display:none';
    fileInput.onchange=readFile;
    var link = document.createElement("a");
    link.href="#";
    link.setAttribute("onclick","document.getElementById('getFile').click(); return false;");
    link.innerHTML='&#8593;';
    link.title='Upload Answers JSON';
    document.getElementById('AnswersUL').appendChild(fileInput);
    document.getElementById('AnswersUL').appendChild(link);
}

function ShowAnswersCount() {
	var elems = document.getElementById('AnswersCounter');
	if (!elems) {
		elems= document.body.appendChild(document.createElement('div'));
		elems.id = 'AnswersCounter';
	}
	var j = 0;
	var j2 = 0;
    Object.keys(QuestionsOrig).forEach(function(key, id) {
        if (QuestionsOrig[key].includes(cOk) != 0) {
            j++;
        } else if (QuestionsOrig[key].includes(cNOk) != 0) {
            j2++;
		}
	});
	AppendScript(MyScripts);
	elems.innerHTML =
		'<style type="text/css">.acounter td {padding: 1px 3px 1px 3px;} .acountersvg {position: absolute; top: -3px; left: 7px; display: none;}</style>' +
		'<span style="z-index: 999; line-height: 12px; position: fixed; bottom: 1px; right: 1px; background-color: #A0D0F0; ' +
		'box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5), 0 3px 6px -3px rgba(0,0,0,0.25); ' +
		'text-shadow: 0 2px 2px rgba(255,255,255,1); ' +
		'border: 1px dashed #6090B0; border-radius: 7px; padding: 3px; text-align:center;">' +
		'<span style="font-size: 10px; font-weight: bold; color: red;">' +
		'<table class="acounter"><tr><td rowspan="2">' +
		'Answers<br>v' + GM_info.script.version +
		'</td><td rowspan="2"><a href=\'#\' onClick="ToggleAA(); return false;">Auto<br>' + (sessionStorage.getItem('AutoAnswer') ? 'on' : 'off') + '</a></td>' +
		'<td><font color="green">Ok</font></td><td><font color="brown">???</font></td><td>NOk</td><td><font color="black">All</font></td></tr><tr>' +
		'<td><font color="green">' + j + '</font></td><td><font color="brown">' + (Object.keys(QuestionsOrig).length - j - j2) + '</font></td><td>' + j2 + '</td><td><font color="black">' + Object.keys(QuestionsOrig).length +
		'</font></td></tr></table></span>' +
        '<span style="font-size: 9px; float: center;">DB: ' + (QuestionsOrig.ID ? '<a href="' + urlJSON2URL + '" target="_blank">' + dateFormat(QuestionsOrig.ID[0], "dd/mm/yyyy HH:MM") + '</a>' : 'Loading now...') + '</span>' +
        '<span id="AnswersDL" style="float:left"></span>' +
        '<span id="AnswersUL" style="float:right"></span>' +
        '<span id="svgAnswersCounter" class="acountersvg">' +
		'<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 50 50"><path fill="#00AF00" d="M24.9 44a18.7 18.7 0 1 0 0-37.4v4a14.6 14.6 0 0 1 0 29.3v4z"><animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"/></path></svg>' +
        '</span></span>';
    AppendLoadJSON();
    if (bQALoaded) AppendSaveJSON();
}


function AppendScript(s) {
	document.head.appendChild(document.createElement('script'))
		.innerHTML = s.toString().replace(/^function.*{|}$/g, '');
}
function MyScripts() {
	function ToggleAA() {
		if(sessionStorage.getItem('AutoAnswer')){
			sessionStorage.removeItem('AutoAnswer');
		}else{
			sessionStorage.setItem('AutoAnswer', 'true');
		}
		window.location.reload();
	}
}


function SetAutoAnswer(AutoCheck) {
	if(sessionStorage.getItem('AutoAnswer')){
		setTimeout(function(){AutoAnswer(AutoCheck)}, Math.floor(Math.random() * WaitForAutoAnswer * 0.75 + WaitForAutoAnswer * 0.5));
	}
}

function AutoAnswer(AutoCheck) {
    if (AutoCheck) {
        var elems = document.getElementsByClassName('b-question-list__item')[0].getElementsByClassName('b-answer-list__item');
        for (var i = 0; i < elems.length; i++) {
            if (elems[i].innerHTML.indexOf('_Ok:') != -1) {
                elems[i].getElementsByTagName('input')[0].checked = false;
                elems[i].getElementsByTagName('input')[0].click();
            }
        }
    }
	elems = document.getElementById('test_questinos');
	if (elems) {
		elems = elems.getElementsByClassName('b-submit');
		var AutoOff = true;
		for (i = 0; i < elems.length; i++) {
			if (elems[i].innerHTML.toLowerCase().indexOf('далее') != -1) {
				setTimeout(function(){ elems[i].click(); }, 2000);
				AutoOff = false;
				break;
			}
		}
		if (AutoOff) {
			sessionStorage.removeItem('AutoAnswer');
		}
	}
}


function SetOnClick() {
	var elems = document.getElementById('test_questinos');
	if (elems) {
		elems = elems.getElementsByClassName('b-submit');
		for (var i = 0; i < elems.length; i++) {
			elems[i].onclick = function() {
				var elems = document.getElementById('test_questinos').getElementsByClassName('checked');
				if (elems.length != 0) {
					for (var i = 0; i < elems.length; i++) {
						if (elems[i].tagName == 'LABEL') {
                            var s = elems[i].innerHTML.replace(/Q.*:/,'').replace(/<[^>]+>/g,'').replace(/&nbsp;/g,' ').replace(/\s+/g,' ').trim();
                            var Answer_str = (Answer_str ? Answer_str + '++' : '') + s;
							var Answer_md5 = (Answer_md5 ? Answer_md5 + '++' : '') + md5(s.toLowerCase());
						}
					}
                    QuestionsCurr[Question][1] = Answer_md5;
                    QuestionsCurr[Question][3] = Answer_str;
				} else {
                    delete QuestionsCurr[Question];
                }
                sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
			}
		}
	}
}

function FindQuestions() {
    function SetStyleAnswer(elems, mode) {
        elems.style.fontWeight = "bold"
        switch (mode) {
            case cOk:
                elems.style.backgroundColor = "#90EE90";
                elems.style.color = "green";
                var headAnswer = 'Q_Ok:&nbsp;&nbsp;';
                break;
            case cNOk:
                elems.style.backgroundColor = "red";
                headAnswer = 'Q_NOk:&nbsp;&nbsp;';
                break;
            default:
                elems.style.backgroundColor = "yellow";
                headAnswer = 'Q_?' + QuestionsOrig[Question][i2+1] + '?:&nbsp;&nbsp;';
        }
        return headAnswer;
    }
    function FormatAnswer(elems, mode) {
        var wwwAnswer = elems.innerHTML.replace(/<[^>]+>/g,'').trim();
        var wwwPos = elems.innerHTML.indexOf(wwwAnswer);
        elems.innerHTML = elems.innerHTML.substring(0,wwwPos) + SetStyleAnswer(elems, mode) + wwwAnswer + elems.innerHTML.substring(wwwPos + wwwAnswer.length);
    }
	var elems = document.getElementsByClassName('b-question-list__item');
	if (elems) {
		SetOnClick();
		for (var i = 0; i < elems.length; i++) {
			var Question_txt = elems[i].innerHTML.split('<')[0];
            elems[i].innerHTML = elems[i].innerHTML.substring(0,elems[i].innerHTML.indexOf(Question_txt)) + '<a class="b-link b-h0" href="' + SearchURL + Question_txt.replaceAll('"','') + '" target="_blank">' + Question_txt + '</a>' + elems[i].innerHTML.substring(elems[i].innerHTML.indexOf(Question_txt) + Question_txt.length);
            Question_txt = Question_txt.replace(/&nbsp;/g,' ').replace(/\s+/g,' ').trim();
            Question = md5(Question_txt.toLowerCase().replace(/[^0-9а-яa-z]/g,""));
            if ( !(Question in QuestionsCurr) ) {
                QuestionsCurr[Question] = [Question_txt, '', cOk, ''];
                sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
            }
            if ( Question in QuestionsOrig ) {
                var elems2 = elems[0].getElementsByClassName('b-answer-list__item');
                var SingleSelectAnswer = elems2[0].getElementsByTagName('input')[0].type == 'radio';
                var Found = false;
                for (var i2 = 1; i2 < QuestionsOrig[Question].length; i2 += 3) {
                    var arrAnswers = QuestionsOrig[Question][i2].split('++');
                    var ff = 0;
                    for (var ii = 0; ii < elems2.length; ii++) {
                        ff += arrAnswers.indexOf(md5(elems2[ii].innerHTML.replace(/<[^>]+>/g,'').prepare())) != -1;
                    }
                    if (ff == arrAnswers.length) {
                        //подходящий ответ найден
                        for (ii = 0; ii < elems2.length; ii++) {
                            if ( arrAnswers.indexOf(md5(elems2[ii].innerHTML.replace(/<[^>]+>/g,'').prepare())) != -1 ) {
                                FormatAnswer(elems2[ii], QuestionsOrig[Question][i2+1]);
                            }
                        }
                        /*
                             Single  Multi
                        cOk   F B     F B
                        cNok            B
                        c?              B
                        F - Found=true, B - need break
                        */
                        Found = (QuestionsOrig[Question][i2+1] == cOk);
                        if ((Found)||(!SingleSelectAnswer)) break;
                    }
                }
                if ( !Found ) {
                    for (i2 = 1; i2 < QuestionsOrig[Question].length; i2 += 3) {
                        elems2 = elems[i].appendChild(document.createElement('span'));
                        SetStyleAnswer(elems2, QuestionsOrig[Question][i2+1]);
                        elems2.innerHTML = '<br><br>' + (QuestionsOrig[Question][i2+2] == '' ? 'old style answer' : QuestionsOrig[Question][i2+2].replaceAll('++',' &&<br>'));
                    }
                }
                SetAutoAnswer(Found);
			} else {
				elems[i].innerHTML += '<br><font color="red">Вопрос отсутствует в базе =(</font>';
				SetAutoAnswer(false);
			}
		}
	}
}

function UpdateQA(a1, a2) {
	function UpdateQA_cb() {
        QuestionsOrig.ID = [Date.now()];
        //Обновляем ответы в основной базе. На входе:
        //QuestionsOrig - актуальная база с сервера, QuestionsCurr - вопросы-ответы с текущего теста и статусом их правильности
        //a1 - число правильных ответов, a2 - число неправильных ответов
        //В процессе обработки: b1 - рассчитанное число правильных ответов в QuestionOrig
        //если a1 будет равно b1, то значит все остальные ответы (со статусом '1') будут неверными
        var b1 = 0;
        Object.keys(QuestionsCurr).forEach(function(key, id) {
            console.log(QuestionsCurr);
            console.log('---begin--------------------');
            console.log('key: ' + key);
            console.log('Orig >');
            console.log(QuestionsOrig[key]);
            console.log('Curr >');
            console.log(QuestionsCurr[key]);
            if ( !(key in QuestionsOrig) ) {
                //если такого вопроса ранее не было, то просто добавляем
                QuestionsOrig[key] = QuestionsCurr[key];
            } else {
                //если такой вопрос уже был, то:
                //обновляем текст вопроса
                QuestionsOrig[key][0] = QuestionsCurr[key][0];
                var PosPresent = QuestionsOrig[key].indexOf(QuestionsCurr[key][1]);
                if ( PosPresent == -1 ) {
                    //не было такого ответа - добавляем его полностью: текст, md5, статус
                    QuestionsOrig[key] = QuestionsOrig[key].concat(QuestionsCurr[key].slice(1));
                } else {
                    //такой ответ был, обновляем его текст
                    QuestionsOrig[key][PosPresent+2] = QuestionsCurr[key][3];
                    if ( QuestionsCurr[key][2] != 1 ) {
                        //если текущий статус не '1' (соответственно это или cOk, или cNOk), то и записываем его в базу
                        QuestionsOrig[key][PosPresent+1] = QuestionsCurr[key][2];
                    } else {
                        //в противном случае, если в базе статус не cOk или cNOk, то увеличиваем его на 1
                        QuestionsOrig[key][PosPresent+1] += (QuestionsOrig[key][PosPresent+1] < 8 ? 1 : 0);
                    }
                }
            }
            console.log('Orig result >');
            console.log(QuestionsOrig[key]);
            console.log('------------------------------end---');
            //считаем сколько всего в результате стало правильных ответов
            PosPresent = QuestionsOrig[key].indexOf(QuestionsCurr[key][1]);
            b1 += (QuestionsOrig[key][PosPresent+1] == cOk ? 1 : 0);
        });
        Object.keys(QuestionsCurr).forEach(function(key, id) {
            //если число правильных ответов по тесту совпадает с посчитанным в Orig, то можем вычислить, какие ответы были не правильными
            if ( (a2 != 0) && (a1 == b1) ) {
                var PosPresent = QuestionsOrig[key].indexOf(QuestionsCurr[key][1]);
                if (QuestionsOrig[key][PosPresent+1] < 10) {
                    console.log('Key: ' + key + ' Pos: ' + PosPresent + ' -- вычисленный NOk!');
                    QuestionsOrig[key][PosPresent+1] = cNOk;
                }
            }
            //для упрощения в дальнейшем процесса поиска ответа, сортируем массив ответов по статусу cOk -> cNOk -> ...
            var n = QuestionsOrig[key].length;
            for (var i = 2; i < n - 3; i += 3) {
                var min = i;
                for (var j = i + 3; j < n; j += 3) {
                    if (QuestionsOrig[key][min] < QuestionsOrig[key][j]) { min = j; }
                }
                if (min != i) {
                    [QuestionsOrig[key][i-1], QuestionsOrig[key][min-1]] = [QuestionsOrig[key][min-1], QuestionsOrig[key][i-1]];
                    [QuestionsOrig[key][i], QuestionsOrig[key][min]] = [QuestionsOrig[key][min], QuestionsOrig[key][i]];
                    [QuestionsOrig[key][i+1], QuestionsOrig[key][min+1]] = [QuestionsOrig[key][min+1], QuestionsOrig[key][i+1]];
                }
            }
        });
        sessionStorage.setItem('QuestionsOrig', JSON.stringify(QuestionsOrig));
        /*
	    var req = new XMLHttpRequest();
        req.open('POST', urlJSON, true);
        req.setRequestHeader('Content-type', 'application/json');
        req.setRequestHeader('Access-Control-Allow-Origin', '*');
        req.onload = function () {
            console.log('DB1 save - status:' + req.status);
            Toast.add({text: 'DB1 save - status:' + req.status});
        }
        req.send(JSON.stringify(QuestionsOrig));
        */
	    var req2 = new XMLHttpRequest();
        req2.open('PUT', urlJSON2, true);
        req2.setRequestHeader('Content-type', 'application/json');
        req2.onload = function () {
            console.log('DB2 save - status:' + req2.status);
            Toast.add({text: 'DB2 save - status:' + req2.status});
        }
        req2.send(JSON.stringify(QuestionsOrig));
        window.localStorage.setItem('Answers2', JSON.stringify(QuestionsOrig));
        Toast.add({text: 'LocalStorage save - Ok'});
        ShowAnswersCount();
	}
	if (Object.keys(QuestionsCurr).length > 0) {LoadAA(UpdateQA_cb);}
}

function CheckResult() {
    //до этого по умолчанию все ответы заранее были со статусом cOk
	if ( Object.keys(QuestionsCurr).length > 0 ) {
        var i; var a1 = 0; var a2 = 0;
		//поиск списка неверных ответов
		try {
			var elems = document.getElementsByClassName('b-question-list')[0].getElementsByTagName('li');
		} catch (e) { }
		if (elems) {
			//есть список неверных ответов
			for (i = 0; i < elems.length; i++) {
                //сначала проверим, что на такой вопрос мы отвечали (а, например, не пропустили) и если да, то ставим статус NOk
				if (md5(elems[i].innerHTML.prepare().replace(/[^0-9а-яa-z]/g,"")) in QuestionsCurr) {QuestionsCurr[md5(elems[i].innerHTML.prepare().replace(/[^0-9а-яa-z]/g,""))][2] = cNOk;}
			}
			sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
		} else {
			//если списка нет, например, при успешной сдаче, и результат не 100%, то выставляем 1 - точно неизвестно какие ответы верные, какие нет
			elems = document.getElementsByClassName('b-result-test__input');
            if ( (!elems) || (Number(String(elems[0].value).replace('%','')) != 100) ) { //операторы && и || - операторы короткого замыкания, где правая часть не вычисляется, если слева уже однозначное решение по if
                Object.keys(QuestionsCurr).forEach(function(key, id) { QuestionsCurr[key][2] = 1; });
                sessionStorage.setItem('QuestionsCurr', JSON.stringify(QuestionsCurr));
                //достаём a1 и a2, вдруг далее получится косвенно, по числу имеющихся правильных ответов, вычислить неправильные
                elems = document.getElementsByClassName('b-result-test__answer_count');
                a1 = (elems[0] ? elems[0].innerHTML : 0); //число правильных ответов
                a2 = (elems[1] ? elems[1].innerHTML : 0); //число неправильных ответов
            }
        }
        UpdateQA(a1, a2);
        //sessionStorage.removeItem('QuestionsOrig');
        sessionStorage.setItem('QuestionsCurr', '{}');
    }
}


function RunOnQALoad(call) {
	if (!bQALoaded) {
		setTimeout(function(){RunOnQALoad(call)},300);
		return false;
	}
	call();
}

function Refakt() {
	UpdateQA();
}

//проверка базы на пустые md5
function CheckForEmpty_md5() {
    Object.keys(QuestionsOrig).forEach(function(key, id) {
        var n = QuestionsOrig[key].length;
        for (var i = 1; i < n; i += 3) {
            if (QuestionsOrig[key][i] == '') {console.log(key)};
        }
    });
}


function chunk(arr, chunkSize) {
    var R = [];
    arr = Object.entries(arr);
    for (var i=0,len=arr.length; i<len; i+=chunkSize)
        R.push(arr.slice(i,i+chunkSize));
    return R;
}
function unchunk(arr) {
    var R = [];
    for (var i=0,len=arr.length; i<len; i++)
        R.push(...arr[i]);
    return Object.fromEntries(R);
}
function tst() {
    var d = chunk(QuestionsOrig,200);
    console.log(d);
    //for (var i=0,len=d.length; i<len; i++)
    //    console.log(JSON.stringify(Object.fromEntries(d[i])));
    //console.log(unchunk(chunk(Object.entries(QuestionsOrig),200)));
}
//RunOnQALoad(tst);


LoadQA();
//RunOnQALoad(Refakt);
//RunOnQALoad(CheckForEmpty_md5);

var href_locase = window.location.href.toLowerCase();
if (href_locase.indexOf("obuchenie-i-razvitie-v-obi") == -1) {
	//TestID = parseInt(href_locase.split('test_id=')[1]).toString(36) + '$';
	if ((href_locase.indexOf("attempt") == -1)||(href_locase.indexOf("init_attempt=y") != -1)) {
		RunOnQALoad(FindQuestions);
	}else{
        sessionStorage.removeItem('AutoAnswer'); //отключаем автоклик
		RunOnQALoad(CheckResult);
	}
}


})();