/*
 * 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 CL */
/* jshint maxlen: 6666 */


module.exports = (function() {

    'use strict';

    // @param options {Array<ZpGroupElement>} The options
    // @param encryptionParms {model.EncyptionParams} The encryption parameters
    // @param encryptionParms {ElGamalKeys} The encryption keys
    // @param encrypterValues {model.EncypterValues} The precomputed encrypter values (optional)

    var encryptEG = function(options, encryptionParms, encryptionKey, encrypterValues) {

        var elGamalCipherFactory = new CL.homomorphic.cipher.factory.ElGamalCipherFactory();
        var encrypter = elGamalCipherFactory.createEncrypter(encryptionKey);

        // prepare precomputed encrypterValues if supplied

        var egEncrypterValues = null;
        if (encrypterValues) {
            egEncrypterValues = encrypterValues;
        }
        var x = encrypter.encryptGroupElements(options, egEncrypterValues);
        return x;
    };

	var reducePreComputedIfNecessary = function(numDesiredSubkeys, precomputed) {

        if(precomputed.getPhis().length === numDesiredSubkeys) {

            return precomputed;

        } else if (precomputed.getPhis().length > numDesiredSubkeys) {

            return {
                getGamma: function() {
                    return precomputed.getGamma();
                },
                getPhis: function() {
                    var arrayWithDesiredNumElements = [];
                    for(var i = 0; i < numDesiredSubkeys; i++) {
                        arrayWithDesiredNumElements.push(precomputed.getPhis()[i]);
                    }

                    return arrayWithDesiredNumElements;
                },
                getElGamalComputationValues: function() {
                    return precomputed.getElGamalComputationValues();
                },
                getR: function() {
                    return precomputed.getR();
                }
            };

        } else {
            throw new Error('There are fewer subkeys in the precomputation than the required number. There are: ' + precomputed.getPhis().length + ', but the number needed is: ' + numDesiredSubkeys);
        }
    };
	
	var reduceKeySizeIfNecessary = function(numDesiredSubkeys, originalKey) {

        if(originalKey.getGroupElementsArray().length === numDesiredSubkeys) {

            return originalKey;

        } else if (originalKey.getGroupElementsArray().length > numDesiredSubkeys) {

            return {
                getGroup: function() {
                    return originalKey.getGroup();
                },
                getGroupElementsArray: function() {
                    var arrayWithDesiredNumElements = [];
                    for(var i = 0; i < numDesiredSubkeys; i++) {
                        arrayWithDesiredNumElements.push(originalKey.getGroupElementsArray()[i]);
                    }
                    return arrayWithDesiredNumElements;
                }
            };
        } else {
            throw new Error('There are fewer subkeys than the required number. There are: ' + originalKey.getGroupElementsArray().length + ', but the number needed is: ' + numDesiredSubkeys);
        }
    };
	
    // @param options {Array<ZpGroupElement>} The vote option prime numbers
    // @param encryptionParms {model.EncyptionParams} The encryption parameters and keys
    // @param encrypterValues {model.EncypterValues} The precomputed encrypter values (optional)

    var encryptOptionsAndWriteins = function(options, encryptionParms, encrypterValues) {

        var egEncrypterValues;
        if ( encrypterValues && encrypterValues.options ) {
            egEncrypterValues = encrypterValues.options;

            var precomputed = reducePreComputedIfNecessary(options.length, egEncrypterValues);
        }

        var pulicKey = reduceKeySizeIfNecessary(options.length, encryptionParms.optionsEncryptionKey);

        return encryptEG(options, encryptionParms, pulicKey, precomputed);       
    };

    // Cipher vote options

    // @param options {Array<ZpGroupElement>} The vote option prime numbers
    // @param encryptionParms {model.EncyptionParams} The encryption parameters and keys
    // @param encrypterValues {model.EncypterValues} The precomputed encrypter values (optional)

    var encryptOptions = function(options, encryptionParms, encrypterValues) {

        var compressedOptions = CL.commons.mathematical.groupUtils.compressGroupElements(encryptionParms.group, options);

        var egEncrypterValues = null;
        if ( encrypterValues && encrypterValues.options ) {
            egEncrypterValues = encrypterValues.options;
        }

        return encryptEG([compressedOptions], encryptionParms, encryptionParms.optionsEncryptionKey, egEncrypterValues);      

    };

    // Cipher partial choice codes

    // @param options {Array<BigInteger>} The partial choice codes
    // @param encryptionParms {model.EncyptionParams} The encryption parameters and keys
    // @param encrypterValues {model.EncypterValues} The precomputed encrypter values (optional)

    var encryptPartialChoiceCodes = function(codes, encryptionParms, encrypterValues) {

        var zpCodes = codes.map(function(x) {
            return new CL.commons.mathematical.ZpGroupElement(x, encryptionParms.group.getP(), encryptionParms.group.getQ());            
        });
        
        var egEncrypterValues = null;
        if ( encrypterValues && encrypterValues.choicecodes ) {
            egEncrypterValues = encrypterValues.choicecodes;
        }
        
        return encryptEG(zpCodes, encryptionParms, encryptionParms.choiceCodesEncryptionKey, egEncrypterValues);

    };

    // symmetric decrypt 

    // @param key {String} Key
    // @param encrypted {String} Encrypted message

    var symmetricDecrypt = function(key, encrypted) {
        return CL.symmetric.service.decrypt(key, encrypted);
    };


    return {
        encryptOptions: encryptOptions,
        encryptOptionsAndWriteins: encryptOptionsAndWriteins,
        encryptPartialChoiceCodes: encryptPartialChoiceCodes,
        symmetricDecrypt: symmetricDecrypt
    };

})();






