/*
 * 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.symmetric.service;

import java.io.InputStream;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIDerivedKey;
import com.scytl.cryptolib.symmetric.cipher.configuration.SymmetricCipherPolicy;
import com.scytl.cryptolib.symmetric.cipher.configuration.SymmetricCipherPolicyFromProperties;
import com.scytl.cryptolib.symmetric.cipher.factory.CryptoSymmetricCipher;
import com.scytl.cryptolib.symmetric.cipher.factory.SymmetricCipherFactory;
import com.scytl.cryptolib.symmetric.key.configuration.SymmetricKeyPolicy;
import com.scytl.cryptolib.symmetric.key.configuration.SymmetricKeyPolicyFromProperties;
import com.scytl.cryptolib.symmetric.key.factory.CryptoSecretKeyGeneratorForEncryption;
import com.scytl.cryptolib.symmetric.key.factory.CryptoSecretKeyGeneratorForHmac;
import com.scytl.cryptolib.symmetric.key.factory.SecretKeyGeneratorFactory;
import com.scytl.cryptolib.symmetric.mac.configuration.MacPolicy;
import com.scytl.cryptolib.symmetric.mac.configuration.MacPolicyFromProperties;
import com.scytl.cryptolib.symmetric.mac.factory.CryptoMac;
import com.scytl.cryptolib.symmetric.mac.factory.MacFactory;

/**
 * Class which offers symmetric services.
 * <P>
 * Note: repeated calls to the methods in this class will return the same object
 * instances.
 * <P>
 * Instances of this class are immutable.
 */
public final class SymmetricService implements SymmetricServiceAPI {

    private final CryptoSecretKeyGeneratorForEncryption _secretKeyGeneratorForEncryption;

    private final CryptoSecretKeyGeneratorForHmac _secretKeyGeneratorForHmac;

    private final CryptoSymmetricCipher _cryptoSymmetricCipher;

    private final CryptoMac _cryptoMac;

    private SymmetricKeyPolicy _symmetricKeyPolicy;

    /**
     * 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 properties are invalid.
     */
    public SymmetricService() throws GeneralCryptoLibException {
        this(Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH);
    }

    /**
     * Constructor which initializes its 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 SymmetricService(final String path)
            throws GeneralCryptoLibException {

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

        _symmetricKeyPolicy = new SymmetricKeyPolicyFromProperties(path);
        SecretKeyGeneratorFactory secretKeyGeneratorFactory =
            new SecretKeyGeneratorFactory(_symmetricKeyPolicy);
        _secretKeyGeneratorForEncryption =
            secretKeyGeneratorFactory.createGeneratorForEncryption();
        _secretKeyGeneratorForHmac =
            secretKeyGeneratorFactory.createGeneratorForHmac();

        SymmetricCipherPolicy symmetricCipherPolicy =
            new SymmetricCipherPolicyFromProperties(path);
        SymmetricCipherFactory symmetricCipherFactory =
            new SymmetricCipherFactory(symmetricCipherPolicy);
        _cryptoSymmetricCipher = symmetricCipherFactory.create();

        MacPolicy macPolicy = new MacPolicyFromProperties(path);
        MacFactory macFactory = new MacFactory(macPolicy);
        _cryptoMac = macFactory.create();
    }

    @Override
    public SecretKey getSecretKeyForEncryption() {
        return _secretKeyGeneratorForEncryption.genSecretKey();
    }

    @Override
    public SecretKey getSecretKeyForHmac() {
        return _secretKeyGeneratorForHmac.genSecretKey();
    }

    @Override
    public byte[] encrypt(final SecretKey key, final byte[] data)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNullOrEmpty(data, "Data");

        return _cryptoSymmetricCipher.encrypt(key, data);
    }

    @Override
    public byte[] decrypt(final SecretKey key, final byte[] data)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNullOrEmpty(data, "Encrypted data");

        return _cryptoSymmetricCipher.decrypt(key, data);
    }

    @Override
    public byte[] getMac(final SecretKey key, final byte[]... data)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNullOrEmpty(data, "Data element array");
        if (data.length == 1) {
            Validate.notNullOrEmpty(data[0], "Data");
        } else {
            for (byte[] dataElement : data) {
                Validate.notNullOrEmpty(dataElement, "A data element");
            }
        }

        return _cryptoMac.generate(key, data);
    }

    @Override
    public byte[] getMac(final SecretKey key, final InputStream in)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNull(in, "Data input stream");

        return _cryptoMac.generate(key, in);
    }

    @Override
    public boolean verifyMac(final SecretKey key, final byte[] mac,
            final byte[]... data) throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNullOrEmpty(mac, "MAC");
        Validate.notNullOrEmpty(data, "Data element array");
        if (data.length == 1) {
            Validate.notNullOrEmpty(data[0], "Data");
        } else {
            for (byte[] dataElement : data) {
                Validate.notNullOrEmpty(dataElement, "A data element");
            }
        }

        return _cryptoMac.verify(key, mac, data);
    }

    @Override
    public boolean verifyMac(final SecretKey key, final byte[] mac,
            final InputStream in) throws GeneralCryptoLibException {

        Validate.notNull(key, "Secret key");
        Validate.notNullOrEmpty(key.getEncoded(), "Secret key content");
        Validate.notNullOrEmpty(mac, "MAC");
        Validate.notNull(in, "Data input stream");

        return _cryptoMac.verify(key, mac, in);
    }

    @Override
    public SecretKey getSecretKeyForMacFromDerivedKey(
            final CryptoAPIDerivedKey key)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Derived key");
        Validate.notNullOrEmpty(key.getEncoded(), "Derived key content");

        return getSecretKeyForMacFromBytes(key.getEncoded());
    }

    @Override
    public SecretKey getSecretKeyForEncryptionFromDerivedKey(
            final CryptoAPIDerivedKey key)
            throws GeneralCryptoLibException {

        Validate.notNull(key, "Derived key");
        Validate.notNullOrEmpty(key.getEncoded(), "Derived key content");

        return getSecretKeyForEncryptionFromBytes(key.getEncoded());
    }

    @Override
    public SecretKey getSecretKeyForEncryptionFromBytes(
            final byte[] keyBytes) throws GeneralCryptoLibException {

        Validate.notNullOrEmpty(keyBytes, "Secret key");

        String algorithm =
            _symmetricKeyPolicy.getSecretKeyAlgorithmAndSpec()
                .getAlgorithm();

        return new SecretKeySpec(keyBytes, algorithm);
    }

    private SecretKey getSecretKeyForMacFromBytes(final byte[] keyBytes)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmpty(keyBytes, "Secret key");

        String algorithm =
            _symmetricKeyPolicy.getHmacSecretKeyAlgorithmAndSpec()
                .getAlgorithm();

        return new SecretKeySpec(keyBytes, algorithm);
    }
}
