/*
 * 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.channel.cc.commons.keymanagement;

import static java.nio.file.Files.deleteIfExists;
import static java.nio.file.Files.readAllBytes;
import static java.text.MessageFormat.format;
import static java.util.Arrays.fill;
import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStore.PasswordProtection;

import javax.security.auth.DestroyFailedException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

/**
 * Implementation of {@link NodeKeysActivator}.
 */
@Transactional(noRollbackFor = {InvalidKeyStoreException.class,
        InvalidPasswordException.class, InvalidNodeCAException.class,
        NoSuchFileException.class,
        IOException.class }, rollbackFor = KeyManagementException.class)
public class NodeKeysActivatorImpl implements NodeKeysActivator {
    private static final Logger LOGGER = LoggerFactory.getLogger("std");

    private final KeyManager manager;

    /**
     * Constructor.
     * 
     * @param manager
     *            the key manager
     */
    public NodeKeysActivatorImpl(KeyManager manager) {
        this.manager = manager;
    }

    private static void deletePasswordFile(Path passwordFile)
            throws IOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                format("Deleting password file ''{0}''...", passwordFile));
        }
        deleteIfExists(passwordFile);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(format("Password file ''{0}'' has been deleted.",
                passwordFile));
        }
    }

    private static void destroyPassword(PasswordProtection password) {
        try {
            password.destroy();
        } catch (DestroyFailedException e) {
            LOGGER.warn("Failed to destroy password.", e);
        }
    }

    private static PasswordProtection loadPassword(Path file)
            throws NoSuchFileException, IOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                format("Loading password from file ''{0}''...", file));
        }
        ByteBuffer bytes = ByteBuffer.wrap(readAllBytes(file));
        try {
            CharBuffer chars = StandardCharsets.UTF_8.decode(bytes);
            try {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(format(
                        "Password has been loaded from file ''{0}''.",
                        file));
                }
                return new PasswordProtection(chars.array());
            } finally {
                fill(chars.array(), '\u0000');
            }
        } finally {
            fill(bytes.array(), (byte) 0);
        }
    }

    @Override
    public void activateNodeKeys(Path folder)
            throws InvalidKeyStoreException, InvalidPasswordException,
            InvalidNodeCAException, KeyManagementException,
            NoSuchFileException, IOException {
        activateNodeKeys(folder, "ccncakey");
    }

    @Override
    public void activateNodeKeys(Path folder, String nodeCAAlias)
            throws InvalidKeyStoreException, InvalidPasswordException,
            InvalidNodeCAException, KeyManagementException,
            NoSuchFileException, IOException {
        Path keyStoreFile = folder.resolve("keys.p12");
        Path passwordFile = folder.resolve("password");
        activateNodeKeys(keyStoreFile, nodeCAAlias, passwordFile);
    }

    @Override
    public void activateNodeKeys(Path keyStoreFile, String nodeCAAlias,
            Path passwordFile)
            throws InvalidKeyStoreException, InvalidPasswordException,
            InvalidNodeCAException, KeyManagementException,
            NoSuchFileException, IOException {
        requireNonNull(manager, "Manager is null.");
        requireNonNull(keyStoreFile, "Key store file is null.");
        requireNonNull(passwordFile, "Password file is null.");
        
        PasswordProtection password = null;
        boolean activated = false;
		try {
			password = loadPassword(passwordFile);
			while (!activated) {
				try {
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Activating existing node keys....");
					}
					manager.activateNodeKeys(password);
					LOGGER.info("Node keys have been activated.");
					activated = true;
				} catch (KeyNotFoundException nFE) {
					LOGGER.debug("Node keys have not been found.", nFE);
					try {
						manager.createAndActivateNodeKeys(keyStoreFile, nodeCAAlias, password);
						LOGGER.info("Node keys have been created and activated.");
						activated = true;
					} catch (KeyAlreadyExistsException aEE) {
						LOGGER.info("Node keys already exist.", aEE);
					}
				}
			}
		} finally {
			if (password != null) {
				destroyPassword(password);
			}
			deletePasswordFile(passwordFile);
		}
    }
}
