/**
 * Base class for all ciphers. Stores the text and provides shared helpers
 * for character manipulation used by all subclasses. encrypt() and decrypt()
 * act as identity functions (no transformation) by default.
 */
public abstract class Cipher {

    /** Text to encrypt or decrypt. */
    private String text;

    /**
     * Constructs a Cipher, cleaning the input text before storing it.
     *
     * @param text the raw input text
     */
    public Cipher(String text) {
        this.text = cleanText(text);
    }

    /**
     * Returns the text stored in this cipher.
     *
     * @return the cleaned text
     */
    public String getText() {
        return text;
    }

    /**
     * Encrypt the text (method does not change it, subclasses will override this method)
     *
     * @return the encrypted text
     */
    public abstract String encrypt();

    /**
     * Decrypt the text (method does not change it, subclasses will override this method)
     *
     * @return the decrypted text
     */
    public abstract String decrypt();

    /**
     * Strips non-alphabetic characters and converts to uppercase.
     *
     * @param text the raw input text
     * @return the cleaned text
     */
    protected String cleanText(String text) {
		//StringBuilder is a version of String that is built for
		//concatenation (and is much faster at concatenating)
        StringBuilder sb = new StringBuilder();
		//we'll see for-each loops in ~2 lectures
        for (char c : text.toCharArray()) {
            if (Character.isLetter(c)) {
                sb.append(Character.toUpperCase(c));
            }
        }
        return sb.toString();
    }

    /**
     * Converts an uppercase letter to its 0-based index (A=0, B=1, ..., Z=25).
     *
     * @param c an uppercase letter
     * @return the index of the letter
     */
    protected int charToIndex(char c) {
        return c - 'A';
    }

    /**
     * Converts a 0-based index to its corresponding uppercase letter.
     *
     * @param i an index in [0, 25]
     * @return the corresponding uppercase letter
     */
    protected char indexToChar(int i) {
        return (char) ('A' + i);
    }


    /**
     * Encrypts the cipher's text, prints the result, then decrypts the
     * encrypted text and prints that result.
     *
     * @param c     the cipher to demonstrate
     * @param label a description to print before the results
     */
    private static void demonstrateCipher(Cipher c, String label) {
        System.out.println(label);
        String encrypted = c.encrypt();
        System.out.println("  Encrypted: " + encrypted);
        c.text = encrypted;
        System.out.println("  Decrypted: " + c.decrypt());
        System.out.println();
    }

    /**
     * Encrypts a sample message with Caesar, Vigenere, and substitution ciphers,
     * then decrypts each and prints the results.
     *
     * @param args command-line arguments (unused)
     */
    public static void main(String[] args) {
        String message = "The quick brown fox jumps over the lazy dog";
        System.out.println("Message:  " + message);
        System.out.println();

        demonstrateCipher(new CaesarCipher(message, 13), "Caesar (shift=13)");
        demonstrateCipher(new VigenereCipher(message, "KEYWORD"), "Vigenere (key=KEYWORD)");
        demonstrateCipher(new SubstitutionCipher(message, "QWERTYUIOPASDFGHJKLZXCVBNM"), "Substitution (key=QWERTYUIOPASDFGHJKLZXCVBNM)");


        //(at end of class) practice when abstract:
        //Cipher c = new VigenereCipher("abs", "abs");  //compiles!  VigenereCipher is concrete
        //Cipher c2 = new Cipher("abc");  //does not compile!  Cipher is abstract
    }

}
