/*
 * 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.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

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

import com.scytl.products.ov.config.multishare.EncryptedMultipleSharesContainer;
import com.scytl.products.ov.config.multishare.MultipleSharesContainer;
import com.scytl.products.ov.config.shares.exception.SmartcardException;
import com.scytl.shares.EncryptedShare;
import com.scytl.shares.Share;
import com.scytl.shares.ShareException;

/**
 * Implementation of the Smart Cards Services using the file system to write and read the Shares. The reason for the
 * existence of this class is only for providing a way to do and automatic e2e
 */
class FileSystemSmartCardService implements SmartCardService {

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

    private static final Path SMART_CARD_FOLDER = Paths.get(System.getProperty("user.home") + "/sdm/smart-cards");

    private static final Path SMART_CARD_FILE_PATH = Paths.get(SMART_CARD_FOLDER.toString(), "/smart-card.b64");

    @Override
    public void write(final Share share, final String name, final String oldPinPuk, final String newPinPuk,
            final PrivateKey signingPrivateKey)
            throws SmartcardException {

        EncryptedShare encryptedShare = new EncryptedShare(share, signingPrivateKey);
        try {
            Files.createDirectories(SMART_CARD_FOLDER);
            Path path = SMART_CARD_FILE_PATH;
            LOG.info("Saving smartcard to: " + path.toAbsolutePath().toString());
            Files.write(path,
                Arrays.asList(Base64.getEncoder().encodeToString(encryptedShare.getEncryptedShare()),
                    Base64.getEncoder().encodeToString(encryptedShare.getEncryptedShareSignature()),
                    Base64.getEncoder().encodeToString(encryptedShare.getSecretKeyBytes()), name));
        } catch (IOException e) {
            throw new SmartcardException(e.getMessage(), e);
        }

    }

    @Override
    public Share read(final String pin, final PublicKey signatureVerificationPublicKey) throws SmartcardException {

        try {
            List<String> lines = readFile();

            EncryptedShare es = new EncryptedShare(Base64.getDecoder().decode(lines.get(0)),
                Base64.getDecoder().decode(lines.get(1)), signatureVerificationPublicKey);
            return es.decrypt(Base64.getDecoder().decode(lines.get(2)));
        } catch (ShareException e) {
            throw new SmartcardException(e.getMessage(), e);
        } catch (IOException e) {
            throw new SmartcardException(e.getMessage(), e);
        }
    }

    /**
     * Checks the status of the inserted smartcard.
     *
     * @return true if the smartcard status is satisfactory, false otherwise
     */
    @Override
    public boolean isSmartcardOk() {

        return Files.exists(SMART_CARD_FILE_PATH);

    }

    /**
     * Read the smartcard label
     *
     * @return the label written to the smartcard
     * @throws SmartcardException
     */
    @Override
    public String readSmartcardLabel() throws SmartcardException {
        try {
            List<String> lines = readFile();
            return lines.get(3);
        } catch (IOException e) {
            throw new SmartcardException(e.getMessage(), e);
        }
    }

    /**
     * Use publicKey to verify current share signature
     *
     * @param publicKey
     *            the public key used to check the shares signature
     * @return true if the share signature validates, false otherwise
     * @throws SmartcardException
     *             if there is an error during the signature validation or if there is no smartcard to read.
     */
    public boolean verifyShareSignature(final PublicKey publicKey) throws SmartcardException {

        try {
            List<String> lines = readFile();
            new EncryptedShare(Base64.getDecoder().decode(lines.get(0)), Base64.getDecoder().decode(lines.get(1)),
                publicKey);
            return true;
        } catch (IOException e) {
            throw new SmartcardException(e.getMessage(), e);
        } catch (ShareException e) {
        	LOG.info("Error trying to create a new EncryptedShare.", e);
        	return false;
        }
    }

    /**
     * @return
     * @throws IOException
     */
    private List<String> readFile() throws IOException {
        Files.createDirectories(SMART_CARD_FOLDER);
        return Files.readAllLines(SMART_CARD_FILE_PATH);
    }

    /*
     * (non-Javadoc)
     * @see com.scytl.products.ov.config.shares.service.SmartCardService#readEG(java.lang.String,
     * java.security.PublicKey)
     */
    @Override
    public MultipleSharesContainer readElGamal(String pin, PublicKey signatureVerificationPublicKey)
            throws SmartcardException {
        try {
            List<String> lines = readFile();
            EncryptedMultipleSharesContainer es =
                new EncryptedMultipleSharesContainer(Base64.getDecoder().decode(lines.get(0)),
                    Base64.getDecoder().decode(lines.get(1)), signatureVerificationPublicKey);
            return es.decrypt(Base64.getDecoder().decode(lines.get(2)));
        } catch (ShareException e) {
            throw new SmartcardException(e.getMessage(), e);
        } catch (IOException e) {
            throw new SmartcardException(e.getMessage(), e);
        }
    }

}
