/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
/* global require */
/* global forge */
/* global OV */

var XMLHttpRequest = XMLHttpRequest || require('xhr2');

module.exports = (function() {

    'use strict';

    var Q = require('q');
    var config = require('./config.js');
    var session = require('./session.js');

    var crono = require('./crono.js')();

    var writeInsParser = require('../parsers/write-ins.js');
    var jsrsasign = require('jsrsasign');
 
	// 'informations' processing (client challenge)

    var processInformationsResponse = function(informationsResponse) {

        // parse credentials

        session('credentials', OV.parseCredentials(informationsResponse.credentialData, session('pin')));
        session('certificates', informationsResponse.certificates);

        // validate certificate chain

        OV.validateCertificateChain(
            session('credentials').certificate,
            session('credentials').certificateAuth,
            informationsResponse.certificates);

        // parse server challenge

        var dataToVerify = {
            challengeValue: informationsResponse.serverChallengeMessage.serverChallenge,
            timestamp: informationsResponse.serverChallengeMessage.timestamp,
            electionEventId: config('electionEventId'),
            credentialId: session('credentialId')
        };

        var clientChallengeMessage =
            OV.parseServerChallenge(
                informationsResponse,
                dataToVerify,
                session('credentials'));

        return {
            serverChallengeMessage: informationsResponse.serverChallengeMessage,
            clientChallengeMessage: clientChallengeMessage,
            certificate: session('credentials').certificateAuth,
            credentialId: session('credentialId')
        };
    };
	
		
	var processTokensResponse = function(tokensResponse) {

        // verify and store authToken
        OV.parseTokenResponse(
            tokensResponse,
            session('certificates'),
            session('credentialId'),
            config('electionEventId'));

        // store voting card id to validate it later against the authToken credentialId
        session('votingCardId', tokensResponse.authenticationToken.voterInformation.votingCardId);

        session('authenticationToken', tokensResponse.authenticationToken);
        session('verificationCardId',tokensResponse.authenticationToken.voterInformation.verificationCardId);

        // store the verification card publicKey
        session('verificationCardPublicKey',
            JSON.parse(forge.util.decode64(
                tokensResponse.verificationCard.signedVerificationPublicKey)));

        // parse credentials from verification card data
        session('verificationCardSecret',
            OV.parseVerificationCard(
                tokensResponse.verificationCard.verificationCardKeystore,
                session('pin')));

        // parse and store encryption parameters
        session('encParams', OV.parseEncryptionParams(tokensResponse));

        // store the verification card set data
        session('verificationCardSet', tokensResponse.verificationCardSet);

        var ballotBox = jsrsasign.jws.JWS.parse(tokensResponse.ballotBox.signature).payloadObj.objectToSign;

        // store the ballot box certificate
        session('ballotBoxCert', ballotBox.ballotBoxCert);

        // parse ballot as it comes in string format
        var ballot = JSON.parse(jsrsasign.jws.JWS.parse(tokensResponse.ballot.signature).payloadObj.objectToSign);

        // store the ballot box alphabet decoded
        if (typeof ballotBox.writeInAlphabet === 'string') {

            ballot.writeInAlphabet = Buffer.from(
                forge.util.binary.base64.decode(ballotBox.writeInAlphabet.toString())
            ).toString('utf8');
            session('writeInAlphabet', ballot.writeInAlphabet);

        }

        session('maximumPossibleNumWriteins', writeInsParser.calculateMaximumPossibleWriteins(ballot));
        session('ballotWriteInsRepresentations', writeInsParser.getBallotWriteInsRepresentations(ballot));

        var ballotTexts = tokensResponse.ballotTextsSignature.map(function (item) {
            return JSON.parse(jsrsasign.jws.JWS.parse(item.signedObject).payloadObj.objectToSign);
        });

        return {
            ballot: ballot,
            ballotTexts: ballotTexts,
            status: tokensResponse.votingCardState,
            validationError: tokensResponse.validationError
        };

    };

	var processTokens = function(deferred, tokensResponse) {

        try {

            crono.start('processTokensResponse');
            deferred.resolve(processTokensResponse(tokensResponse));
            crono.stop();
            console.log(JSON.stringify(crono.getLog()));

        } catch (e) {
            deferred.reject(e);
        }

    };
	
    var processInformations = function(deferred, informationsResponse) {

        try {

            crono.start('processInformationsResponse (challenge)');
            var clientChallengeMessage = processInformationsResponse(informationsResponse).clientChallengeMessage;
            crono.stop();

            // get the ballot

            crono.start('http authenticate token');
            var endpoint = config('endpoints.tokens')
                .replace('{tenantId}', config('tenantId'))
                .replace('{electionEventId}', config('electionEventId'))
                .replace('{credentialId}', session('credentialId'));

            var xhr = new XMLHttpRequest();
            xhr.open('POST', config('host') + endpoint);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var response = JSON.parse(this.responseText);
                        if (response.authenticationToken) {
                            crono.stop();
                            processTokens(deferred, response);
                        } else {
                            deferred.reject(response.validationError ? response : xhr.status);
                        }
                    } else {
                        deferred.reject(xhr.status);
                    }
                }
            };
            xhr.onerror = function() {
                try {
                    deferred.reject(xhr.status);
                } catch (e) {
					//This block is intentionally left blank
				}
            };

            xhr.setRequestHeader('Accept', 'application/json');
            xhr.setRequestHeader('Content-Type', 'application/json');
            xhr.send(JSON.stringify({
                serverChallengeMessage: informationsResponse.serverChallengeMessage,
                clientChallengeMessage: clientChallengeMessage,
                certificate: session('credentials').certificateAuth,
                credentialId: session('credentialId')
            }));

        } catch (e) {
            deferred.reject(e);
        }

    };
	
	// perform c/s challenge and request ballot

    var requestBallot = function(startVotingKey) {

        crono.reset();

        var deferred = Q.defer();

        try {

            // derive startVotingKey

            crono.start('parseStartVotingKey');
            var derived = OV.parseStartVotingKey(startVotingKey, config('electionEventId'));
            session('pin', derived.pin);
            session('credentialId', derived.credentialId);

            crono.stop();

            // request 'informations'

            crono.start('http request token');
            var endpoint = config('endpoints.informations')
                .replace('{tenantId}', config('tenantId'))
                .replace('{electionEventId}', config('electionEventId'))
                .replace('{credentialId}', session('credentialId'));


            var xhr = new XMLHttpRequest();
            xhr.open('GET', config('host') + endpoint);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        crono.stop();
                        processInformations(deferred, JSON.parse(this.responseText));
                    } else {
                        deferred.reject(xhr.status);
                    }
                }
            };
            xhr.onerror = function(error) {
                try {
                    console.log('** xhr.error', error);
                    deferred.reject(xhr.status);
                } catch (e) {
					//This block is intentionally left blank
				}
            };
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.send();

        } catch (e) {
            deferred.reject(e);
        }

        return deferred.promise;
    };

    return {
        requestBallot: requestBallot,
        processInformationsResponse: processInformationsResponse,
        processTokensResponse: processTokensResponse
    };

}());
