/* Lecture code 14.1
 *
 * Lexicon class with support for copy-on-write.
 */

#include <iostream>
#include <string>
#include <iterator>
#include <set>
#include <stdexcept>
#include <algorithm>
#include <cctype>
#include <fstream>
#include "sharedptr.h" // Should contain the SharedPointer implementation from 14.0.
using namespace std;

/* A version of the CS106X Lexicon that's layered on top of an STL set.
 * Not nearly as efficient as the real Lexicon (which is massively
 * optimized), but pretty good nonetheless.
 */
class Lexicon
{
public:
	/* Constructor loads in all of the words from a file, throwing
	 * a runtime_error exception if it can't find the file.
	 */
	explicit Lexicon(const string& filename);

	/* Returns whether a word is contained in the Lexicon. */
	bool containsWord(const string& word) const;

	/* Returns whether a word is a prefix in the Lexicon. */
	bool containsPrefix(const string& prefix) const;

	/* Adds a word to the Lexicon. */
	void addWord(const string& word);

	/* Allows you to iterate over the contents of the Lexicon. */
	typedef set<string>::const_iterator const_iterator;
	const_iterator begin() const;
	const_iterator end() const;
private:
	SharedPointer<set<string> > allWords;
};

/* Helper function to convert a word to lower case. */
string ConvertToLowerCase(const string& str)
{
	string result = str;
	transform(result.begin(), result.end(), result.begin(), (int(*)(int))tolower);
	return result;
}

Lexicon::Lexicon(const string& filename) : allWords(new set<string>)
{
	/* Try opening file, fail if we cannot. */
	ifstream input(filename.c_str());
	if(input.fail())
		throw runtime_error("Can't open the file: " + filename);

	/* Load words. */
	allWords->insert(istream_iterator<string>(input), istream_iterator<string>());
}

/* Contains check converts to lower case and looks up. */
bool Lexicon::containsWord(const string& word) const
{
	return allWords->find(ConvertToLowerCase(word)) != allWords->end();
}

/* Contains prefix check works by incrementing the last letter of the word and seeing if the
 * span of words in the range is empty.  Because the list of words is sorted alphabetically,
 * we can check if a word starts with a given prefix by finding the first word lexicographically
 * greater than that prefix and the first word lexicographically greater than the next prefix.
 * If they aren't coincident, there must be some work with that prefix.
 */
bool Lexicon::containsPrefix(const string& prefix) const
{
	/* If the prefix is empty, then it's valid unless we have no words. */
	if(prefix.empty()) return allWords->empty();

	/* Convert to lower case. */
	const string lowerCase = ConvertToLowerCase(prefix);

	/* Increment last letter. */
	string nextLetter = lowerCase;
	nextLetter[nextLetter.length() - 1]++;

	/* See if we got anything! */
	return allWords->lower_bound(lowerCase) != allWords->lower_bound(nextLetter);
}

/* Adds a new word, but first does the copy-on-write check. */
void Lexicon::addWord(const string& word)
{
	/* Ensure uniqueness. */
	if(allWords.getSharedCount() != 1)
		allWords.reset(new set<string>(*allWords));

	/* Add the word. */
	allWords->insert(word);
}