/**
 * Контроллер проверок
 */
var checker = {
    /**
     * Режим отладки
     * @type {boolean}
     * @public
     */
    debug: false,
    /**
     * Состояние проверки:
     * 1  = Проверка в процессе
     * 0  = Проверка завершена (или не начиналась)
     * -1 = Проверка приостановлена и ждет отклика от пользователя
     * @private
     * @type int
     */
    checkInProgress: 0,
    /**
     * Название текущей проверки
     * @public
     * @type {boolean|string}
     */
    currentStep: false,
    /**
     * Пользовательский сертификат
     * @public
     */
    userCert: false,
    /**
     * Дата по серверу
     * @type {string}
     */
    serverDate: "",
    /**
     * Флаг таймера
     * @private
     * @type boolean
     */
    timer: false,
    /**
     * Информация об установленных продуктах КриптоПро
     * @public
     * @type {{provider, plugin}}
     */
    csp: {
        provider: '',
        plugin: ''
    },

    /**
     * Загрузка объекта сразу после загрузки страницы
     * @public
     */
    init: function () {
        this.debug = (window.location.search == "?debug");
        this.serverDate = $("#serverDate").get(0).value;
        this._loadProgressFromCookies();
        view.init();
    },
    /**
     * Вывод отладочной информации
     * @param {*} data
     * @public
     */
    log: function (data) {
        this.debug && window.console && console.log(data);
    },
    /**
     * Полный сброс страницы
     * @public
     */
    reset: function() {
        this.log('reset');
        this.checkInProgress = 0;
        this.currentStep = false;
        this.userCert = false;

        this._setCookie('checkSuccess', 0, 365);

        //View
        view.setStartButton(true);
        view.setHttpsButton(false);
        view.setAllChecks('checkWait');
        view.resetCertList();
        view.debugBox(false);

        errHelper.code = "";
        errHelper.message = "";
        errHelper.catched = {};
        libHelper.subjStr = '';
    },

    /**
     * Запуск следующей проверки.
     * Если последовательность проверок не в процессе, то просто помечает
     * пункт текущей проверки пройденным
     * (сделано для возможности запуска отдельных проверок).
     * @private
     */
    nextStep: function () {
        this.log('nextStep');
        view.setCurrentCheck('checkSuccess');
        if (!this.checkInProgress) return;
        switch (this.currentStep)
        {
            case 'OS': this.checkBrowser(); break;
            case 'Browser': this.checkPlugin(); break;
            case 'Plugin': this.checkRootCert(); break;
            case 'RootCert': this.checkUserCert(); break;
            case 'UserCert': this.checkConnection(); break;
            case 'Connection': this.finishSequence(); break;
        }
    },
    /**
     * Запуск последовательности проверок
     * @public
     */
    runSequence: function () {
        this.log('runSequence');
        this.reset();
        view.setStartButton(false);

        this.checkInProgress = 1;
        this.checkOS();
    },
    /**
     * Остановка последовательности проверок
     * @private
     */
    stopSequence: function () {
        this.log('stopSequence in '+this.currentStep);
        this.log({code: errHelper.code, msg: errHelper.message});
        this.checkInProgress = 0;
        view.setCurrentCheck('checkFailed');

        if (errHelper.code) {
            errHelper.sendReport();
        }

        if (this.debug) {
            var translation  = false, translationText = false;
            if(typeof errHelper.catched.number != "undefined") {
                translationText = errHelper.getErrTranslate(errHelper.catched.number);
                translation = {
                    dec: errHelper.catched.number,
                    text: translationText ? translationText
                        : (typeof errHelper.catched.message != "undefined") ? errHelper.catched.message
                            : "Неопознанная ошибка."
                };
            }
            view.debugBox(translation);
        }
        view.setStartButton(true);
    },
    /**
     * Приостановка последовательности, ожидание отклика от пользователя
     * @private
     */
    pauseSequence: function () {
        this.log('pauseSequence');
        this.checkInProgress = -1;
    },
    /**
     * Возобновление последовательности проверок при получении отклика
     * @private
     */
    resumeSequence: function () {
        this.log('resumeSequence');
        this.checkInProgress = 1;
    },
    /**
     * Окончание проверок, сохранение результата в cookie
     * @private
     */
    finishSequence: function () {
        this.log('finishSequence');
        this.checkInProgress = 0;
        this.currentStep = false;
        this._setCookie('checkSuccess', 1, 365);
        view.setStartButton(true);
        view.setHttpsButton(true);
    },

    /**
     * 1. Проверка операционной системы
     * @public
     */
    checkOS: function () {
        this.currentStep = 'OS';
        view.setCurrentCheck('checking');

        if (!this._isDate()) {
            errHelper.code = 'badDate';
            this.stopSequence(); return;
        }
        if (!this._isWin() && !this._isiOS()) {
            errHelper.code = 'badOS';
            this.stopSequence(); return;
        }

        this.nextStep();
    },
    /**
     * 2. Проверка браузера
     * @public
     */
    checkBrowser: function() {
        this.currentStep = 'Browser';
        view.setCurrentCheck('checking');

        if (!this._cookieTest()) {
            errHelper.code = 'noCookies';
            this.stopSequence(); return;
        }
        if (!this._isIE()) {
            errHelper.code = 'badBrowser';
            this.stopSequence(); return;
        }
        /*if (!this._compatibilityOff()) {
            errHelper.code = 'compatibility';
            this.stopSequence(); return;
        }*/

        this.nextStep();
    },
    /**
     * 3. Проверка наличия плагина
     * @public
     */
    checkPlugin: function() {
        this.currentStep = 'Plugin';
        view.setCurrentCheck('checking');

        var signer = libHelper.getSigner();
        if (!signer){
            errHelper.code = 'newSigner';
            this.stopSequence(); return;
        }

        if (!this._checkPluginVersion()) {
            errHelper.code = 'badPluginVersion';
            this.stopSequence(); return;
        }

        if (!this._checkCSPVersion()) {
            view.showWarning('badCspVersion');
        }

        this.nextStep();
    },
    /**
     * 4. Проверка корневого сертификата
     * @public
     */
    checkRootCert: function() {
        this.currentStep = 'RootCert';
        view.setCurrentCheck('checking');

        var certSha1 = "E22BE24F07B6DB37BF6740DDE61459AFFB3FD5F3";
        var result = libHelper.getCertBySha1(certSha1, true);
        if (!result){
            this.stopSequence(); return;
        }

        this.nextStep();
    },
    /**
     * 5. Проверка сертификата пользователя
     * @public
     */
    checkUserCert: function () {
        this.currentStep = 'UserCert';
        view.setCurrentCheck('checking');

        var cadescomCPSigner, cadescomSignedData,
            capicomStore = libHelper.getMyStore();

        var userCert = false;

        try {
            //Всплывающее окно выбора сертификатов (может не работать на x64)
            userCert = capicomStore.Certificates.Select(
                'Список', 'Выберите сертификат из хранилища "Личные"'
            );
        } catch (err) {
            errHelper.catched = err;
            //Если пользователь не стал выбирать сертификат
            if (err.number == "-2138568446") {
                errHelper.code = 'certsNotSelected';
                this.stopSequence(); return;
            } else {
                view.fillCertList();
                this.pauseSequence(); return;
            }
        }

        //Выбранный сертификат не найден
        if (!userCert.Count) {
            errHelper.code = 'certNotFound';
            this.stopSequence(); return;
        }

        this.userCert = userCert.Item(1); //Выбранный сертификат

        cadescomCPSigner = libHelper.getSigner();
        if (!cadescomCPSigner){
            this.stopSequence(); return;
        }
        cadescomSignedData = libHelper.getSignedData();
        if (!cadescomSignedData){
            this.stopSequence(); return;
        }

        cadescomCPSigner.Certificate = this.userCert;
        cadescomSignedData.ContentEncoding = libHelper.CADESCOM_BASE64_TO_BINARY;
        cadescomSignedData.Content = 'dataToSign';

        try {
            //Закрытый ключ в сертификате обязателен!
            cadescomSignedData.SignCades(cadescomCPSigner, libHelper.CADESCOM_CADES_BES, true);
        } catch (error) {
            errHelper.catched = error;
            errHelper.code = 'signDataError';
            errHelper.message = error;
            this.stopSequence(); return;
        }

        this.nextStep();
    },
    /**
     * Продолжение проверки сертификата пользователя.
     * Вызывается в случае, когда используется html-список сертификатов
     * @public
     */
    checkUserCertContinue: function () {
        var sha1 = document.getElementById('CertListBox').value;
        if (!sha1 || this.checkInProgress != -1) return;
        this.resumeSequence();

        var cadescomCPSigner, cadescomSignedData;

        this.userCert = libHelper.getCertBySha1(sha1, false);
        if (!this.userCert){
            this.stopSequence(); return;
        }
        cadescomCPSigner = libHelper.getSigner();
        if (!cadescomCPSigner){
            this.stopSequence(); return;
        }
        cadescomSignedData = libHelper.getSignedData();
        if (!cadescomSignedData){
            this.stopSequence(); return;
        }

        cadescomCPSigner.Certificate = this.userCert;
        cadescomSignedData.ContentEncoding = libHelper.CADESCOM_BASE64_TO_BINARY;
        cadescomSignedData.Content = 'dataToSign';

        try {
            //Закрытый ключ в сертификате обязателен!
            cadescomSignedData.SignCades(cadescomCPSigner, libHelper.CADESCOM_CADES_BES, true);
        } catch (error) {
            errHelper.catched = error;
            errHelper.code = 'signDataError';
            errHelper.message = error;
            this.stopSequence(); return;
        }
        view.resetCertList(); //Чистка списка от прошлой проверки
        this.nextStep();
    },
    /**
     * 6. Проверка возможности соединения по https
     * @public
     */
    checkConnection: function () {
        this.currentStep = 'Connection';
        view.setCurrentCheck('checking');

        this._setTimer(30); //Ограничение времени на прохождение проверки

        var me = this;
        var url = 'https://' + window.location.hostname + '/user/check/cert';
        $.ajax({
            url: url,
            jsonp: "callback",
            dataType: "jsonp",
            data: {
                t: (new Date()).getTime()
            },
            success: function(response) {
                me.checkConnectionSuccess(response);
            },
            error: function (err) {
                me.checkConnectionError(err);
            }
        });

        this.pauseSequence();
    },
    /**
     * Соединение удачно установлено
     * @private
     * @param {{success, fio:{last, first}, inn, ogrn, ssl_number}} resp
     */
    checkConnectionSuccess: function (resp) {
        this.log('checkConnectionSuccess');
        this.timer = false;

        if (resp) {
            if (resp.success == 'true') {
                //Проверка на ФИО
                if (!resp.fio.last || !resp.fio.first) {
                    errHelper.code = 'badFio';
                    errHelper.message = resp;
                    this.stopSequence(); return;
                }
                //Проверка ИНН ОГРН
                if (resp.inn == 'undefined' || resp.ogrn == 'undefined') {
                    errHelper.code = 'badInnOgrn';
                    errHelper.message = resp;
                    this.stopSequence(); return;
                }
                //Проверка SSL
                if (resp.ssl_number == 'undefined') {
                    errHelper.code = 'badSsl';
                    errHelper.message = resp;
                    this.stopSequence(); return;
                }
            } else {
                errHelper.code = 'httpsFalseResp';
                errHelper.message = resp;
                this.stopSequence(); return;
            }
        } else {
            errHelper.code = 'httpsNoResp';
            errHelper.message = resp;
            this.stopSequence(); return;
        }

        this.nextStep();
    },
    /**
     * Ошибка создания https-подключения либо таймаут
     * @private
     * @param {{}|boolean} err
     */
    checkConnectionError: function (err) {
        this.log('checkConnectionError: timer = '+this.timer);
        if (!this.timer) return;

        errHelper.code = 'getJsonpError';
        errHelper.message = err;
        this.stopSequence();
    },
//------------------------------------------------------------------------------
    /**
     * Загрузка прошлого результата из cookie
     * @private
     */
    _loadProgressFromCookies: function () {
        this.log('loadProgressFromCookies');
        var checkSuccess = this._getCookie('checkSuccess');
        if (checkSuccess == '1'){
            view.setAllChecks('checkSuccess');
            view.setHttpsButton(true);
        }
    },
    /**
     * Проверка работоспособности cookie
     * @returns {boolean}
     * @private
     */
    _cookieTest: function (){
        this._setCookie('cookieTest', '1', 365);
        var cookieTest = this._getCookie('cookieTest');
        return cookieTest == '1';
    },
    /**
     * Set cookie
     * @param c_name
     * @param value
     * @param exdays
     * @private
     */
    _setCookie: function (c_name, value, exdays) {
        var exdate = new Date();
        exdate.setDate(exdate.getDate() + exdays);
        var c_value = escape(value) + ((exdays == null) ? '' : '; expires=' + exdate.toUTCString());
        document.cookie = c_name + '=' + c_value;
    },
    /**
     * Get cookie
     * @param c_name
     * @returns {null|string}
     * @private
     */
    _getCookie: function(c_name) {
        var i, x, y, ARRcookies = document.cookie.split(';');
        for (i = 0; i < ARRcookies.length; i++) {
            x = ARRcookies[i].substr(0, ARRcookies[i].indexOf('='));
            y = ARRcookies[i].substr(ARRcookies[i].indexOf('=') + 1);
            x = x.replace(/^\s+|\s+$/g, '');
            if (x == c_name) {
                return unescape(y);
            }
        }
        return null;
    },

    /**
     * Событие - клик по сертификату в списке
     * @param value
     */
    selectCertClick: function (value) {
        view.enableCertButtons(value);
    },
    /**
     * Отображает свойства выбранного сертификата
     * @public
     */
    getCertInfo: function () {
        var sha1 = document.getElementById('CertListBox').value;
        var cert = libHelper.getCertBySha1(sha1, false);

        try{
            cert.Display(); //Нативное окно
        } catch (err){
            errHelper.catched = err;
            libHelper.subjStr = cert.SubjectName.toString();
            view.renderCertInfo(libHelper.getFullInfo()); //html-вывод
        }
        libHelper.subjStr = cert.SubjectName.toString();
    },

    /**
     * Проверка на версию КриптоПро Plugin для браузера
     * @return {boolean}
     * @private
     */
    _checkPluginVersion: function () {
        /**
         * Содержит информацию о версиях КриптоПро CSP и КриптоПро плагина
         * @type {{BuildVersion, MajorVersion, MinorVersion, Version, providerVersion()}}
         */
        var oAbout;
        try {
            oAbout = libHelper.objCreator("CAdESCOM.About");
            this.csp.provider = oAbout.providerVersion();
            this.csp.plugin = oAbout.Version;
            view.setVersions();
        } catch(ex) {
            errHelper.catched = ex;
            return true;
        }

        var result = true;
        try{
            var pluginVersion = oAbout.MajorVersion;
            if (pluginVersion < 2){ //Good version "2.0.2011"+
                result = false;
            }
        } catch (e) {
            errHelper.catched = e;
        }
        return result;
    },
    /**
     * Необязательная проверка на версию КриптоПро CSP
     * @return {boolean}
     * @private
     */
    _checkCSPVersion: function () {
        /**
         * Содержит информацию о версиях КриптоПро CSP и КриптоПро плагина
         * @type {{BuildVersion, MajorVersion, MinorVersion, Version, providerVersion()}}
         */
        var oAbout;
        try {
            oAbout = libHelper.objCreator("CAdESCOM.About");
            this.csp.provider = oAbout.providerVersion();
            this.csp.plugin = oAbout.Version;
            view.setVersions();
        } catch(ex) {
            errHelper.catched = ex;
            return true;
        }

        var result = true;
        try{
            var cspVersion = oAbout.providerVersion(),
                cspMajor = Number(cspVersion.split('.')[0]),
                cspMinor = Number(cspVersion.split('.')[1]),
                cspBuild = Number(cspVersion.split('.')[2]);

            if (cspMajor == 3){
                if (cspMinor < 6 || cspBuild < 7777){
                    result = false;
                }
            } else if (cspMajor < 3) {
                result = false;
            }
        } catch (e) {
            errHelper.catched = e;
        }
        return result;
    },

    /**
     * Проверка для Windows
     * @returns {boolean}
     * @private
     */
    _isWin: function () {
        var resultWin = /Windows NT \d+\.\d+/.exec(navigator.userAgent);
        if (resultWin != '' && resultWin != null) {
            resultWin = /\d+\.\d+/.exec(resultWin) + '';
            resultWin = resultWin.replace('.', '') - 0;
            return resultWin >= 51;
        }
        return false;
    },
    /**
     * Проверка для Mac
     * @returns {boolean}
     * @private
     */
    _isiOS: function () {
        var resultOSX1 = /Mac OS X \d+\.\d+/.exec(navigator.userAgent),
            resultOSX2 = /Mac OS X \d+_\d+/.exec(navigator.userAgent);

        if (resultOSX1 || resultOSX2) {
            var resultOSXVer = '';
            if (resultOSX1) {
                resultOSX1 = /\d+\.\d+/.exec(resultOSX1) + '';
                resultOSXVer = resultOSX1.replace('.', '');
            } else if (resultOSX2) {
                resultOSX2 = /\d+_\d+/.exec(resultOSX2) + '';
                resultOSXVer = resultOSX2.replace('_', '');
            }
            if (resultOSXVer >= 109) {
                return true;
            }
        }
        return false;
    },
    /**
     * Проверка времени на компьютере
     * @returns {boolean}
     * @private
     */
    _isDate: function () {
        var today, date, month, year, yesterday, tomorrow;
        var this_day, prev_day, next_day;

        date = new Date();
        year = date.getFullYear();
        this_day = date.getDate();
        month = Number(date.getMonth()) + 1;
        if (month < 10) {
            month = '0' + month;
        }
        if (this_day < 10) {
            this_day = '0' + this_day;
        }
        today = this_day + '.' + month + '.' + year; //Текущий день

        date.setDate(date.getDate() + 1); //Завтрашний день
        next_day = date.getDate();
        month = Number(date.getMonth()) + 1;
        if (month < 10) {
            month = '0' + month;
        }
        if (next_day < 10) {
            next_day = '0' + next_day;
        }
        tomorrow = next_day + '.' + month + '.' + year;

        date.setDate(date.getDate() - 2); //Вчерашний день
        prev_day = date.getDate();
        month = Number(date.getMonth()) + 1;
        if (month < 10) {
            month = '0' + month;
        }
        if (prev_day < 10) {
            prev_day = '0' + prev_day;
        }
        yesterday = prev_day + '.' + month + '.' + year;

        if (this.serverDate != today &&
            this.serverDate != yesterday &&
            this.serverDate != tomorrow)
        {
            errHelper.message = today;
            return false;
        } else {
            return true;
        }
    },
    /**
     * Проверка на Internet Explorer
     * @returns {boolean|Array|{index: number, input: string}}
     * @private
     */
    _isIE: function () {
        var appName = ("Microsoft Internet Explorer" == navigator.appName),
            uaDetect = navigator.userAgent.match(/Trident\/./i),
            htmlDetect = document.getElementById("IeDetector");
        return (!!appName || !!uaDetect || !!htmlDetect);
    },
    /**
     * Режим совместимости должен быть выключен
     * @returns {boolean}
     * @private
     */
    _compatibilityOff: function () {
        return !(document.all && document.querySelector);
    },
    /**
     * Ограничитель времени на прохождение проверки https-соединения
     * @param secs
     * @private
     */
    _setTimer: function(secs){
        var me = this;
        me.timer = true;
        window.setTimeout(function(){
            if (me.timer){
                me.timer = false;
                me.checkConnectionError(false);
            }
        }, secs*1000);
    }
};

