/*
 * 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
 */
package com.scytl.cryptolib.primitives.service;

import java.io.InputStream;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIKDFDeriver;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIPBKDFDeriver;
import com.scytl.cryptolib.ffc.cryptoapi.FFCEngine;
import com.scytl.cryptolib.primitives.derivation.configuration.DerivationPolicyFromProperties;
import com.scytl.cryptolib.primitives.derivation.factory.CryptoKeyDeriverFactory;
import com.scytl.cryptolib.primitives.ffc.FFCEngineImpl;
import com.scytl.cryptolib.primitives.messagedigest.configuration.MessageDigestPolicyFromProperties;
import com.scytl.cryptolib.primitives.messagedigest.factory.MessageDigestFactory;
import com.scytl.cryptolib.primitives.securerandom.configuration.SecureRandomPolicyFromProperties;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomBytes;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomString;
import com.scytl.cryptolib.primitives.securerandom.factory.SecureRandomFactory;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomBytes;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomInteger;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;

/**
 * Class which implements {@link PrimitivesServiceAPI}. Instances of this class
 * are immutable.
 */
public class PrimitivesService implements PrimitivesServiceAPI {

    private final CryptoRandomBytes _cryptoRandomBytes;

    private final CryptoRandomInteger _cryptoRandomInteger;

    private CryptoKeyDeriverFactory _cryptoKeyDeriverFactory;

    private final CryptoRandomString _stringRandom32;

    private final CryptoRandomString _stringRandom64;

    private final SecureRandom _secureRandom;

    private final MessageDigestFactory _messageDigestFactory;

    private final FFCEngine _ffcEngine;

    /**
     * Initializes all properties to default values. These default values are
     * obtained from the path indicated by
     * {@link Constants#CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH}.
     * 
     * @throws GeneralCryptoLibException
     *             if the default properties are invalid.
     */
    public PrimitivesService() throws GeneralCryptoLibException {
        this(Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH);
    }

    /**
     * Constructor which initializes its internal state using the properties
     * file located at the specified path.
     * 
     * @param path
     *            The path of the properties file to be used to configure the
     *            service.
     * @throws GeneralCryptoLibException
     *             if the path is invalid or the properties are invalid.
     */
    public PrimitivesService(final String path)
            throws GeneralCryptoLibException {

        Validate.notNullOrBlank(path, "Properties file path");

        SecureRandomFactory secureRandomFactory = new SecureRandomFactory(
            new SecureRandomPolicyFromProperties(path));

        _cryptoRandomBytes = secureRandomFactory.createByteRandom();

        _cryptoRandomInteger = secureRandomFactory.createIntegerRandom();

        _cryptoKeyDeriverFactory = new CryptoKeyDeriverFactory(
            new DerivationPolicyFromProperties(path));

        _stringRandom32 = secureRandomFactory
            .createStringRandom(SecureRandomConstants.ALPHABET_BASE32);
        _stringRandom64 = secureRandomFactory
            .createStringRandom(SecureRandomConstants.ALPHABET_BASE64);

        _messageDigestFactory = new MessageDigestFactory(
            new MessageDigestPolicyFromProperties(path));

        _secureRandom = secureRandomFactory.createSecureRandom();

        _ffcEngine = FFCEngineImpl.newInstance(_cryptoRandomBytes,
            _messageDigestFactory);
    }

    @Override
    public CryptoAPIRandomBytes getCryptoRandomBytes() {
        return _cryptoRandomBytes;
    }

    @Override
    public CryptoAPIRandomInteger getCryptoRandomInteger() {
        return _cryptoRandomInteger;
    }

    @Override
    public CryptoAPIRandomString get32CharAlphabetCryptoRandomString() {
        return _stringRandom32;
    }

    @Override
    public CryptoAPIRandomString get64CharAlphabetCryptoRandomString() {
        return _stringRandom64;
    }

    @Override
    public CryptoAPIKDFDeriver getKDFDeriver() {
        return _cryptoKeyDeriverFactory.createKDFDeriver();
    }

    @Override
    public CryptoAPIPBKDFDeriver getPBKDFDeriver() {
        return _cryptoKeyDeriverFactory.createPBKDFDeriver();
    }

    @Override
    public byte[] getHash(final byte[] data)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmpty(data, "Data");

        return _messageDigestFactory.create().generate(data);
    }

    @Override
    public byte[] getHash(final InputStream in)
            throws GeneralCryptoLibException {

        Validate.notNull(in, "Data input stream");

        return _messageDigestFactory.create().generate(in);
    }

    @Override
    public MessageDigest getRawMessageDigest() {

        return _messageDigestFactory.create().getRawMessageDigest();
    }

    @Override
    public void shuffle(final List<?> list)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(list, "List");

        Collections.shuffle(list, _secureRandom);
    }

    @Override
    public FFCEngine getFFCEngine() {
        return _ffcEngine;
    }
}
