package edu.caltech.cs2.project01;

import org.junit.jupiter.api.Assertions;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;

public class SubstitutionCipher {
    private String ciphertext;
    private Map<Character, Character> key;

    // Use this Random object to generate random numbers in your code,
    // but do not modify this line.
    private static final Random RANDOM = new Random();

    /**
     * Construct a SubstitutionCipher with the given cipher text and key
     * @param ciphertext the cipher text for this substitution cipher
     * @param key the map from cipher text characters to plaintext characters
     */
    public SubstitutionCipher(String ciphertext, Map<Character, Character> key) {
        this.ciphertext = ciphertext;
        this.key = key;
    }

    /**
     * Construct a SubstitutionCipher with the given cipher text and a randomly
     * initialized key.
     * @param ciphertext the cipher text for this substitution cipher
     */
    public SubstitutionCipher(String ciphertext) {
        // generate random key
        String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Map<Character, Character> identityKey = new HashMap<>();
        Map<Character, Character> randomKey = new HashMap<>();
        for (int i = 0; i < alphabet.length(); i++){
            identityKey.put(alphabet.charAt(i), alphabet.charAt(i));
        }

        SubstitutionCipher cipher = new SubstitutionCipher(ciphertext, identityKey);
        for (int j = 0; j < 10000; j++){
            cipher = cipher.randomSwap();
        }
        this.ciphertext = ciphertext;
        this.key = cipher.key;
    }

    /**
     * Returns the unedited cipher text that was provided by the user.
     * @return the cipher text for this substitution cipher
     */
    public String getCipherText() {
        return ciphertext;
    }


    /**
     * Applies this cipher's key onto this cipher's text.
     * That is, each letter should be replaced with whichever
     * letter it maps to in this cipher's key.
     * @return the resulting plain text after the transformation using the key
     */
    public String getPlainText() {
        String plaintext = "";
        for (int i = 0; i < ciphertext.length(); i++){
            char cipherChar = ciphertext.charAt(i);
            char txt = key.get(cipherChar);
            plaintext = plaintext + txt;
        }
        return plaintext;
    }

    /**
     * Returns a new SubstitutionCipher with the same cipher text as this one
     * and a modified key with exactly one random pair of characters exchanged.
     *
     * @return the new SubstitutionCipher
     */
    public SubstitutionCipher randomSwap() {
        Map<Character, Character> swappedKey = new HashMap<>();
        swappedKey.putAll(key);
        int index1 = RANDOM.nextInt(swappedKey.size());
        int index2 = RANDOM.nextInt(swappedKey.size());
        while (index1 == index2) {
            index2 = RANDOM.nextInt(swappedKey.size());
        }
        ArrayList keyset = new ArrayList(swappedKey.keySet());
        Character key1 = (Character)keyset.get(index1);
        Character key2 = (Character)keyset.get(index2);
        Character temp1 = swappedKey.get(key1);
        Character temp2 = swappedKey.get(key2);
        swappedKey.put(key1, temp2);
        swappedKey.put(key2, temp1);
        SubstitutionCipher swappedCipher = new SubstitutionCipher(ciphertext, swappedKey);
        return swappedCipher;
    }

    /**
     * Returns the "score" for the "plain text" for this cipher.
     * The score for each individual quadgram is calculated by
     * the provided likelihoods object. The total score for the text is just
     * the sum of these scores.
     * @param likelihoods the object used to find a score for a quadgram
     * @return the score of the plain text as calculated by likelihoods
     */
    public double getScore(QuadGramLikelihoods likelihoods) {
        double score = 0.0;
        String plaintext = getPlainText();
        String temp = "";
        for (int i = 0; i < plaintext.length()-3; i++){
            temp = plaintext.substring(i, i+4);
            score += likelihoods.get(temp);
        }
        return score;
    }