/**
 * Работа со страницей
 */
var view = {

    init: function () {
        var user = detect.parse(navigator.userAgent);
        user.os.name == "Windows XP" && this.unlockSpecials("OnlyForXP");
        user.browser.family == "IE" && this.unlockSpecials("OnlyForIe");
        user.browser.family != "IE" && this.unlockSpecials("OnlyForNotIe");

        //Динамические надписи
        $('#os_name').html(user.os.name);
        this.setStartButton(true);
    },

    /**
     * Показывает блок отладки когда есть что показывать, скрывает его когда показывать нечего.
     * @param {{text, hex, dec}|boolean} data
     * @return {boolean}
     */
    debugBox: function (data) {
        var box = $("#errorLog");

        if(!data) {
            box.addClass('hidden').html("");
            return false;
        }

        var htmlString = "";
        htmlString += "Код ошибки: " + data.dec + "\n";
        htmlString += "Текст ошибки: " + data.text;
        box.removeClass('hidden').html(htmlString);
        return true;
    },

    /**
     * Убирает принудительное сокрытие отдельных элементов при спец условиях
     * @param className
     */
    unlockSpecials: function (className) {
        $("."+className).removeClass(className);
    },

    /**
     * Прописывание текущих версий ПО в спец элементы
     */
    setVersions: function () {
        $('#csp_version').html(checker.csp.provider);
        $('#plugin_version').html(checker.csp.plugin);
    },

    /**
     * Установить внешний вид пункта проверки.
     * @public
     * @param {string|boolean} newClass Состояние проверки:
     * 'run' = в процессе;
     * 'done' = пойдена;
     * 'error' = не пройдена;
     ** false = не проходилась.
     */
    setCurrentCheck: function (newClass) {
        checker.log('setCurrentCheck: '+checker.currentStep+' => '+newClass);

        //Изменение строки с текстом проверки
        $("#check"+checker.currentStep)
            .removeClass()
            .addClass('check')
            .addClass(newClass);
        if (newClass == 'checkFailed') {
            //Отобразить блок общей ошибки
            $(".casual.check"+checker.currentStep).removeClass('hidden');

            //Отобразить блок частной ошибки
            $('.'+errHelper.code+'.check'+checker.currentStep).removeClass('hidden');
        }
    },

    /**
     * Установить значения для всех пунктов проверок
     * @public
     */
    setAllChecks: function (newClass) {
        checker.log('setAllChecks: '+newClass);
        $(".check").each(function(index, item){
            $(item)
                .removeClass()
                .addClass('check')
                .addClass(newClass);
        });

        if (newClass != 'checkFailed') {
            $(".errorBlock").each(function (index, item) {
                $(item).addClass('hidden');
            });
        }
    },

    /**
     * Показывает сообщение о некритичной ошибке
     */
    showWarning: function (cls) {
        checker.log('showWarning: '+cls);
        $(".warning")
            .filter('.check'+checker.currentStep)
            .filter('.'+cls)
            .removeClass('hidden');
    },

    /**
     * Скрыть/показать кнопку перехода на основной сайт
     * @public
     * @param vision
     */
    setHttpsButton: function(vision) {
        var btn = $("#submit");
        if (vision){
            btn.removeClass('hidden');
        } else {
            btn.addClass('hidden');
        }
    },

    /**
     * Скрыть/показать кнопку запуска проверок
     * @public
     * @param {boolean} vision
     */
    setStartButton: function (vision) {
        var btn = $('#startTests');
        if (vision) {
            btn.removeClass('hidden');
        } else {
            btn.addClass('hidden');
        }
    },

    /**
     * Enable/Disable кнопок под списком сертификатов
     * @public
     * @param {boolean} enable
     */
    enableCertButtons: function (enable) {
        var selectBtn = document.getElementById('selectCertBySha1Btn');
        var showBtn = document.getElementById('showCertInfoBtn');
        if (enable){
            selectBtn.removeAttribute('disabled');
            showBtn.removeAttribute('disabled');
        } else {
            selectBtn.setAttribute('disabled', 'disabled');
            showBtn.setAttribute('disabled', 'disabled');
        }
    },

    /**
     * Заполнение списка сертификатов
     * @public
     * @returns {boolean}
     */
    fillCertList: function () {
        checker.log('fillCertList');

        $("#CertListDiv").removeClass('hidden'); //Отображаем сам слой со списком и кнопками
        $("#CertListBox").removeClass('hidden'); //Отображаем список сертификатов

        var myStore = libHelper.getMyStore();
        var certCounter = myStore.Certificates.Count;
        var listBox = $("#CertListBox").get(0); //select

        //Шапка списка
        var headOption = document.createElement("OPTION");
        headOption.disabled = true;
        headOption.text = certCounter
            ? "--- Выберите КСКПЭП для создания тестовой электронной подписи ---"
            : "--- Личные сертификаты не найдены ---";
        listBox.add(headOption);

        for(var i = 1; i < certCounter+1; i++){
            /**
             * Сертификат
             * @type {{SubjectName,Thumbprint}}
             */
            var cert = myStore.Certificates.Item(i);
            libHelper.subjStr = cert.SubjectName;
            var info = libHelper.getFullInfo();

            var option = document.createElement("OPTION");
            option.style.minWidth = "500px";
            option.value = option.title = cert.Thumbprint; //sha1
            option.text = info.CN; //Common name
            listBox.add(option);

            libHelper.subjStr = '';
        }
        listBox.size = certCounter+1; //Размер списка
    },

    /**
     * Вывод информации из сертификата на страницу
     * @public
     * @param {{CN,fio,title,email,inn,ogrn}} info Информация из сертификата
     */
    renderCertInfo: function (info) {
        if (!info) return;

        /**
         * @type {string}
         */
        var resultInfo = "Субъект:<br />"
            +"ФИО: " + info.fio + "<br />\n"
            +"Должность: " + info.title + "<br />\n"
            +"Email: " + info.email + "<br />\n"
            +"ИНН: " + info.inn + "<br />\n"
            +"ОГРН: " + info.ogrn + "<br />\n";

        $("#CertInfoBox").removeClass('hidden').html(resultInfo);
    },

    /**
     * Сброс списка сертификатов, скрываем все поля для выбора сертификата
     * @private
     */
    resetCertList: function () {
        $("#CertInfoBox").addClass('hidden'); //Скрываем информацию о сертификате
        $("#CertListDiv").addClass('hidden'); //Отображаем сам слой со списком и кнопками
        var obj = $("#CertListBox").get(0);
        if (obj == null || obj.options == null) return;
        while (obj.options.length > 0) {
            obj.remove(0);
        }
    }
};

