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

import java.security.PrivateKey;
import java.security.PublicKey;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.products.ov.config.multishare.MultipleSharesContainer;
import com.scytl.products.ov.config.multishare.TokenOpsForMultipleSharesContainer;
import com.scytl.products.ov.config.shares.exception.NoSmartcardFoundException;
import com.scytl.products.ov.config.shares.exception.SmartcardException;
import com.scytl.products.ov.config.shares.exception.WrongPinException;
import com.scytl.shares.EncryptedShare;
import com.scytl.shares.Share;
import com.scytl.shares.ShareException;
import com.scytl.token.AcquireTokenTask;
import com.scytl.token.GuiOutput;
import com.scytl.token.ModuleManager;
import com.scytl.token.TokenOps;

import iaik.pkcs.pkcs11.Module;
import iaik.pkcs.pkcs11.Token;
import iaik.pkcs.pkcs11.TokenException;

/**
 * Implementation of the SmartCard Service
 */
class DefaultSmartCardService implements SmartCardService {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    private final TokenOps tokenOps = new TokenOps();

    @Override
    public synchronized void write(final Share share, final String name, final String oldPinPuk, final String newPinPuk,
            final PrivateKey signingPrivateKey) throws SmartcardException {
        Token token = acquireToken();
        EncryptedShare encryptedShare = new EncryptedShare(share, signingPrivateKey);
        try {
            tokenOps.writeShare(token, oldPinPuk.toCharArray(), newPinPuk.toCharArray(), name, encryptedShare);
        } catch (TokenException e) {
            throw new SmartcardException("Error while writing the token to the smartcard", e);
        }
    }

    @Override
    public synchronized Share read(final String pin, final PublicKey signatureVerificationPublicKey)
            throws SmartcardException {
        Token token = acquireToken();
        Share shareRead;
        try {
            shareRead = tokenOps.readShare(token, pin.toCharArray(), signatureVerificationPublicKey);
        } catch (TokenException | ShareException | IllegalArgumentException e) {
            // Due to bad design of TokenOps, IllegalArgumentException here means that
            // the share or it's signature are missing in the smartcard.
            throw new SmartcardException("There was an error reading the smartcard", e);
        }
        if (shareRead == null) {
            throw new WrongPinException();
        }
        return shareRead;
    }

    @Override
    public synchronized MultipleSharesContainer readElGamal(final String pin,
            final PublicKey signatureVerificationPublicKey) throws SmartcardException {

		TokenOpsForMultipleSharesContainer tokenOpsForMultipleSharesContainer = new TokenOpsForMultipleSharesContainer();
		Token token = acquireToken();
		MultipleSharesContainer shareRead;
		try {
			shareRead = tokenOpsForMultipleSharesContainer.readShare(token, pin.toCharArray(),
					signatureVerificationPublicKey);
		} catch (TokenException | ShareException | IllegalArgumentException e) {
			// Due to bad design of TokenOps, IllegalArgumentException here means that
            // the share or it's signature are missing in the smartcard.
            throw new SmartcardException("There was an error reading the smartcard", e);
        }
        if (shareRead == null) {
            throw new WrongPinException();
        }
        return shareRead;
    }

    @Override
    public synchronized boolean isSmartcardOk() {
        // Check if a token can be retrieved for the inserted smartcard
        return tryAcquireToken() != null;
    }

    @Override
    public synchronized String readSmartcardLabel() throws SmartcardException {
        Token token = acquireToken();
        try {
            // Label is trimmed as it is padded to the max label size (32
            // chars)
            return token.getTokenInfo().getLabel().trim();
        } catch (TokenException e) {
            throw new SmartcardException("There was an error reading the smartcard label", e);
        }
    }

    private Token acquireToken() throws NoSmartcardFoundException {
        Token token = tryAcquireToken();
        if (token == null) {
            throw new NoSmartcardFoundException();
        }
        return token;
    }

    private Token tryAcquireToken() {
        Module module = ModuleManager.getModule();
        GuiOutput output = new GuiOutput() {

            @Override
            public void tooManyTokens(final int nTokens) {
                LOG.info("Too many tokens detected, number of tokens: " + nTokens);
            }

            @Override
            public void noTokenPresent() {
                LOG.info("No token detected");
            }
        };
        return new AcquireTokenTask(module, output, false).call();
    }
}
