var lookup = [];
var revLookup = [];
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;

var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
for (var i = 0, len = code.length; i < len; ++i) {
    lookup[i] = code[i];
    revLookup[code.charCodeAt(i)] = i;
}

// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup['-'.charCodeAt(0)] = 62;
revLookup['_'.charCodeAt(0)] = 63;

function getLens(b64) {
    var len = b64.length;

    if (len % 4 > 0) {
        throw new Error('Invalid string. Length must be a multiple of 4');
    }

    // Trim off extra bytes after placeholder bytes are found
    // See: https://github.com/beatgammit/base64-js/issues/42
    var validLen = b64.indexOf('=');
    if (validLen === -1)
        validLen = len;

    var placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);

    return [validLen, placeHoldersLen];
}

// base64 is 4/3 + up to two characters of the original data
function byteLength(b64) {
    var lens = getLens(b64);
    var validLen = lens[0];
    var placeHoldersLen = lens[1];
    return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen;
}

function _byteLength(b64, validLen, placeHoldersLen) {
    return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen;
}

function toByteArray(b64) {
    var tmp;
    var lens = getLens(b64);
    var validLen = lens[0];
    var placeHoldersLen = lens[1];

    var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen));

    var curByte = 0;

    // if there are placeholders, only get up to the last complete 4 chars
    var len = placeHoldersLen > 0 ? validLen - 4 : validLen;

    var i;
    for (i = 0; i < len; i += 4) {
        tmp =
            (revLookup[b64.charCodeAt(i)] << 18) |
            (revLookup[b64.charCodeAt(i + 1)] << 12) |
            (revLookup[b64.charCodeAt(i + 2)] << 6) |
            revLookup[b64.charCodeAt(i + 3)];
        arr[curByte++] = (tmp >> 16) & 0xFF;
        arr[curByte++] = (tmp >> 8) & 0xFF;
        arr[curByte++] = tmp & 0xFF;
    }

    if (placeHoldersLen === 2) {
        tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4);
        arr[curByte++] = tmp & 0xFF;
    }

    if (placeHoldersLen === 1) {
        tmp =
            (revLookup[b64.charCodeAt(i)] << 10) |
            (revLookup[b64.charCodeAt(i + 1)] << 4) |
            (revLookup[b64.charCodeAt(i + 2)] >> 2);
        arr[curByte++] = (tmp >> 8) & 0xFF;
        arr[curByte++] = tmp & 0xFF;
    }

    return arr;
}

function tripletToBase64(num) {
    return lookup[num >> 18 & 0x3F] +
        lookup[num >> 12 & 0x3F] +
        lookup[num >> 6 & 0x3F] +
        lookup[num & 0x3F];
}

function encodeChunk(uint8, start, end) {
    var tmp;
    var output = [];
    for (var i = start; i < end; i += 3) {
        tmp =
            ((uint8[i] << 16) & 0xFF0000) +
            ((uint8[i + 1] << 8) & 0xFF00) +
            (uint8[i + 2] & 0xFF);
        output.push(tripletToBase64(tmp));
    }
    return output.join('');
}

function fromByteArray(uint8) {

    var tmp;
    var len = uint8.length;
    var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    var parts = [];
    var maxChunkLength = 16383; // must be multiple of 3

    // go through the array every three bytes, we'll deal with trailing stuff later
    for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
        parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)));
    }

    // pad the end with zeros, but make sure to not forget the extra bytes
    if (extraBytes === 1) {
        tmp = uint8[len - 1];
        parts.push(
            lookup[tmp >> 2] +
            lookup[(tmp << 4) & 0x3F] +
            '=='
        );
    } else if (extraBytes === 2) {
        tmp = (uint8[len - 2] << 8) + uint8[len - 1];
        parts.push(
            lookup[tmp >> 10] +
            lookup[(tmp >> 4) & 0x3F] +
            lookup[(tmp << 2) & 0x3F] +
            '='
        );
    }

    return parts.join('');
}

function decode(code) {
    code = code.replace(/-/g, '+').replace(/_/g, '/') + '===='.substring(0, (4 - (code.length % 4)) % 4);
    return toByteArray(code);
}

function encode(code) {
    code = fromByteArray(ensureUint8Array(code));
    return code.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

function ensureUint8Array(arg) {
    if (arg instanceof ArrayBuffer) {
        return new Uint8Array(arg);
    } else {
        return arg;
    }
}

function decodeExcludeCredentials(cred) {
    cred.id = decode(cred.id);
    delete cred.transports;
}

function registerAutheticator(response) {
    var options = response.responseXML.documentElement.getAttribute('answer');
	var valid = !!options;
	
	if(!valid){
		jslog("Error while generating registration request.");
		close();
		return false;
	}
	
    options = JSON.parse(options);
    options.user.id = decode(options.user.id);
    options.challenge = decode(options.challenge.value);

    delete options.user.icon;
    delete options.rp.icon;

    if (options.excludeCredentials) {
        options.excludeCredentials.forEach(decodeExcludeCredentials);
    }

    navigator.credentials.create({
        publicKey: options
    }).then(function(response) {
        var res = {};
        res.id = response.id;
        res.rawId = encode(response.rawId);
        res.type = response.type;
        res.response = {};
        res.response.clientDataJSON = encode(response.response.clientDataJSON);
        res.response.attestationObject = encode(response.response.attestationObject);
        res.clientExtensionResults = response.getClientExtensionResults();

        if (response.response.getTransports)
            res.response.transports = JSON.stringify(response.response.getTransports());

        validateRegistrationResponse(res);
    }).catch(function(error) {
        alert(error);
    });
}

function doesClientSupportWebAuthn() {
    if (!window.PublicKeyCredential) {
        return false;
    }
    return true;
}

function authenticate(response) {
    var options = response.responseXML.documentElement.getAttribute('answer');
    options = JSON.parse(options);
    options.challenge = decode(options.challenge.value);
    options.allowCredentials.forEach(function(item, index) {
        item.id = decode(item.id);
        delete item.transports;
    });

    delete options.extensions;

    navigator.credentials.get({
        publicKey: options
    }).then(function(response) {
        var res = {};
        res.id = response.id;
        res.rawId = encode(response.rawId);
        res.type = response.type;
        res.response = {};
        res.response.clientDataJSON = encode(response.response.clientDataJSON);
        res.response.authenticatorData = encode(response.response.authenticatorData);
        res.response.signature = encode(response.response.signature);
        res.clientExtensionResults = response.getClientExtensionResults();
        gel('sys_web_authentication_response').value = JSON.stringify(res);
        validateAuthenticationResponse(res);
    }).catch(function(error) {
        jslog("Error while authenticating: " + error);
    });
}