#include <iostream>
#include <fstream>
#include <cstring>
#include <openssl/evp.h>

using namespace std;

// This is the same as what was given, just added the key string as a parameter
// so I could manipulate it in here instead.
int do_crypt(FILE *in, FILE *out, string inkey, int do_encrypt)
{
    /* Allow enough space in output buffer for additional block */
    unsigned char inbuf[1024], outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
    int inlen, outlen;
    EVP_CIPHER_CTX *ctx;
    /*
     * Bogus key and IV: we'd normally set these from
     * another source.
    */
    
    // hardcoding the IV because we already know what it is.
    unsigned char iv[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    
    // I'm adding the spaces in here.
    for(int i = inkey.length(); i < 16; ++i)
    {
        inkey += ' ';
    }

    // Converting the input key into an unsigned char array.
    unsigned char key[17];
    strcpy((char*) key, inkey.c_str());
    
    /* Don't set key or IV right away; we want to check lengths */
    ctx = EVP_CIPHER_CTX_new();
    EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL, do_encrypt);
    OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
    OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
    /* Now we can set key and IV */
    EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);
    for (;;) {
        inlen = fread(inbuf, 1, 1024, in);
        if (inlen <= 0)
            break;
        if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
            /* Error */
            EVP_CIPHER_CTX_free(ctx);
            return 0;
        }
        fwrite(outbuf, 1, outlen, out);
    }
    if (!EVP_CipherFinal_ex(ctx, outbuf, &outlen)) {
        /* Error */
        EVP_CIPHER_CTX_free(ctx);

        // I added this just in case to make sure thigns cleaned up properly.
        EVP_CIPHER_CTX_cleanup(ctx);
        return 0;
    }

    fwrite(outbuf, 1, outlen, out);

    EVP_CIPHER_CTX_free(ctx);
    EVP_CIPHER_CTX_cleanup(ctx);
    return 1;
}

int main()
{
    // Initialize files.
    FILE *input;
    FILE *output;

    // Open the needed files.
    input = fopen("ciphertext.txt", "rb");
    output = fopen("output.txt", "wb");

    // I prefer ifstreams when dealing with files,
    // but I leave the other two as file pointers
    // so I don't have to change more code.
    ifstream words("words.txt");
    ifstream original("plaintext.txt");

    // Just an initial key. It will be overwritten.
    string key = "0123456789abcdef";
    
    // Variable to storing the goal string.
    string secret = "";
    getline(original, secret);

    // Variable for storing output when we read
    // from the file that we wrote to.
    string templine = "";

    while(!words.eof())
    {
        // Get the current key to test with.
        key = "";
        getline(words, key);

        // Only test it if it's less that 16 characters.
        if(key.length() > 16)
        {
            continue;
        }

        // Try to decrypt with this key.
        do_crypt(input, output, key, 0);

        // Set the file pointers back to the beginning
        // of the files.
        rewind(input);
        rewind(output);

        // Read the file and see if it matches the original
        // message, if it does, then we guessed the key.
        fclose(output);
        
        ifstream tempoutput("output.txt");

        getline(tempoutput, templine);

        tempoutput.close();

        if(templine.compare("This is a top secret.") == 0)
        {
            // Output the key and the output text
            // to prove we know the key and we
            // successfully decrypted the message.
            cout << key << endl;
            cout << templine << endl;
            break;
        }
        else
        {
            // We didn't decrypt it, try again,
            // overwrite whatever was in the file.
            output = fopen("output.txt", "wb");
        }

    }

    fclose(input);
    words.close();
    original.close();

    return 0;
}
