/* Lecture code 14.1
 *
 * A sample implementation of Lexicon that uses copy-on-write to optimize
 * copy behavior.  This code requires the SharedPtr class from Lecture
 * Code 14.0 to be in a file called sharedptr.h.
 */

#include <iostream>
#include <string>
#include <iterator>
#include <set>
#include <stdexcept>
#include <algorithm>
#include <cctype>
#include <fstream>
#include "sharedptr.h"
using namespace std;

/* A version of the CS106B/X 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.  The file
	 * is assumed to exist; the Lexicon is empty otherwise.
	 */
	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;

	/* Allows you to iterate over the contents of the Lexicon. */
	typedef set<string>::const_iterator const_iterator;
	const_iterator begin() const { return allWords->begin(); }
	const_iterator end() const { return allWords->end(); }

	/* Adds a word to the Lexicon. */
	void addWord(const string& word);
private:
	/* Pointer to the set that actually contains the words.  Note that this is
	 * a SharedPtr to automate reference management.
	 */
	SharedPtr<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(), ::tolower);
	return result;
}

/* Constructor just copies the strings out of the file using copy
 * and some istream_iterators.
 */
Lexicon::Lexicon(const string& filename): allWords(new set<string>)
{
	ifstream input(filename.c_str()); // Assume file exists... would normally do error-checking.
	allWords->insert(istream_iterator<string>(input), istream_iterator<string>());
}

/* containsWord just checks if the word is in the set. */
bool Lexicon::containsWord(const string& word) const
{
	return allWords->find(ConvertToLowerCase(word)) != allWords->end();
}

/* Checking whether a prefix exists is a bit subtle.  We use
 * the lower_bound function to get an iterator to the first word that
 * compares lexicographically no less than the prefix.  If the element
 * has the given prefix, the prefix exists; otherwise it does not.
 */
bool Lexicon::containsPrefix(const string& prefix) const
{
	const string lowerPrefix = ConvertToLowerCase(prefix);

	/* Get an iterator to the first element no less than the prefix. */
	set<string>::const_iterator itr = allWords->lower_bound(lowerPrefix);

	/* If we get the end iterator, this prefix comes after everything in
	 * the lexicon.
	 */
	if(itr == allWords->end()) return false;

	/* Otherwise, check how the word compares to the word we found. */
	return itr->compare(0, lowerPrefix.size(), lowerPrefix) == 0;
}

/* addWord ensures that no other SharedPtrs reference this resource, then
 * adds the word to the word set.
 */
void Lexicon::addWord(const string& word)
{
	/* If some other Lexicon shares this implementation, the share count
	 * will be something other than one.
	 */
	if(GetShareCount(allWords) != 1)
		Reset(allWords, new set<string>(*allWords)); // Point to a new copy of the word list.
	allWords->insert(word);
}

int main()
{
	Lexicon lex("dictionary.txt");
	cout << "Words loaded." << endl;
	cout << lex.containsWord("hokey") << endl;
	cout << lex.containsPrefix("hax") << endl;

	Lexicon lex2 = lex;
	lex.addWord("haxxor");

	cout << lex2.containsWord("hokey") << endl;
	cout << lex2.containsPrefix("hax") << endl;
	return 0;
}
