package se.chalmers.gdcn.hashcash;

import se.chalmers.gdcn.network.WorkerID;

import javax.crypto.SecretKey;
import java.io.UnsupportedEncodingException;
import java.security.*;

/**
 * Created by weeeeeew on 2014-03-31.
 */
public class HashCash {
    public final static String HASH_ALGORITHM = "SHA-1";
    
    private final int hardDifficulty, easyDifficulty;
    private final SecretKey key;

    public static enum Purpose { REG, AUTH, NONE }

    /**
     * Creates a new HashCash-cookie instance with standard difficulties.
     * The supplied key is used to validate solutions.
     * @param key A key for MACing, must be compatible with javax.crypto.Mac. At least SHA-256 is recommended,
     *            which can be generated with KeyGenerator.getInstance("HmacSHA256").generateKey()
     */
    public HashCash(SecretKey key) throws InvalidKeyException {
        this(key,15,20);
        //TODO Insert real numbers for easy and hard difficulties.
        // 28-30 seems good for REG. Lowers this for debugging purposes.
        // perhaps 22-25 for AUTH
    }

    /**
     * Creates a new HashCash-cookie instance with custom difficulties.
     * The supplied key is used to validate solutions.
     * @param key A key for MACing, must be compatible with javax.crypto.Mac. At least SHA-256 is recommended,
     *            which can be generated with KeyGenerator.getInstance("HmacSHA256").generateKey()
     * @param easy The difficulty of easy challenges.
     * @param hard The difficulty of hard challenges.
     */
    public HashCash(SecretKey key, int easy, int hard) {
        this.key = key;
        hardDifficulty = hard;
        easyDifficulty = easy;
    }

    /**
     * Generates a new Challenge without a purpose.
     * @param seed The seed of the challenge.
     * @param difficulty The difficulty of the challenge.
     * @return The challenge.
     */
    public Challenge generateChallenge(String seed, int difficulty) {
        return generateChallenge(Purpose.NONE, seed, difficulty);
    }

    /**
     * Generates a new Challenge with a purpose.
     * @param purpose The purpose of the challenge.
     * @param seed The seed of the challenge.
     * @param difficulty The difficulty of the challenge.
     * @return The challenge.
     */
    public Challenge generateChallenge(Purpose purpose, String seed, int difficulty) {
        try {
            return new Challenge(purpose, hash(seed), difficulty, key);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            //TODO Tell the user that the key supplied to the HashCash is invalid.
            return null;
        }
    }

    /**
     * Generates a hard challenge for registration purposes.
     * @param jobOwner The issuer of the challenge.
     * @param worker The worker that wants to register.
     * @param score The worker's current score.
     * @return The challenge.
     */
    public Challenge generateRegistrationChallenge(WorkerID jobOwner, WorkerID worker, int score) {
        String seed = jobOwner.toString() + worker + score;

        return generateChallenge(Purpose.REG, seed, hardDifficulty);
    }

    /**
     * Generates an easy challenge for authentication purposes.
     * @param jobOwner The issuer of the challenge.
     * @param worker The worker that wants to register.
     * @param score The worker's current score.
     * @return The challenge.
     */
    public Challenge generateAuthenticationChallenge(WorkerID jobOwner, WorkerID worker, int score) {
        String seed = jobOwner.toString() + worker + score;

        return generateChallenge(Purpose.AUTH, seed, easyDifficulty);
    }

    /**
     * Checks the validity and authenticity of a solution.
     * @param solution The solution to check.
     * @param jobOwner The issuer of the challenge.
     * @param worker The worker that claims to have solved a challenge.
     * @param score The worker's current score.
     * @return True if the worker solved the solution, false otherwise.
     */
    public boolean validateSolution(Solution solution, WorkerID jobOwner, WorkerID worker, int score) throws InvalidKeyException {
        byte[] seed = hash(jobOwner.toString() + worker + score);

        return solution.isValid(key,seed);
    }

    private static byte[] hash(String message) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance(HASH_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            //TODO Something went really wrong here, present it to the user in some way?
        }

        try {
            md.update(message.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            System.out.println("HashCash: UTF-8 is required! Things will fail now.");
        }
        return md.digest();
    }
}