    /**
     * Attempt to solve this substitution cipher through the hill
     * climbing algorithm. The SubstitutionCipher this is called from
     * should not be modified.
     * @param likelihoods the object used to find a score for a quadgram
     * @return a SubstitutionCipher with the same ciphertext and the optimal
     *  found through hill climbing
     */
/*
    public SubstitutionCipher getSolution(QuadGramLikelihoods likelihoods) {
        SubstitutionCipher C = new SubstitutionCipher(this.ciphertext, this.key);
        SubstitutionCipher M = null;
        int i = 0;
        double cScore = 0.0;
        double mScore = 0.0;
        boolean isReplaced = true;
        while (i < 1000 && isReplaced == true) {
            cScore = C.getScore(likelihoods);
            M = C.randomSwap();
            mScore = M.getScore(likelihoods);
            if (mScore > cScore){
                C = M;
            }
            else {isReplaced = false;}
            i += 1;
        }
        return C;
    }
*/


    public SubstitutionCipher getSolution(QuadGramLikelihoods likelihoods) {
        SubstitutionCipher C = new SubstitutionCipher(this.ciphertext, this.key);
        SubstitutionCipher M = null;
        int i = 0;
        double cScore = 0.0;
        double mScore = 0.0;
        while (i < 1000 ) {
            cScore = C.getScore(likelihoods);
            M = C.randomSwap();
            mScore = M.getScore(likelihoods);
            if (mScore > cScore){
                C = M;
            }
            i += 1;
        }
        return C;
    }


/*
    public static Map<Character, Character> genIdentityMap() {
        return Map.ofEntries(Map.entry('A', 'A'),
                Map.entry('B', 'B'),
                Map.entry('C', 'C'),
                Map.entry('D', 'D'),
                Map.entry('E', 'E'),
                Map.entry('F', 'F'),
                Map.entry('G', 'G'),
                Map.entry('H', 'H'),
                Map.entry('I', 'I'),
                Map.entry('J', 'J'),
                Map.entry('K', 'K'),
                Map.entry('L', 'L'),
                Map.entry('M', 'M'),
                Map.entry('N', 'N'),
                Map.entry('O', 'O'),
                Map.entry('P', 'P'),
                Map.entry('Q', 'Q'),
                Map.entry('R', 'R'),
                Map.entry('S', 'S'),
                Map.entry('T', 'T'),
                Map.entry('U', 'U'),
                Map.entry('V', 'V'),
                Map.entry('W', 'W'),
                Map.entry('X', 'X'),
                Map.entry('Y', 'Y'),
                Map.entry('Z', 'Z'));
    }

    public static void swapSubstitutionCipherRandom(int seed) {
        // Replace the static random in the class with new seeded rand
        RANDOM.setSeed(seed);
    }

    public static void main(String[] args) {
        QuadGramLikelihoods likelihoods = null;
        try {
            likelihoods = new QuadGramLikelihoods();
        }
        catch(Exception e) {
            System.out.println(e.getMessage());
        }

        String ciphertext = "ILBNUPQCNYBNBGIYGDYDZNZUPGIBGQZIBPGPTUNOYTPUQNPGZXNICZUIBPGBGACPVCZEEBGVXPILBGDYNBVGPTACPVCZENZGDDZIZNIPCZVYOYDBNUQNNZGDBEASYEYGITQGDZEYGIZSDZIZNICQUIQCYNZGDZSVPCBILENRBZZNYCBYNPTSZXNZGDACPFYUINOYOBSSVCZDYPGUPCCYUIGYNNZGDYTTBUBYGUJPTPQCACPVCZENILBNUPQCNYQNYNFZRZZNZGBEASYEYGIZIBPGSZGVQZVYXQIOYDPGPIYKAYUIZGJACBPCFZRZACPVCZEEBGVYKAYCBYGUY";
        System.out.println(ciphertext);
        SubstitutionCipher best = new SubstitutionCipher(ciphertext);
        //for (int i = 0; i < 20; i ++) {
            SubstitutionCipher cipher = best.getSolution(likelihoods);
            if (cipher.getScore(likelihoods) > best.getScore(likelihoods)) {
                best = cipher;
            }
       // }
        System.out.println(best.getPlainText());
    }
*/
}
