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

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import org.apache.commons.io.IOUtils;

import com.scytl.products.ov.config.shares.exception.SharesException;
import com.scytl.products.ov.config.shares.service.SmartCardService;
import com.scytl.products.ov.config.shares.service.SmartCardServiceFactory;
import com.scytl.token.AcquireTokenTask;
import com.scytl.token.GuiOutput;
import com.scytl.token.ModuleManager;

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

/**
 * Tool that provides functionality for configuring smartcards.
 */
public class SmartcardTool {

    private static final int MINIMUM_PIN_PUK_LENGTH = 6;

    private static final String CHANGE_PIN_PUK_OPTION = "-changePinPuk";

    private static final String TEST_PIN_OPTION = "-testPin";
    
    private static final boolean TEST_PIN_OK = true;
    
    private static final boolean TEST_PIN_NOK = false;
    
    private static SmartCardService smartcardService;

    public SmartcardTool() {
        
        smartcardService = SmartCardServiceFactory.getSmartCardService(true);
    }

    public void changePinPuk() throws SharesException, IOException, TokenException {

        System.out.println("\n=============================================================================");
        System.out.println("                   Configure PIN & PUK");
        System.out.println("=============================================================================\n");
        System.out.println("How many smartcards do you want to configure? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        System.out.print("Enter a number: ");

        int n = Integer.parseInt(in.readLine());
        System.out.print("\n");

        for (int i = 0; i < n; i++) {

            int indexToDisplay = i + 1;

            System.out.println("Please insert smartcard " + indexToDisplay + " and press Enter");
            String smCard = in.readLine();
            checkCardStatusAndFreeResourcesIfNecessary(in);
            
            System.out.print("Please enter the OLD smartcard PUK: ");
            String oldPUK = in.readLine();
            System.out.print("\n");

            System.out.print("Please enter the NEW smartcard PUK: ");
            String newPUK = in.readLine();
            System.out.print("\n");

            // clear the token, initializing PUK
            Token token = acquireToken();
            token.initToken(oldPUK.toCharArray(), "cardLabel");

            // get session to make operations to token.
            Session sessionRW =
                    token.openSession(Token.SessionType.SERIAL_SESSION, Token.SessionReadWriteBehavior.RW_SESSION, null,
                        null);

            // initialize PUK
            try {
                sessionRW.login(Session.UserType.SO, oldPUK.toCharArray());
                sessionRW.initPIN(newPUK.toCharArray());
                sessionRW.setPIN(oldPUK.toCharArray(), newPUK.toCharArray());
                System.out.println("Smartcard (" + indexToDisplay + ") " + smCard + " has been successfully updated\n");
            } catch (TokenException e) {
                System.out.println("Failed to set the PIN and PUK");
                System.out.println("Error was: " + e.getMessage());
            } finally {
                sessionRW.logout();
            }
        }

        System.out.println("The changing PIN and PUK process has finished\n");
        printCredits();
    }

    public boolean testPin() throws IOException, SharesException, TokenException {

        System.out.println("\n=============================================================================");
        System.out.println("                   Test current PIN");
        System.out.println("=============================================================================\n");
        System.out
        .println("Note: If the option '-testPin' is called 3 times with an incorrect PIN, then the smartcard will be blocked from reading, although the option of changing the PIN and PUK will still be available if the current PUK is known.\n");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("Please insert smartcard and press Enter");
        String smCard=in.readLine();
        
        if (checkCardStatusAndFreeResourcesIfNecessary(in)) {
	        System.out.print("Enter the pin that you would like to try: ");
	        String pinAsString = in.readLine();
			
	        if (isAvalidPin(pinAsString)) {
				try {
					tryToObtainSessionUsingSuppliedPin(pinAsString);
				} catch (TokenException e) {
					System.out.println("The entered PIN for Smartcard " + smCard + " is NOT the correct PIN.");
				}
				System.out.println("The entered PIN for Smartcard " + smCard + " is the current PIN.");
			}
	        return TEST_PIN_OK;
        } else {
        	return TEST_PIN_NOK;
        }
    }

    public static void main(final String... args) throws Exception {

		if (args.length != 1) {
			printHelp();
		} else {

			String command = args[0];
			SmartcardTool tool = new SmartcardTool();
			if (command.equals(CHANGE_PIN_PUK_OPTION)) {
				tool.changePinPuk();
			} else if (command.equals(TEST_PIN_OPTION)) {
				if (!tool.testPin()) {
					System.exit(-1);
				}
			} else {
				printHelp();
			}
		}
    }

    private boolean checkCardStatusAndFreeResourcesIfNecessary(final BufferedReader in) throws IOException {
        if (!smartcardService.isSmartcardOk()) {
            System.out.println("Failed to read from smartcard. Is the smartcard inserted correctly?");
            System.out.println("Terminating program, this can take several seconds...");
            in.close();
            return false;
        }
        return true;
    }

    private void tryToObtainSessionUsingSuppliedPin(final String pinAsString) throws TokenException {
        Token token = acquireToken();
        Session sessionRO =
            token.openSession(Token.SessionType.SERIAL_SESSION, Token.SessionReadWriteBehavior.RO_SESSION, null, null);
        sessionRO.login(Session.UserType.USER, pinAsString.toCharArray());
        sessionRO.logout();
    }

    private boolean isAvalidPin(final String pinAsString) {

        if (pinAsString.length() < MINIMUM_PIN_PUK_LENGTH) {
            System.out.println("The length of the PIN is 6 characters");
            return false;
        }
        
        try {
            Integer.parseInt(pinAsString);
        } catch (NumberFormatException e) {
            System.out.println("The entered PIN is the current PIN.");
            return false;
        }
        return true;
    }

    private void printCredits() {

        System.out.println("Credits:\n");

        int width = 100;
        int height = 30;

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        g.setFont(new Font("SansSerif", Font.PLAIN, 10));

        Graphics2D graphics = (Graphics2D) g;
        graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        graphics.drawString("Scytl", 10, 20);

        // save this image
        for (int y = 0; y < height; y++) {
            StringBuilder sb = new StringBuilder();
            for (int x = 0; x < width; x++) {
                sb.append(image.getRGB(x, y) == -16777216 ? " " : "$");
            }

            if (sb.toString().trim().isEmpty()) {
                continue;
            }

            System.out.println(sb);
        }
    }

    private static void printHelp() {

        printHeader();
        printUsageFromFile();
    }

    private static void printUsageFromFile() {
        try (InputStream is =
            Thread.currentThread().getContextClassLoader().getResourceAsStream("config-shares.help.txt");
             PrintWriter pw = new PrintWriter(System.out)) {
            IOUtils.copy(is, pw);
        } catch (final IOException e) {
            System.out.println("A error occurred when trying to print the help.");
            System.out.println("Error was: " + e.getMessage());
        }
    }

    private static void printHeader() {
        System.out.println("\n=============================================================================");
        System.out.println("                   Smartcard PIN and PUK configuration tool");
        System.out.println("=============================================================================\n");
    }

    private static Token acquireToken() {
        Module module = ModuleManager.getModule();
        AcquireTokenTask acquireTokenTask = new AcquireTokenTask(module, new GuiOutput() {

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

            @Override
            public void noTokenPresent() {
                System.out.println("No token detected");
            }
        }, false);

        return acquireTokenTask.call();
    }
}