/**
 * Работа с ошибками
 */
var errHelper = {

    /**
     * Код ошибки
     * @public
     * @type {string}
     */
    code: '',
    /**
     * Данные об ошибке
     * @public
     * @type {*}
     */
    message: '',
    /**
     * Ответ от скрипта, сохраняющего логи ошибок
     * @public
     * @type {*}
     */
    reportResponse: false,

    /**
     * Справочник известных ошибок при работе с сертификатами и подписями
     * @private
     * @type {[]}
     */
    libErrors: [
        {
            text: 'Набор ключей не существует.',
            hex: '0x80090016',
            dec: '-2146893802'
        },
        {
            text: 'Параметр задан неверно.',
            hex: '0x80070057',
            dec: '-2147024809'
        },
        {
            text: 'Данные настройки для этого продукта повреждены. Обратитесь в службу поддержки организации',
            hex: '0x8007064A',
            dec: '-2147023286'
        },
        {
            text: 'Не указан адрес службы OCSP.',
            hex: '0xC2110121',
            dec: '-1039072991'
        },
        {
            text: 'Пустое значение запрашиваемого свойства.',
            hex: '0x80094004',
            dec: '-2146877436'
        },
        {
            text: 'При отправке запроса произошла ошибка HTTP.',
            hex: '0xC2100100',
            dec: '-1039138560'
        },
        {
            text: 'Не удается найти сертификат и закрытый ключ для расшифровки.',
            hex: '0x8009200B',
            dec: '-2146885621'
        },
        {
            text: 'Встречено неверно значение тега ASN.1',
            hex: '0x8009310B',
            dec: '-2146881269'
        },
        {
            text: 'Класс не зарегистрирован.',
            hex: '0x80040154',
            dec: '-2147221164'
        },
        {
            text: 'Невозможно создание объекта сервером программирования объектов.',
            hex: '0x800A01AD',
            dec: '-2146827859'
        },
        {
            text: 'Ключ не существует.',
            hex: '0x8009000D',
            dec: '-2146893811'
        },
        {
            text: 'Действие было отменено пользователем',
            hex: '0x8010006E',
            dec: '-2146434962'
        },
        {
            text: 'Произошла внутренняя ошибка в цепочке сертификатов.',
            hex: '0x800B010A',
            dec: '-2146762486'
        },
        {
            text: 'Ошибка исполнения функции.',
            hex: '0x8007065B',
            dec: '-2147023269'
        },
        {
            text: 'The requested operation has been cancelled by the user.',
            hex: '0x80880902',
            dec: '-2138568446'
        },
        {
            text: 'Внутренняя ошибка.',
            hex: '0x80090020',
            dec: '-2146893792'
        },
        {
            text: 'Операция была отменена пользователем.',
            hex: '0x800704C7',
            dec: '-2147023673'
        },
        {
            text: 'Набор ключей не определен.',
            hex: '0x80090019',
            dec: '-2146893799'
        },
        {
            text: 'Неправильный зарегистрированный набор ключей.',
            hex: '0x8009001A',
            dec: '-2146893798'
        },
        {
            text: 'Неправильная подпись',
            hex: '0x80090006',
            dec: '-2146893818'
        },
        {
            text: 'Ошибка при загрузке DDL',
            hex: '0x800A0030',
            dec: '-2146828240'
        },
        {
            text: 'Сервер RPC недоступен',
            hex: '0x800706BA',
            dec: '-2147023174'
        },
        {
            text: 'Библиотека поставщика проинициализирована неправильно.',
            hex: '0x8009001D',
            dec: '-2146893795'
        },
        {
            text: 'Не найден автор оригинальной подписи.',
            hex: '0x8009100E',
            dec: '-2146889714'
        },
        {
            text: 'Объект не поддерживает это свойство или метод',
            hex: '0x800A01B6',
            dec: '-2146827850'
        },
        {
            text: 'Отказано в доступе',
            hex: '0x80090010',
            dec: '-2146893808'
        },
        {
            text: 'Нет доступа к карте. Введен неправильный PIN-код.',
            hex: '0x8010006B',
            dec: '-2146434965'
        },
        {
            text: 'Нет доступа к карте. Число попыток ввести правильный PIN-код исчерпано.',
            hex: '0x8010006C',
            dec: '-2146434964'
        },
        {
            text: 'Смарт-карта извлечена, дальнейший обмен информацией с ней невозможен.',
            hex: '0x80100069',
            dec: '-2146434967'
        },
        {
            text: 'Operatsiya otmenena pol\'zovatelem. (0x80880902)',
            hex: '0x80004005',
            dec: '-2147467259'
        },
        {
            text: 'Операция успешно завершена',
            hex: '0x80070000',
            dec: '-2147024896'
        },
        {
            text: 'Указан неправильный алгоритм',
            hex: '080090008',
            dec: '-2146893816'
        },
        {
            text: 'Для выполнения операции права недостаточны.',
            hex: '0x80072098',
            dec: '-2147016552'
        },
        {
            text: 'Указан неправельный тип поставщика.',
            hex: '080090014',
            dec: '-2146893804'
        },
        {
            text: 'Тип поставщика не соответствует зарегистрированному значению.',
            hex: '0x8009001B',
            dec: '-2146893797'
        },
        {
            text: 'Неправильный параметр набора ключей',
            hex: '0x8009001F',
            dec: '-2146893793'
        }
    ],

    /**
     * Оригинал объекта полученного при работе try..catch
     * @type {{description, message, name, number}}
     */
    catched: {},

    /**
     * Возвращает описание ошибки в читаемой кодировке
     * @param errCode
     * @returns {{text, hex, dec}|boolean}
     */
    getErrTranslate: function (errCode) {
        var result = false;
        this.libErrors.forEach(function (item) {
            if(errCode.toString() == item.dec.toString()) {
                result = item.text;
            }
        });
        return result;
    },

    /**
     * Парсер объектов
     * @private
     * @param {object} obj
     */
    _objParser: function (obj) {
        if (typeof obj == 'object'){
            for(var key in obj){
                if (obj.hasOwnProperty(key)){
                    if(typeof obj.key == "function") {
                        delete obj.key;
                    } else if (typeof obj[key] == 'object') {
                        obj[key] = this._objParser(obj[key]);
                    } else {
                        obj[key] = encodeURIComponent(obj[key]);
                    }
                }
            }
        }
        return obj;
    },

    /**
     * Сохранение лога ошибки для её анализа
     * @private
     */
    sendReport: function () {
        checker.log("sendReport");
        if (window.location.hostname != "lkul.nalog.ru") {
            checker.log("Sending canceled");
            return;
        }
        var me = this;

        $.ajax({
            type: "POST",
            url: "logger.php",
            data: {
                userAgent: navigator.userAgent,
                step: checker.currentStep,
                code: me.code,
                message: this._objParser(me.message),
                csp: this._objParser(checker.csp)
            },
            complete: function (resp)
            {
                me.reportResponse = resp;
                checker.log("Reporter response:");
                checker.log(me.reportResponse);
            }
        });
    }
};

