/*
 *	Checker Class
 *	By Jeremy Lennert
 *	February 8, 2001
 *	v. 1.1
 *
 *	Checker.cpp
 *
 *	Defines the methods for the class defined in Checker.h.
 */

#include <assert.h>
#include "Checker.h"
#include "FreqTable.h"
#include "Keys.h"

Checker::Checker(FreqTable *lnfreq)	// Creates a new Checker
{
	best.key = NULL;
	best.score = -1.7E-308;	// Most negative value a double can store
	lnf = lnfreq;
}

double Checker::check(unsigned char *plaintext, Keys *key)	// Runs standard test
{
	return thorough(plaintext, key);	// Until I write another test algorithm
}

double Checker::quick(unsigned char *plaintext, Keys *key)	// Runs fast tests to try to
{															// eliminate candidate easily
	return thorough(plaintext, key);	// Until I write another test algorithm
}

double Checker::thorough(unsigned char *plaintext, Keys *key)	// Runs a thorough test
{
	int bigrams[256][256];	// Observed bigram counts
	int count = 0;		// Length of text
	candid test;	// Holds data on key being tested
	Chain *data;	// Keeps track of data taken from hash table
	int x, y;	// Loop variables

	test.key = key;
	test.score = 0.0;
	for (x = 0; x < 256; x++)
	{
		for (y = 0; y < 256; y++)
		{
			bigrams[x][y] = 0;
		}
	}
	freq = new FreqTable();

	assert(plaintext[0] != '\0');
	for (count = 1; plaintext[count] != '\0'; count++)
	{
		bigrams[plaintext[count-1]][plaintext[count]]++;
	}

	for (x = 0; x < 256; x++)
	{
		for (y = 0; y < 256; y++)
		{
			freq->hash(x, y, ((bigrams[x][y] * 10000) / count) );
		}
	}

	while ( (data = freq->dump()) != NULL)
	{
		double zerocheck = lnf->find(data->unhashed) / 10000.0;
		if (zerocheck == 0) zerocheck = -9.2103;	// (natural log of .0001)
		test.score += data->value * zerocheck;
	}
	test.score += count * 5.545;	// N * ln(256)
	compare(test);

	delete freq;
	return test.score;
}

double Checker::score(unsigned char *plaintext, Keys *key)	// Gets a score, but doesn't
{													// remember key even if score is best so far
	int bigrams[256][256];	// Observed bigram counts
	int count;		// Length of text
	candid test;	// Holds data on key being tested
	Chain *data;	// Keeps track of data taken from hash table
	int x, y;	// Loop variables

	test.key = key;
	test.score = 0.0;
	for (x = 0; x < 256; x++)
	{
		for (y = 0; y < 256; y++)
		{
			bigrams[x][y] = 0;
		}
	}
	freq = new FreqTable();

	if (plaintext[0] != '\0')
	{
		for (count = 1; plaintext[count] != '\0'; count++)
		{
			bigrams[plaintext[count-1]][plaintext[count]]++;
		}
	}

	for (x = 0; x < 256; x++)
	{
		for (y = 0; y < 256; y++)
		{
			freq->hash(x, y, ((bigrams[x][y] * 10000) / count) );
		}
	}

	while ( (data = freq->dump()) != NULL)
	{
		double zerocheck = lnf->find(data->unhashed) / 10000.0;
		if (zerocheck == 0) zerocheck = -9.2103;	// (natural log of .0001)
		test.score += data->value * zerocheck;
	}
	test.score += count * 5.545;	// N * ln(256)

	delete freq;
	return test.score;
}

double Checker::coincidence(unsigned char *text)	// Runs an index of coincidence test
{
	int freq[256];
	int x, count;
	double result = 0;
	for (x = 0; x < 256; x++)
	{
		freq[x] = 0;
	}

	count = 0;
	while (text[count] != '\0')
	{
		freq[text[count]]++;
		count++;
	}

	if (count <= 1) return 1;	// Can't divide by zero (below)

	for (x = 0; x < 256; x++)
	{
		result += freq[x] * (freq[x] - 1);
	}
	result /= count * (count - 1);

	return result;
}

const candid Checker::Best()	// Reports the key and score of the best candidate so far
{
	return best;
}

void Checker::reset()	// Reset score and forget best key
{
	best.key = NULL;
	best.score = -1.7E-308;	// Most negative value a double can store
}

int Checker::compare(candid test)	// Checks if the latest score is better than the best,
{									// if so, it records the new best
	if (best.key == NULL)
	{
		best.key = new Keys(test.key);
		best.score = test.score;
		return true;
	}
	else if (test.score > best.score)
	{
		delete best.key;
		best.key = new Keys(test.key);
		best.score = test.score;
		return true;
	}
	else
	{
		return false;
	}
}