/*
 * 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.products.ov.config.spring;

import java.util.Base64;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.certificates.CertificatesServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.api.services.ServiceFactory;
import com.scytl.cryptolib.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricServiceFactoryHelper;
import com.scytl.cryptolib.certificates.service.CertificatesServiceFactoryHelper;
import com.scytl.cryptolib.elgamal.service.ElGamalServiceFactoryHelper;
import com.scytl.cryptolib.primitives.service.PrimitivesServiceFactoryHelper;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.cryptolib.symmetric.service.SymmetricServiceFactoryHelper;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.choices.codes.BallotCastingKeyGenerator;
import com.scytl.products.ov.choices.codes.ChoicesCodesGenerationAPI;
import com.scytl.products.ov.choices.codes.ChoicesCodesGenerator;
import com.scytl.products.ov.choices.codes.RandomWithChecksumBallotCastingKeyGenerator;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodecImpl;
import com.scytl.products.ov.config.commands.api.ConfigurationService;
import com.scytl.products.ov.config.commands.ballotbox.BallotBoxGenerator;
import com.scytl.products.ov.config.commands.ballotbox.BallotBoxHolderInitializer;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventGenerator;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventHolderInitializer;
import com.scytl.products.ov.config.commands.electionevent.CreateElectionEventSerializer;
import com.scytl.products.ov.config.commands.electionevent.datapacks.beans.ElectionInputDataPackUtils;
import com.scytl.products.ov.config.commands.electionevent.datapacks.generators.ElGamalCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.electionevent.datapacks.generators.ElectionCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.progress.ProgressManager;
import com.scytl.products.ov.config.commands.progress.ProgressManagerImpl;
import com.scytl.products.ov.config.commands.progress.ProgressManager_old;
import com.scytl.products.ov.config.commands.uuid.UUIDGenerator;
import com.scytl.products.ov.config.commands.voters.JobExecutionObjectContext;
import com.scytl.products.ov.config.commands.voters.JobSelectionStrategy;
import com.scytl.products.ov.config.commands.voters.PropertiesBasedJobSelectionStrategy;
import com.scytl.products.ov.config.commands.voters.VotersHolderInitializer;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VerificationCardCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VerificationCardSetCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VotingCardCredentialDataPackGenerator;
import com.scytl.products.ov.config.commands.voters.datapacks.generators.VotingCardSetCredentialDataPackGenerator;
import com.scytl.products.ov.config.commons.Constants;
import com.scytl.products.ov.config.logs.LoggableInjector;
import com.scytl.products.ov.datapacks.generators.CertificateDataBuilder;
import com.scytl.products.ov.datapacks.generators.X509CertificateGenerator;
import com.scytl.products.ov.readers.ConfigurationInputReader;
import com.scytl.products.ov.utils.ChecksumUtils;
import com.scytl.products.ov.utils.ConfigObjectMapper;
import com.scytl.products.ov.utils.EncryptionParametersLoader;
import com.scytl.products.ov.utils.FingerprintGenerator;
import com.scytl.products.ov.utils.IDsParser;
import com.scytl.products.ov.utils.KeyStoreReader;
import com.scytl.products.ov.utils.X509CertificateLoader;

@Configuration
@PropertySources({@PropertySource("classpath:properties/springConfig.properties"),
        @PropertySource(value = "classpath:springConfig-override.properties", ignoreResourceNotFound = true) })
public class SpringConfigServices {

    @Autowired
    Environment env;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public GenericObjectPoolConfig genericObjectPoolConfig() {

        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxTotal(Integer.valueOf(env.getProperty("services.cryptolib.pool.size")));
        genericObjectPoolConfig.setMaxIdle(Integer.valueOf(env.getProperty("services.cryptolib.timeout")));

        return genericObjectPoolConfig;
    }

    @Bean
    public ServiceFactory<SymmetricServiceAPI> symmetricServiceFactoryHelper(
            final GenericObjectPoolConfig genericObjectPoolConfig) {
        return SymmetricServiceFactoryHelper.getFactoryOfThreadSafeServices(genericObjectPoolConfig);
    }

    @Bean
    public SymmetricServiceAPI symmetricServiceAPI(
            final ServiceFactory<SymmetricServiceAPI> symmetricServiceFactoryHelper) throws GeneralCryptoLibException {
        return symmetricServiceFactoryHelper.create();
    }

    @Bean
    public ServiceFactory<AsymmetricServiceAPI> asymmetricServiceAPIServiceFactory(
            final GenericObjectPoolConfig genericObjectPoolConfig) {
        return AsymmetricServiceFactoryHelper.getFactoryOfThreadSafeServices(genericObjectPoolConfig);
    }

    @Bean
    public AsymmetricServiceAPI asymmetricServiceAPI(
            final ServiceFactory<AsymmetricServiceAPI> asymmetricServiceAPIServiceFactory)
            throws GeneralCryptoLibException {
        return asymmetricServiceAPIServiceFactory.create();
    }

    @Bean
    public ServiceFactory<CertificatesServiceAPI> certificatesServiceAPIServiceFactory(
            final GenericObjectPoolConfig genericObjectPoolConfig) {
        return CertificatesServiceFactoryHelper.getFactoryOfThreadSafeServices(genericObjectPoolConfig);
    }

    @Bean
    public CertificatesServiceAPI certificatesServiceAPI(
            final ServiceFactory<CertificatesServiceAPI> certificatesServiceAPIServiceFactory)
            throws GeneralCryptoLibException {
        return certificatesServiceAPIServiceFactory.create();
    }

    @Bean
    public ScytlKeyStoreServiceAPI storesServiceAPI() throws GeneralCryptoLibException {
        return new ScytlKeyStoreService();
    }

    @Bean
    public ServiceFactory<PrimitivesServiceAPI> primitivesServiceAPIServiceFactory(
            final GenericObjectPoolConfig genericObjectPoolConfig) {
        return PrimitivesServiceFactoryHelper.getFactoryOfThreadSafeServices(genericObjectPoolConfig);
    }

    @Bean
    public PrimitivesServiceAPI primitivesServiceAPI(
            final ServiceFactory<PrimitivesServiceAPI> primitivesServiceAPIServiceFactory)
            throws GeneralCryptoLibException {
        return primitivesServiceAPIServiceFactory.create();
    }

    @Bean
    public ChecksumUtils checksumUtils() {
        return new ChecksumUtils();
    }

    @Bean
    public BallotCastingKeyGenerator ballotCastingKeyGeneratorAPI(PrimitivesServiceAPI primitivesService,
            ChecksumUtils checksumUtils) {
        return new RandomWithChecksumBallotCastingKeyGenerator(primitivesService, checksumUtils);
    }

    @Bean
    public ChoicesCodesGenerationAPI choicesCodesGenerationAPI(final PrimitivesServiceAPI primitivesService,
            final SymmetricServiceAPI symmetricService) {
        return new ChoicesCodesGenerator(primitivesService, symmetricService);
    }

    @Bean
    public CryptoAPIRandomString cryptoAPIRandomString(final PrimitivesServiceAPI primitivesServiceAPI) {
        return primitivesServiceAPI.get32CharAlphabetCryptoRandomString();
    }

    @Bean
    public CertificateDataBuilder certificateDataBuilder() {
        return new CertificateDataBuilder();
    }

    @Bean
    public X509CertificateLoader x509CertificateLoader() {
        return new X509CertificateLoader();
    }

    @Bean
    public X509CertificateGenerator certificatesGenerator(final CertificatesServiceAPI certificatesService,
            final CertificateDataBuilder certificateDataBuilder) {
        return new X509CertificateGenerator(certificatesService, certificateDataBuilder);
    }

    @Bean
    public ElectionCredentialDataPackGenerator electionCredentialDataPackGenerator(final AsymmetricServiceAPI asymmetricService,
            final X509CertificateGenerator certificatesGenerator, final ScytlKeyStoreServiceAPI storesService,
            final PrimitivesServiceAPI primitivesService, final CryptoAPIRandomString cryptoRandomString) {
        return new ElectionCredentialDataPackGenerator(asymmetricService, certificatesGenerator, storesService,
            primitivesService, cryptoRandomString);
    }
    
    @Bean
    public ElGamalCredentialDataPackGenerator elGamalCredentialDataPackGenerator(
            ElGamalServiceAPI elGamalService,
            ScytlKeyStoreServiceAPI storeService,
            PrimitivesServiceAPI primitivesService) {
        return new ElGamalCredentialDataPackGenerator(elGamalService, storeService, primitivesService);
    }

    @Bean
    public VotingCardCredentialDataPackGenerator votingCardCredentialDataPackGenerator(
            final AsymmetricServiceAPI asymmetricService, final X509CertificateGenerator certificatesGenerator,
            final PrimitivesServiceAPI primitivesService, final ScytlKeyStoreServiceAPI storesService,
            final CryptoAPIRandomString cryptoRandomString) {
        return new VotingCardCredentialDataPackGenerator(asymmetricService, certificatesGenerator, storesService,
            primitivesService, cryptoRandomString);

    }

    @Bean
    public VerificationCardSetCredentialDataPackGenerator verificationCardSetCredentialDataPackGenerator(
            final AsymmetricServiceAPI asymmetricService, final X509CertificateGenerator certificatesGenerator,
            final ScytlKeyStoreServiceAPI storesService, final CryptoAPIRandomString cryptoRandomString,
            final ElGamalServiceAPI elGamalService, final PrimitivesServiceAPI primitivesService) {
        return new VerificationCardSetCredentialDataPackGenerator(asymmetricService, certificatesGenerator,
            storesService, cryptoRandomString, elGamalService, primitivesService);
    }

    @Bean
    public VotingCardSetCredentialDataPackGenerator votingCardSetCredentialDataPackGenerator(
            final AsymmetricServiceAPI asymmetricService, final X509CertificateGenerator certificatesGenerator,
            final ScytlKeyStoreServiceAPI storesService, final CryptoAPIRandomString cryptoRandomString,
            final SymmetricServiceAPI symmetricService, final PrimitivesServiceAPI primitivesService) {
        return new VotingCardSetCredentialDataPackGenerator(asymmetricService, certificatesGenerator, storesService,
            cryptoRandomString, symmetricService, primitivesService);
    }

    @Bean
    public CreateElectionEventGenerator createElectionEventGenerator(
            final ElectionCredentialDataPackGenerator electionCredentialDataPackGenerator,
            final ElGamalCredentialDataPackGenerator elGamalCredentialDataPackGenerator,
            final EncryptionParametersLoader encryptionParametersLoader,
            final ProgressManager_old votersProgressManager) {
        return new CreateElectionEventGenerator(
            electionCredentialDataPackGenerator,
            elGamalCredentialDataPackGenerator, 
            encryptionParametersLoader, votersProgressManager);
    }

    @Bean
    public BallotBoxHolderInitializer createBallotBoxHolderInitializer(
            final ConfigurationInputReader configurationInputReader, final X509CertificateLoader x509CertificateLoader,
            final EncryptionParametersLoader encryptionParametersLoader, final KeyStoreReader keyStoreReader) {

        return new BallotBoxHolderInitializer(configurationInputReader, x509CertificateLoader,
            encryptionParametersLoader, keyStoreReader);
    }

    @Bean
    public CreateElectionEventHolderInitializer createElectionEventHolderInitializer(
            final ConfigurationInputReader configurationInputReader) {

        return new CreateElectionEventHolderInitializer(configurationInputReader);
    }

    @Bean
    public VotersHolderInitializer createVotingCardSetInitializer(
            final ConfigurationInputReader configurationInputReader, final X509CertificateLoader x509CertificateLoader,
            final ScytlKeyStoreServiceAPI storesService, final EncryptionParametersLoader encryptionParametersLoader) {

        return new VotersHolderInitializer(configurationInputReader, x509CertificateLoader, storesService,
            encryptionParametersLoader);
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public BallotBoxGenerator createBallotBoxGenerator(
            final ElectionCredentialDataPackGenerator electionCredentialDataPackGenerator) {
        return new BallotBoxGenerator(electionCredentialDataPackGenerator);
    }

    @Bean
    public ConfigurationInputReader configurationInputReader() {
        return new ConfigurationInputReader();
    }

    @Bean
    public CreateElectionEventSerializer createElectionEventSerializer(final PrimitivesServiceAPI primitivesService) {
        return new CreateElectionEventSerializer(primitivesService);
    }

    @Bean
    public KeyStoreReader keyStoreReader() throws GeneralCryptoLibException {
        return new KeyStoreReader();
    }

    @Bean
    public ElectionInputDataPackUtils inputDataPackUtils() {
        return new ElectionInputDataPackUtils();
    }

    @Bean
    public IDsParser idsParser() {
        return new IDsParser();
    }

    @Bean
    public VerificationCardCredentialDataPackGenerator verificationCardCredentialDataPackGenerator(
            final PrimitivesServiceAPI primitivesService, final AsymmetricServiceAPI asymmetricService,
            final X509CertificateGenerator certificatesGenerator, final ElGamalServiceAPI elgamalService,
            final FingerprintGenerator fingerprintGenerator) throws GeneralCryptoLibException {

        final ScytlKeyStoreService keyStoreService = new ScytlKeyStoreService();
        final CryptoAPIRandomString cryptoRandomString = primitivesService.get32CharAlphabetCryptoRandomString();

        return new VerificationCardCredentialDataPackGenerator(asymmetricService, cryptoRandomString,
            certificatesGenerator, keyStoreService, elgamalService, fingerprintGenerator);

    }

    @Bean
    public ProgressManager_old votersProgressManager() {
        return new ProgressManager_old();
    }

    @Bean
    public ConfigObjectMapper configObjectMapper() {
        return new ConfigObjectMapper();
    }

    @Bean
    public EncryptionParametersLoader encryptionParametersLoader(final ConfigObjectMapper configObjectMapper) {
        return new EncryptionParametersLoader(configObjectMapper);
    }

    @Bean
    public ServiceFactory<ElGamalServiceAPI> elGamalServiceAPIServiceFactory(
            final GenericObjectPoolConfig genericObjectPoolConfig) {
        return ElGamalServiceFactoryHelper.getFactoryOfThreadSafeServices(genericObjectPoolConfig);
    }

    @Bean
    public ElGamalServiceAPI elGamalServiceAPI(final ServiceFactory<ElGamalServiceAPI> elGamalServiceAPIServiceFactory)
            throws GeneralCryptoLibException {
        return elGamalServiceAPIServiceFactory.create();
    }

    @Bean
    public UUIDGenerator uuidGenerator() {
        return new UUIDGenerator();
    }

    @Bean
    public ConfigurationService configurationService() {
        return new ConfigurationService();
    }

    @Bean
    public TransactionInfoProvider transactionInfoProvider() {
        return new TransactionInfoProvider();
    }

    @Bean
    public FingerprintGenerator fingerprintGenerator(final PrimitivesServiceAPI primitivesService) {
        return new FingerprintGenerator(primitivesService);
    }

    @Bean
    public ProgressManager progressManager() {
        return new ProgressManagerImpl();
    }

    @Bean
    public LoggableInjector loggableInjector() {
        return new LoggableInjector();
    }

    @Bean
    public Base64.Encoder encoder() {
        return Base64.getEncoder();
    }

    @Bean
    @Qualifier("urlSafeEncoder")
    public Base64.Encoder urlSafeEncoder() {
        return Base64.getUrlEncoder();
    }

    @Bean
    public JobSelectionStrategy jobSelectionStrategy(
            @Value("${spring.batch.jobs.qualifier:product}") String jobQualifier) {

        return new PropertiesBasedJobSelectionStrategy(Constants.JOB_NAME_PREFIX, jobQualifier);
    }

    /**
     * Class to hold 'complex' objects temporarily for during job execution
     *
     * @return
     */
    @Bean
    public JobExecutionObjectContext stepExecutionObjectContext() {
        return new JobExecutionObjectContext();
    }
    
    @Bean
    public ElGamalComputationsValuesCodec elGamalComputationsValuesCodec() {
        return ElGamalComputationsValuesCodecImpl.getInstance();
    }
}