/**
 * Работа с хранилищами и сертификатами
 */
var libHelper = {
    /**
     * Returns certificates matching a specified SHA1 hash.
     * CAPICOM_CERTIFICATE_FIND_TYPE enumeration.
     * @public
     * @const
     * @type {int}
     */
    CAPICOM_CERTIFICATE_FIND_SHA1_HASH: 0,
    /**
     * Root store. This store is used to store the root CA and self-signed,
     * trusted certificates.
     * @public
     * @const
     * @type {string}
     */
    CAPICOM_ROOT_STORE: 'Root',
    /**
     * Root store. This store is used to store the root CA and
     * self-signed, trusted certificates.
     * @public
     * @const
     * @type {string}
     */
    CAPICOM_MY_STORE: 'My',
    /**
     * The store is a current user store. A current user store may be a
     * read/write store. If it is, changes in the contents of the store are
     * persisted.
     * @public
     * @const
     * @type {int}
     */
    CAPICOM_CURRENT_USER_STORE: 2,
    /**
     * Open the store in read/write mode if the user has read/write
     * permissions; otherwise, open the store in read-only mode.
     * @public
     * @const
     * @type {int}
     */
    CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED: 2,
    /**
     * Указание для CAdESCOM о кодировке входных данных
     * @public
     * @const
     * @type {int}
     */
    CADESCOM_BASE64_TO_BINARY: 1,
    /**
     * Тип подписи: Базовая ЭП, т.е. совместима с подписью из CAPICOM
     * @public
     * @const
     * @type {int}
     */
    CADESCOM_CADES_BES: 1,
    /**
     * Строка поля "Субъект" из сертификата
     * @type {string}
     * @public
     */
    subjStr: "",

    /**
     * Создание объектов
     * @public
     * @param {string} name
     * @returns {*}
     */
    objCreator: function (name) {
        if (document.getElementById("IeDetector")) {
            try {
                return new ActiveXObject(name);
            } catch (errActX) {
                errHelper.catched = errActX;
                errHelper.message = errActX;
                return false;
            }
        } else {
            try {
                var cadesObject = document.getElementById('cadesplugin');
                return cadesObject.CreateObject(name);
            } catch (errCadesObj) {
                errHelper.catched = errCadesObj;
                errHelper.message = errCadesObj;
                return false;
            }
        }
    },
    /**
     * Создает и открывает хранилище личное сертификатов
     * @public
     * @returns {{Certificates:{Count:number,Item:(),Select:(),Find:(),Close:()}}|boolean}
     */
    getMyStore: function () {
        checker.log('getMyStore');
        /**
         * Хранилище сертификатов
         * @type {{Open, Certificates:{Count:number,Item:(),Select:(),Find:()}}}
         */
        var capicomStore;
        try {
            capicomStore = this.objCreator('CAPICOM.Store');
        } catch (err) {
            errHelper.catched = err;
            errHelper.code = 'newStore';
            errHelper.message = err;
            return false;
        }

        try {
            capicomStore.Open(
                this.CAPICOM_CURRENT_USER_STORE,
                this.CAPICOM_MY_STORE,
                this.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED
            );
        } catch (err) {
            errHelper.catched = err;
            errHelper.code = 'openMyStore';
            errHelper.message = err;
            return false;
        }
        return capicomStore;
    },
    /**
     * Создает и открывает хранилище доверенных сертификатов
     * @public
     * @returns {{Certificates:{Count:number,Item:(),Select:(),Find:(),Close:()}}|boolean}
     */
    getRootStore: function () {
        checker.log('getRootStore');
        /**
         * Хранилище сертификатов
         * @type {{Open, Certificates:{Count:number,Item:(),Select:(),Find:()}}}
         */
        var capicomStore;
        try {
            capicomStore = this.objCreator('CAPICOM.Store');
        } catch (err) {
            errHelper.catched = err;
            errHelper.code = 'newStore';
            errHelper.message = err;
            return false;
        }

        try {
            capicomStore.Open(
                this.CAPICOM_CURRENT_USER_STORE,
                this.CAPICOM_ROOT_STORE,
                this.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED
            );
        } catch (err) {
            errHelper.catched = err;
            errHelper.code = 'openRootStore';
            errHelper.message = err;
            return false;
        }
        return capicomStore;
    },
    /**
     * @public
     * @returns {{Certificate, ContentEncoding, Content}|boolean}
     */
    getSigner: function () {
        checker.log('getSigner');
        /**
         * Параметры подписи
         * @type {{Certificate, ContentEncoding, Content}}
         */
        var cadescomCPSigner;
        try {
            cadescomCPSigner = this.objCreator('CAdESCOM.CPSigner');
        } catch (error) {
            errHelper.code = 'newSigner';
            errHelper.catched = error;
            errHelper.message = error;
            return false;
        }
        return cadescomCPSigner;
    },
    /**
     * @public
     * @returns {{SignCades:()}|boolean}
     */
    getSignedData: function() {
        checker.log('getSignedData');
        /**
         * Объект для создания подписи
         * @type {{SignCades:()}}
         */
        var cadescomSignedData;
        try {
            cadescomSignedData = this.objCreator('CAdESCOM.CadesSignedData');
        } catch (error) {
            errHelper.code = 'newSignedData';
            errHelper.catched = error;
            errHelper.message = error;
            return false;
        }
        return cadescomSignedData;
    },
    /**
     * Ищет сертификат по sha1
     * @param {string} sha1 Отпечаток
     * @param {boolean} inRoot Хранилище доверенных или личных сертификатов
     * @return {boolean|{SubjectName, Display}}
     */
    getCertBySha1: function(sha1, inRoot) {
        checker.log('getCertBySha1: '+sha1+' in '+(inRoot ? 'Root' : 'My'));
        /**
         * Хранилище сертификатов
         * @type {boolean|{Count,Item}}
         */
        var found = false;
        var capicomStore = inRoot ? this.getRootStore() : this.getMyStore();
        if (!capicomStore) {
            return false;
        }

        try {
            /**
             * Найденные сертификаты
             * @type {{Count: number, Item:()}}
             */
            found = capicomStore.Certificates.Find(
                this.CAPICOM_CERTIFICATE_FIND_SHA1_HASH, sha1
            );
            if (!found.Count){
                errHelper.code = 'noRoot';
                return false;
            }
            return found.Item(1); //Выбранный сертификат
        } catch (err) {
            errHelper.catched = err;
            errHelper.code = 'certSearchError';
            errHelper.message = err;
            return false;
        }
    },
    /**
     * Возвращает объект со значениями поля Субъект
     * @return {{CN, fio, title, email, inn, ogrn}}
     */
    getFullInfo: function () {
        /**
         * Объект SubjectInfo
         * @type {{CN,fio,title,email,inn,ogrn}}
         */
        var SubjectInfo = {};
        var failText = '<span class="red bold">Не удалось прочитать</span>';
        SubjectInfo.CN = this._getField('CN') ? this._getField('CN') : failText;
        SubjectInfo.fio = (this._getField('G') && this._getField('SN'))
            ? this._getField('G') + " " + this._getField('SN') : failText;
        SubjectInfo.title = this._getField(' T') ? this._getField(' T') : failText;
        SubjectInfo.email = this._getField('E') ? this._getField('E') : failText;
        SubjectInfo.inn = this._getField('ИНН') ? this._getField('ИНН') : failText;
        SubjectInfo.ogrn = this._getField('ОГРН') ? this._getField('ОГРН') : failText;
        return SubjectInfo;
    },
    /**
     * Возвращает значение поля из строки this.subjStr
     * @private
     * @param {string} fieldName
     * @return {string|boolean}
     */
    _getField: function (fieldName) {
        if(!this.subjStr) return false;
        var field = fieldName+"=";
        var FieldPos = this.subjStr.indexOf(field);
        if (FieldPos >= 0){
            var FieldValue = this.subjStr.substring(FieldPos+field.length, this.subjStr.length);
            var CommaPos = FieldValue.indexOf(",");
            if (CommaPos >= 0){
                return FieldValue.substring(0,CommaPos);
            }
            return FieldValue;
        }
        return false;
    }
};

$(document).ready(function () {
    checker.init();
    checker.log('Ready');

    $('#startTests').click(function(){
        checker.runSequence();
    });
});