--[[
Misspelled utilizes a slightly Lua'ified version of the Hunspell Dictionary format.
Note: Not all Dictionary sets tags are supported.

Phonetic information has been appeneded, after the affix codes, to each base word
example: "hello/GMS/HL"
A phonetic lookup set "Phonetic" exists before the base words.

Here is some rather generic documentation on that format.
From: http://sourceforge.net/docman/display_doc.php?docid=29374&group_id=143754

TRY
sets the change characters for suggestions.

REP (Replace)
sets a replacement table for multiple character corrections in suggestion mode.

The following affix file example:. 'TRY' suggestions differ from the bad word with an
English letter or an apostrophe. With these REP definitions, Hunspell can suggest the
right word form, when the misspelled word contains f instead of ph and vice versa.


	TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ

	REP 2
	REP f ph
	REP ph f

	PFX A Y 1
	PFX A 0 re .

	SFX B Y 2
	SFX B 0 ed [^y]
	SFX B y ied y

There are two affix classes in the dictionary.
Class A defines a 're-' prefix. Class B defines two '-ed' suffixes.
First suffix can be added to a word if the last character of the word isn't 'y'.
Second suffix can be added to the words terminated with an 'y'. (See later.)

The following dictionary file uses these affix classes.

	3
	hello
	try/B
	work/AB

All accepted words with this dictionary: "hello", "try", "tried", "work", "worked", "rework", "reworked".


Affix Compression
--------------------------------------------------------------------------
An affix is either a  prefix or a suffix attached to root words to make
other words.  For example supply -> supplied by dropping the "y" and
adding an "ied" (the suffix).

Here is an example of how to define one specific suffix borrowed
from the en_US.aff file used by the OpenOffice org spellchecker

SFX D Y 4
SFX D   0     d          e
SFX D   y     ied        [^aeiou]y
SFX D   0     ed         [^ey]
SFX D   0     ed         [aeiou]y

This file is space delimited and case sensitive.
So this information can be interpreted as follows:

The first line has 4 fields:

Field
-----
1     SFX - indicates this is a suffix
2     D   - is the name of the character which represents this suffix
3     Y   - indicates it can be combined with prefixes (cross product)
4     4   - indicates that sequence of 4 affix entries are needed to
               properly store the affix information

The remaining lines describe the unique information for the 4 affix
entries that make up this affix.  Each line can be interpreted
as follows: (note fields 1 and 2 are used as a check against line 1 info)

Field
-----
1     SFX         - indicates this is a suffix
2     D           - is the name of the character which represents this affix
3     y           - the string of chars to strip off before adding affix
                         (a 0 here indicates the NULL string)
4     ied         - the string of affix characters to add
                         (a 0 here indicates the NULL string)
5     [^aeiou]y   - the conditions which must be met before the affix
                    can be applied

Field 5 is interesting.  Since this is a suffix, field 5 tells us that
there are 2 conditions that must be met.  The first condition is that
the next to the last character in the word must *NOT* be any of the
following "a", "e", "i", "o" or "u".  The second condition is that
the last character of the word must end in "y".

So how can we encode this information concisely and be able to
test for both conditions in a fast manner?  The answer is found
but studying the wonderful ispell code of Geoff Kuenning, et.al.
(now available under a normal BSD license).

If we set up a conds array of 256 bytes indexed (0 to 255) and access it
using a character (cast to an unsigned char) of a string, we have 8 bits
of information we can store about that character.  Specifically we
could use each bit to say if that character is allowed in any of the
last (or first for prefixes) 8 characters of the word.

Basically, each character at one end of the word (up to the number
of conditions) is used to index into the conds array and the resulting
value found there says whether the that character is valid for a
specific character position in the word.

For prefixes, it does this by setting bit 0 if that char is valid
in the first position, bit 1 if valid in the second position, and so on.

If a bit is not set, then that char is not valid for that postion in the
word.

If working with suffixes bit 0 is used for the character closest
to the front, bit 1 for the next character towards the end, ...,
with bit numconds-1 representing the last char at the end of the string.

Note: since entries in the conds[] are 8 bits, only 8 conditions
(read that only 8 character positions) can be examined at one
end of a word (the beginning for prefixes and the end for suffixes.

So to make this clearer, lets encode the conds array values for the
first two affentries for the suffix D described earlier.


  For the first affentry:
     numconds = 1             (only examine the last character)

     conds['e'] =  (1 << 0)   (the word must end in an E)
     all others are all 0

  For the second affentry:
     numconds = 2             (only examine the last two characters)

     conds[X] = conds[X] | (1 << 0)     (aeiou are not allowed)
         where X is all characters *but* a, e, i, o, or u


     conds['y'] = (1 << 1)     (the last char must be a y)
     all other bits for all other entries in the conds array are zero


--]]

if bit == nil then
	require("bit")
end

if string.utf8len == nil then
	require("UTF8\\utf8data")
	require("UTF8\\utf8")
end


--Local reference to Global functions for speed
local ipairs = ipairs
local pairs = pairs
local string_byte = string.byte
local string_utf8byte = string.utf8byte
local string_find = string.find
local string_gsub = string.gsub
local string_len = string.len
local string_utf8len = string.utf8len
local string_lower = string.lower
local string_utf8lower = string.utf8lower
local string_match = string.match
local string_rep = string.rep
local string_sub = string.sub
local string_utf8sub = string.utf8sub
local string_upper = string.upper
local string_utf8upper = string.utf8upper

local table_concat = table.concat
local table_insert = table.insert
local table_sort = table.sort

local bit_bor = bit.bor
local bit_lshift = bit.lshift
local bit_rshift = bit.rshift
local bit_band = bit.band
local bit_bnot = bit.bnot
local math_min = math.min

local utf8 = false

--WordDict_Dictionary = {}

local DICTIONARY_TO_LOAD_FROM

--split a string, at delimiter into a table
-- Compatibility: Lua-5.1
local split_t
local split_fpat
local split_last_end
local split_s, split_e, split_cap
local function split(str, pat)
   split_t = {}  -- NOTE: use {n = 0} in Lua-5.0
   split_fpat = "(.-)" .. pat
   split_last_end = 1
   split_s, split_e, split_cap = string_find(str, split_fpat, 1)
   while split_s do
      if split_s ~= 1 or split_cap ~= "" then
	 table_insert(split_t, split_cap)
      end
      split_last_end = split_e+1
      split_s, split_e, split_cap = string_find(str, split_fpat, split_last_end)
   end
	 if split_last_end <= #str then
		split_cap = string_sub(str, split_last_end)
		table_insert(split_t, split_cap)
	 end
   return split_t
end


--WordDict Class
WordDict = {}

WordDict = {
			locale = "",
			baseWords = {},
			replaceChars = {},
			prefixRules = {},
			suffixRules = {},
			phoneticRules= {},
			tryChars = {},
			lastContainsWord = "",
			possibleBaseWords = {},
			suggestions= {},
			soundslike = "Generic",
			maxSuggestions = 8,
			maxEditDistance = 4
			}

WordDict.Const = {
	SoundslikeAlgorithms = {GENERIC="Generic", PHONETIC="Phonetic"} --Enum for the type of sounds like algorithm used in this dictionary.
}

WordDict.AffixUtility = {}

--Takes aff file condition string and creates the condition array
--Rather similar to hunspell affixmgr.cpp AffixMgr::encodeit()
--http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/extensions/spellcheck/hunspell/src/affixmgr.cpp&q=affix encode condition&l=1004
function WordDict.AffixUtility:EncodeConditions(conditionText)
	local condition = {}
	local utf8Condition = {}
	utf8Condition["ascii"] = {}
	utf8Condition["all"] = {}
	utf8Condition["neg"] = {}
	utf8Condition["utf8Chars"] = {}

	local group = false  	--Group indicator
	local groupend = false	--end contition indicator
	local neg = false -- complement indicator
	local conditionCount = 0 -- number of conditions

	local memberChars = {}
	local numMember = 0		-- number of member in group

	local str_sub = string_sub
	local str_len = string_len

	if utf8 == true then
		str_len = string.utf8len
		str_sub = string.utf8sub
 	end

	--local BeginningOnly = false
	--local EndOnly = false

	local ReplaceMode = false


	--print("conditionText", conditionText)

	--Clear the condition array
	for j=0,255 do
		condition[j] = 0
	end

	--Clear the utf8Condition array
	for j=0, 127 do
		utf8Condition.ascii[j] = 0
	end

	--If no condition just return
	if conditionText == "." then
		conditionCount = 0
		return conditionCount, condition
	end

	for i = 1, string_len(conditionText) do
		local cond = str_sub(conditionText, i, i)
		--	print("i", i, "cond", cond)

		--start group indicator
		if cond == "[" then
			group = true

		--complement flag (negative group)
		elseif cond == "^" and group == true then
			neg = true

		--end group indicator
		elseif cond == "]" then
			endgroup = true  -- end of a group

		--add character of group to list
		elseif group == true then
			memberChars[numMember] = cond
			numMember = numMember + 1
		else
			endgroup = true --no group
		end

		--set condition
		if endgroup == true then
			if utf8 == false then
				if group == true then
					if neg == false then
						--turn on chars in member group
						--set the proper bits in the condition array vals for those chars
						for j = 0, numMember -1 do
							local charCode = string_byte(memberChars[j])
							condition[charCode] = bit_bor(condition[charCode], bit_lshift(1, conditionCount))
						end
					else
						--complement so set all of chars on, then uset indicated ones.
						for j=0,255 do
							condition[j] = bit_bor(condition[j], bit_lshift(1, conditionCount))
						end
						--turn off chars in member groupend
						for j = 0, numMember -1 do
							local charCode = string_byte(memberChars[j])
							condition[charCode] = bit_band(condition[charCode], bit_bnot(bit_lshift(1, conditionCount)))
						end
					end
					neg = false
					group = false
					numMember = 0
				else
					--not in a group, so just set the proper bit for this char
					--but first handle special case of . inside condition
					if cond == "." then
						--wild char character, turn all chars on
						for j=0, 255 do
							condition[j] = bit_bor(condition[j], bit_lshift(1, conditionCount))
						end
					else
						--turn on char
						local charCode = string_byte(cond)
						condition[charCode] = bit_bor(condition[charCode], bit_lshift(1, conditionCount))
					end
				end

			else	-- utf8 character set
				--Use two storage methods to store the conditions.
				--First, for simple one byte utf8 characters (ascii) use the storage method above
				--with the understanding tha we know an 1 byte ACSII utf char has to be less than 128,
				--and this requires fewer bits of storage
				--
				--For multi-byte utf8 condition chars, store, up to 8 of them, in and character array,
				--along with the flag information about negative condition groups and the all char
				--wildcard use
				--struct {
				--	char ascii[SETSIZE/2];
				--	char neg[8];
				--	char all[8];
				--	w_char * wchars[8];
				--	int wlen[8];
				--} utf8condition;
				--
				--use the utf8 safe version of byte


				if group == true then
					utf8Condition.neg[conditionCount] = neg
					--
					--First handel the Ascii one byte utf8 chars
					--
					if neg == false then
						--set the proper bits in the condition array vals for those chars
						for j=0, numMember -1 do
							local charCode = string_utf8byte(memberChars[j])
							if charCode > 0 and charCode < 128 then
								utf8Condition.ascii[charCode] = bit_bor(utf8Condition.ascii[charCode], bit_lshift(1, conditionCount))
							end
						end
					else --neg = true
						--complement so set all of chars on, then uset indicated ones.
						for j=0,127 do
							utf8Condition.ascii[j] = bit_bor(utf8Condition.ascii[j], bit_lshift(1, conditionCount))
						end

						--turn off chars in member groupend
						for j = 0, numMember -1 do
							local charCode = string_utf8byte(memberChars[j])
							if charCode < 128 then
								utf8Condition.ascii[charCode] = bit_band(utf8Condition.ascii[charCode], bit_bnot(bit_lshift(1, conditionCount)))
							end
						end
					end

					--Store utf8 multi-byte conditions
					for j = 0, numMember - 1 do
						if string_byte(memberChars[j]) >= 128 then
							if utf8Condition.utf8Chars[conditionCount] == nil then
								utf8Condition.utf8Chars[conditionCount] = ""
							end
							utf8Condition.utf8Chars[conditionCount] = utf8Condition.utf8Chars[conditionCount] .. memberChars[j]
						end
					end

					neg = false
					group = false
					numMember = 0

				else  --group == false
					local condCode = string_utf8byte(cond)
					--Is cond a utf8 multi-byte character
					if  condCode >= 128 then
							if utf8Condition.utf8Chars[conditionCount] == nil then
								utf8Condition.utf8Chars[conditionCount] = ""
							end
							utf8Condition.utf8Chars[conditionCount] = utf8Condition.utf8Chars[conditionCount] .. cond
					else
						--not a group so just set the proper bit for this char
						--by first handle special case of . inside condition
						if cond == "." then
							utf8Condition.all[conditionCount] = true
							for j = 0, 127 do
								utf8Condition.ascii[j] = bit_bor(utf8Condition.ascii[j], bit_lshift(1, conditionCount))
							end
						else
							utf8Condition.all[conditionCount] = false
							utf8Condition.ascii[condCode] = bit_bor(utf8Condition.ascii[condCode], bit_lshift(1, conditionCount))
						end
					end
					neg = false
				end
				conditionCount = conditionCount + 1
				endgroup = false
				neg = false
			end
		end
	end

	--Condition["conditionCount"] = conditionCount
	return conditionCount, condition, utf8Condition
end

--Removes the affix suffix rule entry for the word if valid
function WordDict.AffixUtility:RemoveSuffix(word, entry)
	local tempLength
	local tempWord
	local c
	local charCode
	local passCount

	if utf8 == false then
		tempLength = #word - #entry.AddChars
		if (tempLength > 0)
			and (tempLength + #entry.StripChars >= entry.ConditionCount)
			and (string_match(word, entry.AddChars .. "$") ~= nil) then  --word ends with suffix

			--word without affix
			tempWord = string_sub(word, 1, tempLength)
			--add back strip chars
			tempWord = tempWord .. entry.StripChars
			--check that this is valid
			passCount = 0
			for i = 0, entry.ConditionCount - 1 do
				charCode = string_byte(string_sub(tempWord, #tempWord - (entry.ConditionCount -i) + 1))

				if bit_band(entry.Conditions[charCode], bit_lshift(1, i)) == bit_lshift(1 , i) then
					passCount = passCount + 1
				end
			end
			if passCount == entry.ConditionCount then
				return tempWord
			end
		end
	else
		tempLength = string_utf8len(word) - string_utf8len(entry.AddChars)
 		if (tempLength > 0)
			and (tempLength + string_utf8len(entry.StripChars) >= entry.ConditionCount)
			and (string_match(word, entry.AddChars .. "$") ~= nil) then --word ends with suffix

			--word without suffix
			tempWord = string_utf8sub(word, 1, tempLength)
			--add back strip chars
			tempWord = tempWord .. entry.StripChars

			--check if tempWord is valid
			passCount = 0
			for i = 0, entry.ConditionCount - 1 do
				c = string_utf8sub(tempWord, string_utf8len(tempWord) - (entry.ConditionCount - i) + 1)
				charCode = string_utf8byte(c)

				--if this charCode is 1 byte, check the utf8Condition.Ascii table
				if charCode > 0 and charCode < 128 then
					if bit_band(entry.Utf8Conditions.ascii[charCode], bit_lshift(1, i)) == bit_lshift(1, i) then
						passCount = passCount + 1
					end
				else
					--this is a multi byte utf8 char
					--check if it's stored in the utf8 specific part of the rule.
					local all
					local neg
					local utf8Chars

					all = entry.Utf8Conditions.all[i]
					neg = entry.Utf8Conditions.neg[i]
					utf8Chars = entry.Utf8Conditions.utf8Chars[i]

					--check if this condition has any utf8 multibyte chars.
					if utf8Chars ~= nil then
						if all == true then	-- "." match all characters
							passCount = passCount + 1
						elseif neg == false then
							--check if this char is present
							if string_find(utf8Chars, c) ~= nil then
								passCount = passCount + 1
							end
						else  -- neg == true.  Make sure we can't find the char.
							if string_find(utf8Chars, c) == nil then
								passCount = passCount + 1
							end
						end
					end
				end
			end
			if passCount == entry.ConditionCount then
				return tempWord
			end
		end
	end
	return word
end

--Removes the affix prefix rule entry for the word if valid
function WordDict.AffixUtility:RemovePrefix(word, entry)
	local tempLength
	local tempWord
	local c
	local charCode
	local passCount
	if utf8 == false then
		tempLength = #word - #entry.AddChars
		if (tempLength > 0)
			and (tempLength + #entry.StripChars >= entry.ConditionCount)
			and (string_match(word, "^" .. entry.AddChars) ~= nil) then  --word starts with

			--word with out affix
			tempWord = string_sub(word, #entry.AddChars+1)
			--add back strip chars
			tempWord = entry.StripChars .. tempWord
			--check that this is valid
			passCount = 0
			for i = 0, entry.ConditionCount - 1 do
				charCode = string_byte(string_sub(tempWord, i+1, i+1))

				if bit_band(entry.Conditions[charCode], bit_lshift(1, i)) == bit_lshift(1 , i) then
					passCount = passCount + 1
				end
			end
			if passCount == entry.ConditionCount then
				return tempWord
			end
		end
	else
		tempLength = string_utf8len(word) - string_utf8len(entry.AddChars)
 		if (tempLength > 0)
 			and (tempLength + string_utf8len(entry.StripChars) >= entry.ConditionCount)
			and (string_match(word, "^" .. entry.AddChars) ~= nil) then --word starts with

			--word with out prefix
			tempWord = string_sub(word, string_utf8len(entry.AddChars) + 1)
			--add back strip chars
			tempWord = entry.StripChars .. tempWord

			--check if tempWord is valid
			passCount = 0
			for i = 0, entry.ConditionCount - 1 do
				c = string_utf8sub(tempWord, i+1, i+1)
				charCode = string_utf8byte(c)

				--if this charCode is 1 byte, check the utf8Condition.Ascii table
				if charCode > 0 and charCode < 128 then
					if bit_band(entry.Utf8Conditions.ascii[charCode], bit_lshift(1, i)) == bit_lshift(1, i) then
						passCount = passCount + 1
					end
				else
					--this is a multi byte utf8 char
					local all
					local neg
					local utf8Chars

					all = entry.Utf8Conditions.all[i]
					neg = entry.Utf8Conditions.neg[i]
					utf8Chars = entry.Utf8Conditions.utf8Chars[i]
					--check if this condition has any utf8 multibyte chars.
					if utf8Chars ~= nil then
						if all == true then	-- "." match all characters
							passCount = passCount + 1
						elseif neg == false then
							--check if this char is present
							if string_find(utf8Chars, c) ~= nil then
								passCount = passCount + 1
							end
						else
							if string_find(utf8Chars, c) == nil then
								passCount = passCount + 1
							end
						end
					end
				end
			end
			if passCount == entry.ConditionCount then
				return tempWord
			end
		end
	end
	return word
end

--Adds a suffix to a word
--Returns the word with the suffix added
function WordDict.AffixUtility:AddSuffix(word, rule)
	local passCount
	local charCode
	local tempLen
	local tempWord

	for i, entry in ipairs(rule.AffixEntries) do
		--check if this entry is valid
		if utf8 == false then
			if #word >= entry.ConditionCount then
				passCount = 0
				for i=0, entry.ConditionCount - 1 do
					charCode = string_byte(string_sub(word, #word - (entry.ConditionCount -i)+1,#word - (entry.ConditionCount -i)+1))

					if bit_band(entry.Conditions[charCode], bit_lshift(1, i)) == bit_lshift(1 , i) then
						passCount = passCount + 1
					else
						break
					end
				end
				if passCount == entry.ConditionCount then
					tempLen = #word - #entry.StripChars
					tempWord = string_sub(word, 1, tempLen)
					tempWord = tempWord .. entry.AddChars
					return tempWord
				end
			end
		else  --utf8
			if string_utf8len(word) >= entry.ConditionCount then
				passCount = 0
				for i = 0, entry.ConditionCount - 1 do
					--For each condition, we need to check utf8 characters at the end of the word
					local conditionPos = string_utf8len(word) - (entry.ConditionCount - i) + 1
					local c = string_utf8sub(word, conditionPos, conditionPos)
					charCode = string_utf8byte(c)

					--if this charCode is 1 byte, check the utf8Condition.Ascii table
					if charCode > 0 and charCode < 128 then
						if bit_band(entry.Utf8Conditions.ascii[charCode], bit_lshift(1, i)) == bit_lshift(1, i) then
							passCount = passCount + 1
						else
							break
						end
					else
						--this is a multi-byte utf8 character, check
						local all
						local neg
						local utf8Chars

						all = entry.Utf8Conditions.all[i]
						neg = entry.Utf8Conditions.neg[i]
						utf8Chars = entry.Utf8Conditions.utf8Chars[i]
						--check if this condition has any utf8 multibyte chars.
						if utf8Chars ~= nil then
							if all == true then	-- "." match all characters
								passCount = passCount + 1
							elseif neg == false then
								--check if this char is present
								if string_find(utf8Chars, c) ~= nil then
									passCount = passCount + 1
								else
									break
								end
							else	-- neg == true
								-- check complement
								if string_find(utf8Chars, c) == nil then
									passCount = passCount + 1
								else
									break
								end
							end
						end
					end
				end
				if passCount == entry.ConditionCount then
					tempLen = string_len(word) - #entry.StripChars
					tempWord = string_sub(word, 1 , tempLen)
					tempWord = tempWord .. entry.AddChars
					return tempWord
				end
			end
		end
	end
	return word
end

--Adds a prefix to a word
--Returns the word with the prefix added
function WordDict.AffixUtility:AddPrefix(word, rule)
	local passCount
	local charCode
	local tempLen
	local tempWord
	for i, entry in ipairs(rule.AffixEntries) do
		--check if this entry is valid
		if utf8 == false then
			if #word >= entry.ConditionCount then
				passCount = 0
				for i=0, entry.ConditionCount -1 do
					charCode = string_byte(string_sub(word, i+1, i+1))

					if bit_band(entry.Conditions[charCode], bit_lshift(1, i)) == bit_lshift(1 , i) then
						passCount = passCount + 1
					else
						break
					end
				end
				if passCount == entry.ConditionCount then
					tempWord = string_sub(word, #entry.StripChars)
					tempWord = entry.AddChars .. tempWord
					return tempWord
				end
			end
		else  --utf8
			if string_utf8len(word) >= entry.ConditionCount then
				passCount = 0
				for i = 0, entry.ConditionCount - 1 do
					--For each condition, we need to check utf8 characters at the end of the word
					local c = string_utf8sub(word, i + 1, i + 1)
					charCode = string_utf8byte(c)

					--if this charCode is 1 byte, check the utf8Condition.Ascii table
					if charCode > 0 and charCode < 128 then
						if bit_band(entry.Utf8Conditions.ascii[charCode], bit_lshift(1, i)) == bit_lshift(1, i) then
							passCount = passCount + 1
						else
							break
						end
					else
						--this is a multi-byte utf8 character, check
						local all
						local neg
						local utf8Chars

						all = entry.Utf8Conditions.all[i]
						neg = entry.Utf8Conditions.neg[i]
						utf8Chars = entry.Utf8Conditions.utf8Chars[i]

						--check if this condition has any utf8 multibyte chars.
						if utf8Chars ~= nil then
							if all == true then	-- "." match all characters
								passCount = passCount + 1
							elseif neg == false then
								--check if this char is present
								if string_find(utf8Chars, c) ~= nil then
									passCount = passCount + 1
								else
									break
								end
							else  -- neg == true
								-- check for the complement
								if string_find(utf8Chars, c) == nil then
									passCount = passCount + 1
								else
									break
								end
							end
						end
					end
				end
				if passCount == entry.ConditionCount then
					tempWord = string_sub(word, #entry.StripChars)
					tempWord = entry.AddChars .. tempWord
					return tempWord
				end

			end
		end
	end
	return word
end


function WordDict:LoadPrefixRules()
	local row = 1 --row pointer
	local rowCount = #DICTIONARY_TO_LOAD_FROM.Prefix

	local header
	local hparts
	local hpart1
	local hpart2
	local hpart3
	local rulecount
	local rule1
	local rule2
	local rule3
	local rule4
	local AllowCombine = false
	local entry
	local parsedEntry
	local condCount
	local conditions
	local utf8Conditions

	local p = DICTIONARY_TO_LOAD_FROM.Prefix

	while row < rowCount do
			header = p[row]

			-- headers are in 3 parts
			hparts = split(header, "%s+")
			hpart1 = hparts[1] -- Part 1 = affix key
			hpart2 = hparts[2] -- Part 2 = Allow combine flag
			hpart3 = hparts[3] -- Part 3 = rule count

 			--determin how many parts there are to this prefix
			rulecount = tonumber(hpart3 )
			--print("header", hpart1, hpart2, hpart3)

			--Add header parts 1 & 2 to PrefixRules collection
			self.prefixRules[hpart1] = {["Combine"]=hpart2, ["Rules"]={}}

			--Rules have 4 parts
			--Part1: affix(prefix/suffix) key
			--Part2: strip char
			--Part3: add chars
			--Part4: conditions

			AllowCombine = false
			if hpart2 == "Y" then AllowCombine = true end

			--Add header parts 1 & 2 to PrefixRules collection
			self.prefixRules[hpart1] = {["AllowCombine"]=AllowCombine, ["AffixEntries"]={}}

			for x = 1, rulecount do
				row = row + 1
				entry = p[row]
				parsedEntry = split(entry, "%s+")
				self.prefixRules[hpart1]["AffixEntries"][x] = {}
				--part 2 - strip char
				if parsedEntry[2] == "0" then
					self.prefixRules[hpart1]["AffixEntries"][x]["StripChars"] = ""
				else
				    self.prefixRules[hpart1]["AffixEntries"][x]["StripChars"] = parsedEntry[2]
				end
				--part 3 - add chars
				self.prefixRules[hpart1]["AffixEntries"][x]["AddChars"] = parsedEntry[3]
				--part 4 - conditions
				condCount = nil
				conditions = nil
				utf8Conditions = nil
				condCount, conditions, utf8Conditions = WordDict.AffixUtility:EncodeConditions(parsedEntry[4])
				self.prefixRules[hpart1]["AffixEntries"][x]["ConditionCount"] = condCount
				self.prefixRules[hpart1]["AffixEntries"][x]["Conditions"] = conditions
				self.prefixRules[hpart1]["AffixEntries"][x]["Utf8Conditions"] = utf8Conditions
			end
			row = row + 1
	end

end

function WordDict:LoadSuffixRules()
	local row = 1 --row pointer
	local rowCount = #DICTIONARY_TO_LOAD_FROM.Suffix

	local header
	local hparts
	local hpart1
	local hpart2
	local hpart3
	local rulecount
	local rule1
	local rule2
	local rule3
	local rule4
	local AllowCombine = false
	local entry
	local parsedEntry
	local condCount
	local conditions
	local utf8Conditions

	local s = DICTIONARY_TO_LOAD_FROM.Suffix

	while row < rowCount do
		header = s[row]
		-- headers are in 3 parts
		hparts = split(header, "%s+")
		hpart1 = hparts[1] -- Part 1 = affix key
		hpart2 = hparts[2] -- Part 2 = combine flag
		hpart3 = hparts[3] -- Part 3 = rule count

		--determin how many parts there are to this prefix
		rulecount = tonumber(hpart3)
		--print("Suffix Header", hpart1, hpart2, hpart3, "##", header, "##")


		AllowCombine = false
		if hpart2 == "Y" then AllowCombine = true end

		--Add header parts 1 & 2 to SuffixRules collection
		self.suffixRules[hpart1] = {["AllowCombine"]=AllowCombine, ["AffixEntries"]={}}

		--Rules have 4 parts
		--Part1: affix(prefix/suffix) key
		--Part2: strip char
		--Part3: add chars
		--Part4: conditions

		for x = 1, rulecount do
			row = row + 1
			entry = s[row]
			parsedEntry = split(entry, "%s+")

			--print("Suffix Entry:", entry, "##")
			--tprint(parsedEntry)

			self.suffixRules[hpart1]["AffixEntries"][x] = {}
			--part 2 - strip char
			if parsedEntry[2] == "0" then
				self.suffixRules[hpart1]["AffixEntries"][x]["StripChars"] = ""
			else
				self.suffixRules[hpart1]["AffixEntries"][x]["StripChars"] = parsedEntry[2]
			end
			--part 3 - add chars
			self.suffixRules[hpart1]["AffixEntries"][x]["AddChars"] = parsedEntry[3]
			--part 4 - conditions
			condCount = nil
			conditions = nil
			utf8Conditions = nil
			condCount, conditions, utf8Conditions = WordDict.AffixUtility:EncodeConditions(parsedEntry[4])
			self.suffixRules[hpart1]["AffixEntries"][x]["ConditionCount"] = condCount
			self.suffixRules[hpart1]["AffixEntries"][x]["Conditions"] = conditions
			self.suffixRules[hpart1]["AffixEntries"][x]["Utf8Conditions"] = utf8Conditions
		end
		row = row + 1
	end

end

WordDict.PhoneticUtility = {}

function WordDict.PhoneticUtility:EncodeRule(ruleText)

	local string_byte = string_byte
	local string_len = string_len
	local string_sub = string_sub

	if utf8 then
		string_byte = string_utf8byte
		string_len = string_utf8len
		string_sub = string_utf8sub
	end

	local group = false  	--Group indicator
	local groupend = false	--end contition indicator

	local memberChars = {}
	local numMember = 0		-- number of member in groupend

	local BeginningOnly = false
	local EndOnly = false
	local ConsumeCount = 0
	local ReplaceMode = false
	local Priority = 0
	local ConditionCount = 0
	local cond
	local charCode

	local Rule = {}
	Rule["Condition"] = {}
	Rule["ConsumeCount"] = 0

	--for j = 0, 255 do
	--	RuleCondition[j] = 0
	--end

	--print("ruletext", ruleText)

	for i = 1, string_len(ruleText) do
		cond = string_sub(ruleText, i, i)
		--print("i", i, "cond", cond)

		if cond == "(" then group = true
		elseif cond == ")" then endgroup = true
		elseif cond == "^" then
			BeginningOnly = true
			Rule["BeginningOnly"] = true
		elseif cond == "$" then
			EndOnly = true
			Rule["EndOnly"] = true
		elseif cond == "-" then
			ConsumeCount = ConsumeCount + 1
			Rule["ConsumeCount"] = ConsumeCount
		elseif cond == "<" then
			ReplaceMode = true
			Rule["ReplaceMode"] = true
		--local p = tonumber(cond)
		elseif tonumber(cond) ~= nil then
			Priority = tonumber(cond)
			Rule["Priority"] = Priority
		else
			if group == true then
				--add chars to group
				memberChars[numMember] = cond
				numMember = numMember + 1
			else
				endgroup = true
			end
		end
		if endgroup == true then
			if group == true then
				--turn on chars in member group
				for j = 0, numMember - 1 do
					charCode = string_byte(memberChars[j])
					Rule["Condition"][charCode] = bit_bor(Rule["Condition"][charCode] or 0, bit_lshift(1, ConditionCount))
				end
				group = false
				numMember = 0
			else
				--turn on char
				charCode = string_byte(cond)
				Rule["Condition"][charCode] = bit_bor(Rule["Condition"][charCode] or 0, bit_lshift(1, ConditionCount))
			end
			endgroup = false
			ConditionCount = ConditionCount + 1
			Rule["ConditionCount"] = ConditionCount
		end
	end
	return Rule
end

function WordDict:LoadPhoneticRules()
	local pr = DICTIONARY_TO_LOAD_FROM.Phonetic

	--Phonetic rules are in 2 parts
	--Part1: Encoded Rule
	--Part2: ReplaceString
	for i, v in ipairs(pr) do
		local parsedrule = split(v, "%s+")
		--self.phoneticRules[i]= {parsedrule[1], parsedrule[2]}
		self.phoneticRules[i] = WordDict.PhoneticUtility:EncodeRule(parsedrule[1])
		self.phoneticRules[i]["ReplaceString"] = parsedrule[2]

	end
end

function WordDict:LoadWords()
	local w = DICTIONARY_TO_LOAD_FROM.Words

	local baseWord
	local affixKeys
	local phoneticCode
	local parse = {}

	for i, v in ipairs(w) do
		-- split word input line into its parts
		--Part1: base word
		--Part2: affix keys
		--Part3: phonetic code
		--       Note: Some languanges don't have phonetic info,
		--		 and just use the Generic Phonetic method.
		parse = split(v, "/")
		baseWord = parse[1]
		if parse[2] ~= nil then
			affixKeys = parse[2]
		else
			affixKeys = ""
		end
		if parse[3] ~= nil then
			phoneticCode = parse[3]
		elseif self.soundslike == "Generic" then
			phoneticCode = self:GenericSoundsLike(baseWord)
		else
			phoneticCode = ""
		end


		--(r18) Don't store the affixKeys and phoneticCode in a sub table
		--Saves 6MB of memory.
		--self.baseWords[baseWord] = {affixKeys, phoneticCode}
		self.baseWords[baseWord] = affixKeys .."/" .. phoneticCode
	end
end

--Generates a phonetic code of how the word sounds based on phonetic
--ruless loaded from the dictionary
--Input: word (string)
--Return: a code of how the word sounds
function WordDict:PhoneticCode(word)
	local string_byte = string_byte
	local string_len = string_len
	local string_upper = string_upper
	local string_sub = string_sub

	if utf8 then
		string_byte = string_utf8byte
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_upper = string_utf8upper
	end

	local tempWord = string_upper(word)
	local prevWord = ""
	local code = ""

	local begining
	local ending
	local passCount
	local charCode
	local ruleCondition

	while #tempWord > 0 do
		--save previous word
		prevWord = tempWord

		for i,v in ipairs(self.phoneticRules) do
			begining = false
			ending = false
			if #tempWord == #word then
				begining = true
			end
			if v.ConditionCount == string_len(tempWord) then
				ending = true
			end

			if ((v.BeginningOnly == begining or not v.BeginningOnly)
				and (v.EndOnly == ending or not v.EndOnly)
				and (v.ConditionCount <= string_len(tempWord))) then

				passCount = 0
				-- loop through conditions
				for i = 1, v.ConditionCount do
					charCode = string_byte(string_sub(tempWord, i, i))
					ruleCondition = v.Condition[charCode] or 0

					if bit_band(ruleCondition, bit_lshift(1, i-1)) == bit_lshift(1, i-1) then
						passCount = passCount + 1 -- condition passed
					else
						break -- rule fails if on condition fails
					end
				end
				-- if all conditions passed
				if passCount == v.ConditionCount then
					if v.ReplaceMode == true then
						tempWord = v.ReplaceString .. string_sub(tempWord, v.ConditionCount - v.ConsumeCount + 1, -1)
					else
						if v.ReplaceString ~= "_" then
							code = code .. v.ReplaceString
						end
						tempWord = string_sub(tempWord, v.ConditionCount - v.ConsumeCount + 1)
					end
					break
				end
			end
		end

		-- if no match consume one char
		if #prevWord == #tempWord then
			tempWord = string_sub(tempWord, 2)
		end
	end
	return code
end

local AccentTransformations={
	["á"]="a",["à"]="a",["ã"]="a",["â"]="a",["å"]="a",["ä"]="a",
	["ç"]="c",
	["é"]="e",["è"]="e",["ë"]="e",["ê"]="e",
	["í"]="i",["ì"]="i",["ï"]="i",["î"]="i",
	["ñ"]="n",
	["ó"]="o",["ò"]="o",["õ"]="o",["ô"]="o",["ö"]="o",["ø"]="o",
	["ù"]="u",["û"]="u",["ú"]="u",["ü"]="u",
	["ÿ"]="y",["ý"]="y",
	["Á"]="A",["À"]="A",["Ã"]="A",["Â"]="A",["Å"]="A",["Ä"]="A",
	["Ç"]="C",
	["É"]="E",["È"]="E",["Ë"]="E",["Ê"]="E",
	["Í"]="I",["Ì"]="I",["Ï"]="I",["Î"]="I",
	["Ñ"]="N",
	["Ó"]="O",["Ò"]="O",["Õ"]="O",["Ô"]="O",["Ö"]="O",["Ø"]="0",
	["Ù"]="U",["Û"]="U",["Ú"]="U",["Ü"]="U",
	["Ý"]="Y",["Ÿ"]="Y",
	["Ð"]="D",
	["Œ"]="OE",
	["Þ"]="TH",
	["æ"]="AE",
	["ð"]="d",
	["œ"]="oe",
	["ß"]="ss",
	["þ"]="th"
}
local function removeAccents(text)
	return string_gsub(text, ".", AccentTransformations)
end

--Returns a generic sounds like code, for languanges that don't specify phonetic codes in their dirctionaries,
--and choose to use this "Generic" function in it's place.  Should work for Spanish and French.
--Remove all accents, vowels, and numbers.  Then uppercase the string, and return non repeating characters.
--Example: "herrmanóÞ" returns "HRMNTH"
--Hopefully this code doesn't need to worry about UTF8 because we remove all Accents
function WordDict:GenericSoundsLike(text)
	if text == nil then return "" end
	if #text == 0 then return "" end

	local s = string.upper(removeAccents(text))
	local sRet = ""
	local cPrev
	local cCurr

	s = string.gsub(s, "[AEIOUY0-9]", "")

	if #s >0 then
		cPrev = string.sub(s,1,1)
		sRet = cPrev

		for i = 1, #s do
			cCurr = string.sub(s,i,i)
			if cCurr ~= cPrev then
				sRet = sRet .. cCurr
				cPrev = cCurr
			end
		end
	end
	return sRet
end


--Searched all containted word lists for word
function WordDict:Contains(word)
	--save the word we're checking, so if someone calls Suggest(word)
	--without first calling Contains, we can, call Contains() to refresh the
	--possibleBaseWords list.
	self.lastContainsWord = word

	--clean up possible base word lists
	self.possibleBaseWords = {}

	--Step1: If word length is 1 char, return true
	if #word == 1 then
		return true
	end

	--Step2: search BaseWords
	if self.baseWords[word] ~= nil then
		return true
	end

	--Step3: Remove suffix, then search basewords
	-- save suffixed words for use when removing prefix
	local suffixWords = {}

	suffixWords[word] = 1

	--tprint(self.suffixRules)
	local tempWord
	local baseWordTemp
	local affixKeys
	for key, rule in pairs(self.suffixRules) do
		--print("suffix rule: ", key)
		for x, entry in ipairs(rule.AffixEntries) do
			--print("word/AffixEntry addchars", word, entry.AddChars)
			--tprint(entry)

			tempWord = WordDict.AffixUtility:RemoveSuffix(word, entry)
			if tempWord ~= word then
				baseWordTemp = self.baseWords[tempWord]
				if baseWordTemp ~= nil then
					affixKeys = string_sub(baseWordTemp, 1, (string_find(baseWordTemp, "/") -1))
					--make sure base word has this affix key
					if string_find(affixKeys, key) ~= nil then
						return true --word found
					end
				end

				if rule.AllowCombine == true then
					-- saving word to check if it is a word after prefix is removed
					suffixWords[tempWord] = 1
				else
					-- saving possible base words for use in generating suggestions
					self.possibleBaseWords[tempWord] = 1
				end
			end
		end
	end
	--save possible base words for use in generating suggestions
	for k, v in pairs(suffixWords) do
		self.possibleBaseWords[k] = 1
	end

	--Step4: Remove prefix, then search basewords
	for key, rule in pairs(self.prefixRules) do
		for x, entry in ipairs(rule.AffixEntries) do
			for suffixWord, y in pairs(suffixWords) do
				tempWord = WordDict.AffixUtility:RemovePrefix(suffixWord, entry)
				if tempWord ~= suffixWord then
					baseWordTemp = self.baseWords[tempWord]
					if baseWordTemp ~= nil then
						--make sure base word has this affix keys
						affixKeys = string_sub(baseWordTemp, 1, (string_find(baseWordTemp, "/") -1))

						if string_find(affixKeys, key) ~= nil then
							return true --word found
						end
					end

					--save possible base workd for use in generating suggestions
					self.possibleBaseWords[tempWord] = 1
				end
			end
		end
	end

	return false
end

--Expands an affix compressed base word
--takes in a base word entry with coresponsind affix & phonetic info
--returns a table of the expanded words
function WordDict:ExpandWord(word, affixKeys)
	local suffixWords = {}
	local words = {}

	local string_len = string_len
	local string_sub = string_sub

	if utf8 then
		string_len = string_utf8len
		string_sub = string_utf8sub
	end

	--add word to the suffixWords table
	suffixWords[word] = 1
	local prefixKeys = ""

	-- check suffix keys first
	local key
	local rule
	local tempWord
	for x=1, string_len(affixKeys) do
		key = string_sub(affixKeys, x, x)
		if WordDict.suffixRules[key] ~= nil then
			rule = WordDict.suffixRules[key]
			tempWord = WordDict.AffixUtility:AddSuffix(word, rule)
			if tempWord ~= word then
				if rule.AllowCombine == true then
					suffixWords[tempWord] = 1
				else
					words[tempWord] = 1
				end
			end
		elseif WordDict.prefixRules[key] ~= nil then
			prefixKeys = prefixKeys..key
		end
	end

	--apply prefixes
	for x = 1, string_len(prefixKeys) do
		key = string_sub(prefixKeys, x, x)
		rule = WordDict.prefixRules[key]
		for suffixWord,v in pairs(suffixWords) do
			tempWord = WordDict.AffixUtility:AddPrefix(suffixWord, rule)
			if tempWord ~= suffixWord then
				words[tempWord] = 1
			end
		end
	end

	for key,v in pairs(suffixWords) do
		words[key] = 1
	end
	return words
end

--Calculated the minimum number of changes, inserts of deletes
--required to change the firstWord into the secondWord
--PositionPriority: set to true if the first and last char should have priority
function WordDict:EditDistance(source, target)
	local string_len = string_len
	local string_lower = string_lower
	local string_sub = string_sub

	if utf8 == true then
		string_len = string_utf8len
		string_lower = string_utf8lower
		string_sub = string_utf8sub
	end

	local m = string_len(source)
	local n = string_len(target)

	if n == 0 then return m end
	if m == 0 then return n end

	if source == "teh" and target == "the" then return 0.1 end
	
	local rows = {}
	rows[0] = {[0]=0}

	for x=0, m+1 do
		rows[x] = {[0]=0}
		for y=0,n do
			rows[x][y] = 0
		end
	end

	--tprint(rows)

	for i=0,m do
		rows[i][0] = i
	end

	for j=0,n do
		rows[0][j] = j
	end

	--Ignore if the strings that only differ by case
	source = string_lower(source)
	target = string_lower(target)

	--tprint(rows)
	local subsitutionCost

	for i=1, m do
		for j=1,n do
			if string_sub(source, i, i) == string_sub(target, j, j) then
				subsitutionCost = 0
			else
				subsitutionCost = 1
			end

			rows[i][j] = math_min(	rows[i-1][j] + 1, 					--Insertion
									rows[i][j-1] + 1, 					--Deletion
									rows[i-1][j-1] + subsitutionCost)	--Substitution
		end
	end

	local dist = rows[m][n]

	-- extra edit on first and last chars if position Priority is important
	if string_sub(source, 1, 1) ~= string_sub(target, 1, 1) then
		dist = dist + 1
	end

	if string_sub(source, -1) ~= string_sub(target, -1) then
		dist = dist + 1
	end

	--potential additional
	-- If every character in sounce is in target, give it priority
	--and if every character in target is also in source, give it even more priority.
--~ 	public int EditDistance(string source, string target, bool positionPriority)
--~ 	{
--~ 	string testasciiSource = source.ToLower();
--~ 	string testasciiTarget = target.ToLower();
--~ 	int dist = -1;

--~ 	// If every character in source is also in target, give it priority

--~ 	bool subset = false;
--~		if (Math.Abs(testasciiTarget.Length - testasciiSource.Length) <= 1)
--~		{
--~			subset = true;
--~ 		for (int i = 0; i < testasciiSource.Length; i++)
--~ 		if (testasciiTarget.IndexOf(testasciiSource[i]) == -1)
--~ 		{
--~ 			subset = false;
--~ 			break;
--~ 		}
--~		}

--~ 	// If every character in target is also in source, give it even more priority

--~ 	if (subset)
--~ 	{
--~ 	dist--;
--~ 	for (int i = 0; i < testasciiTarget.Length; i++)
--~ 	if (testasciiSource.IndexOf(testasciiTarget[i]) == -1)
--~ 	{
--~ 	dist++;
--~ 	break;
--~ 	}
--~ 	}

--~ 	if (!subset)
--~ 	{
--~ 	// i.e. 2-D array
--~ 	Array matrix = Array.CreateInstance(typeof(int), source.Length+1, target.Length+1);

--~ 	// boundary conditions
--~ 	matrix.SetValue(0, 0, 0);

--~ 	for(int j=1; j <= target.Length; j++)
--~ 	{
--~ 	// boundary conditions
--~ 	int val = (int)matrix.GetValue(0,j-1);
--~ 	matrix.SetValue(val+1, 0, j);
--~ 	}

--~ 	// outer loop
--~ 	for(int i=1; i <= source.Length; i++)
--~ 	{
--~ 	// boundary conditions
--~ 	int val = (int)matrix.GetValue(i-1, 0);
--~ 	matrix.SetValue(val+1, i, 0);

--~ 	// inner loop
--~ 	for(int j=1; j <= target.Length; j++)
--~ 	{
--~ 	int diag = (int)matrix.GetValue(i-1, j-1);

--~ 	if(source.Substring(i-1, 1) != target.Substring(j-1, 1))
--~ 	{
--~ 	diag++;
--~ 	}

--~ 	int deletion = (int)matrix.GetValue(i-1, j);
--~ 	int insertion = (int)matrix.GetValue(i, j-1);
--~ 	int match = Math.Min(deletion+1, insertion+1);
--~ 	matrix.SetValue(Math.Min(diag, match), i, j);
--~ 	}//for j
--~ 	}//for i

--~ 	dist = (int)matrix.GetValue(source.Length, target.Length);
--~ 	}

--~ 	// extra edit on first and last chars
--~ 	if (positionPriority)
--~ 	{
--~ 	if (source[0] != target[0]) dist++;
--~ 	if (source[source.Length-1] != target[target.Length-1]) dist++;
--~ 	}
--~ 	return dist;
--~ 	}

	--tprint(rows)

	return dist
end



--returns true if word or lower(word) are in baseWords
function WordDict:TestWord(word)
	if WordDict:Contains(word) == true then
		return true
	end

	--check the lower case version of the word, if it contains a capitol
	local w
	if utf8 == false then
		w = string_lower(word)
		if w ~= word then
			if WordDict:Contains(w) == true  then
				return true
			end
		end
	else
		w = string_utf8lower(word)
		if w ~= word then
			if WordDict:Contains(w) == true  then
				return true
			end
		end
	end

	return false
end


function WordDict:FirstCapital(currentWord)
	local suggestions = {}
	local suggestionsCount = 0

	local string_upper = string_upper
	local string_sub = string_sub

	if utf8 == true then
		string_upper = string_utf8upper
		string_sub = string_utf8sub
	end

	local testWord = string_upper(string_sub(currentWord, 1, 1)) .. string_lower(string_sub(currentWord, 2))

	if self:TestWord(testWord) == true then
		suggestions[testWord] = self:EditDistance(currentWord, testWord)
		suggestionsCount = 1
	end

	return suggestions, suggestionsCount
end

--suggestions for a typical fault of spelling, that
--differs with more, than 1 letter from the right form.
function WordDict:ReplaceChars(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0

	local string_lower = string_lower
	--local string_sub = string_sub

	if utf8 then
		string_lower = string_utf8lower
		--string_sub = string_utf8sub
	end

	--Test each set of replacement (key, replacement) pairs.
	for i = 1, #self.replaceChars do
		local key
		local replacement
		local testWord = CurrentWord
		local keyMatches = - 1
		local pos1, pos2
		local atEnd = false

		key, replacement = unpack(split(self.replaceChars[i], "%s+"))

		pos1, pos2 = string_find(CurrentWord, key, 1, true)
		if pos2 == #CurrentWord then
			atEnd = true
		end
		while pos1 ~= nil do
			testWord = string_sub(testWord, 1, pos1 -1) .. replacement .. string_sub(testWord, pos2 + 1)

			if self:TestWord(testWord) == true then
				suggestions[string_lower(testWord)] = self:EditDistance(CurrentWord, testWord)
				suggestionsCount = suggestionsCount + 1
			end

			if atEnd == true then
				break
			end

			pos1, pos2 = string_find(CurrentWord, key, pos1 + 1, true)
			if pos2 == #testWord then
				atEnd = true
			end
		end
	end

	return suggestions, suggestionsCount
end


--swap out each char one by one and try all the tryme chars
--in its place to see if that makes a good word
function WordDict:BadChar(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0

	local tempWord = {}
	local testWord
	local saveChar
	local tryChar
	local string_len = string_len
	local string_sub = string_sub
	local string_lower = string_lower

	if utf8 then
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_lower = string.utf8lower
	end

	for i=1, string_len(CurrentWord) do
		tempWord[i] = string_sub(CurrentWord, i, i)
	end

	for i=1, string_len(CurrentWord) do
		saveChar = tempWord[i]

		for x=1, string_len(self.tryChars) do
			tryChar = string_sub(self.tryChars, x, x)
			if tempWord[i] ~= tryChar then
				tempWord[i] = tryChar

				testWord = table_concat(tempWord)

				if self:TestWord(testWord) == true then
					suggestions[string_lower(testWord)] = self:EditDistance(CurrentWord, testWord)
					suggestionsCount = suggestionsCount + 1
				end
			end
		end

		tempWord[i] = saveChar
	end

	return suggestions, suggestionsCount
end

--try omitting one char of word at a time
function WordDict:ExtraChar(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0
	local tempWord

	local string_len = string_len
	local string_sub = string_sub
	local string_lower = string_lower

	if utf8 == true then
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_lower = string_utf8lower
	end

	if string_len(CurrentWord) > 1 then
		for i=1, string_len(CurrentWord) do
			tempWord = string_sub(CurrentWord, 0, i-1) .. string_sub(CurrentWord, i+1)

			if self:TestWord(tempWord) == true then
				suggestions[string_lower(tempWord)] = self:EditDistance(CurrentWord, tempWord)
				suggestionsCount = suggestionsCount + 1
			end
		end
	end

	return suggestions, suggestionsCount
end


--try inserting a tryme character before every letter
function WordDict:ForgotChar(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0
	local tempWord
	local tryme = self.tryChars

	local string_len = string_len
	local string_sub = string_sub
	local string_lower = string_lower

	if utf8 == true then
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_lower = string_utf8lower
	end

	for i=1, string_len(CurrentWord) do
		for x=1, string_len(tryme) do
			tempWord = string_sub(CurrentWord, 0, i) .. string_sub(tryme, x, x) .. string_sub(CurrentWord, i+1)
			if self:TestWord(tempWord) == true then
				suggestions[string_lower(tempWord)] = self:EditDistance(CurrentWord, tempWord)
				local suggestionsCount = suggestionsCount + 1
			end
		end
	end

	return suggestions, suggestionsCount
end

--try swapping adjacent chars one by one
function WordDict:SwapChar(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0
	local tempWord = {}
	local swap
	local testWord

	local string_len = string_len
	local string_sub = string_sub
	local string_lowr = string_lower

	if utf8 == true then
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_lower = string_utf8lower
	end

	for i=1, string_len(CurrentWord) - 1 do
		tempWord = {}
		for x=1, string_len(CurrentWord) do
			tempWord[x] = string_sub(CurrentWord, x, x)
		end

		swap = tempWord[i]
		tempWord[i] = tempWord[i+1]
		tempWord[i+1] = swap

		testWord = table_concat(tempWord)

		if self:TestWord(testWord) == true then
			suggestions[string_lower(testWord)] = self:EditDistance(CurrentWord, testWord)
			suggestionsCount = suggestionsCount + 1
		end
	end

	return suggestions, suggestionsCount
end


--split the string into two pieces after every char
--if both pieces are good words make them a suggestion
function WordDict:TwoWords(CurrentWord)
	local suggestions = {}
	local suggestionsCount = 0

	local string_len = string_len
	local string_sub = string_sub
	local string_lower = string_lower

	if utf8 == true then
		string_len = string_utf8len
		string_sub = string_utf8sub
		string_lower = string_utf8lower
	end

	if #CurrentWord < 3 then
		return suggestions, suggestionsCount
	end

	local firstWord
	local secondWord
	local tempWord

	for i=1, string_len(CurrentWord) -2 do
		firstWord = string_sub(CurrentWord, 1, i)
		secondWord = string_sub(CurrentWord, i+1)

		if self:TestWord(firstWord) == true then
			if self:TestWord(secondWord) == true then
				tempWord = firstWord .. " " .. secondWord
				suggestions[tempWord] = self:EditDistance(CurrentWord, tempWord) + 0.5
				suggestionsCount = suggestionsCount + 1
			end
		end
	end

	return suggestions, suggestionsCount
end


function WordDict:Suggest(currentWord)
	--Reload the possibleBaseWords if Suggest() isn't being called right after Contains()
	if self.lastContainsWord ~= currentWord then
		WordDict:Contains(currentWord)
	end

	local tempSugCount = 0

	tempSuggestion = {}

	-- generate phonetic code for possible root word
	local codes = {}
	local tempCode

	for tempWord, v in pairs(WordDict.possibleBaseWords) do
		if tempWord ~= nil and #tempWord ~= 1 then
			if self.soundslike == WordDict.Const.SoundslikeAlgorithms.PHONETIC then
				tempCode = WordDict:PhoneticCode(tempWord)
			elseif self.soundslike == WordDict.Const.SoundslikeAlgorithms.GENERIC then
				tempCode = WordDict:GenericSoundsLike(tempWord)
			else
				tempCode = ""
			end
			if #tempCode > 0 and codes[tempCode] == nil then
				codes[tempCode] = 1
			end
		end
	end

	local codeCount = 0
	for k,v in pairs(codes) do
		codeCount = codeCount + 1
	end

	local words = {}
	local editDistance
	local affixKeys
	local pCode

	if codeCount > 0 then
		--search root words for phonetic codes
		for word, v in pairs(WordDict.baseWords) do
			--check if this word's phonetic code matches a phonetic stored in codes
--~ 		affixKeys = string_sub(v, 1, (string_find(v, "/") -1))
--~ 		pCode = string_sub(v, (string_find(v, "/") + 1))
			--New version of above
			affixKeys, pCode = string_match(v, "(.*)/(.*)")
			if codes[pCode] ~= nil then
				words = {}
				words = WordDict:ExpandWord(word, affixKeys)
				--add expanded words
				for expandedWord,v in pairs(words) do
					editDistance = WordDict:EditDistance(currentWord, expandedWord)
					tempSuggestion[expandedWord] = editDistance
					tempSugCount = tempSugCount + 1
				end
			end
		end
	end

	--Obtain additional suggestions

	--check if the first letter should have been capitalized
	--print("Call: FirstCapital()", tempSugCount, "Mem:", collectgarbage("count"))
	for sWord, editDistance in pairs(self:FirstCapital(currentWord)) do
		tempSuggestion[sWord] = editDistance
		tempSugCount = tempSugCount + 1
	end

	--check if a char was typed in place of a replacementChar
	--print("Call: ReplaceChars()", tempSugCount, "Mem:", collectgarbage("count"))
	for sWord, editDistance in pairs(self:ReplaceChars(currentWord)) do
		tempSuggestion[sWord] = editDistance
		tempSugCount = tempSugCount + 1
	end

	--swap out each char on by one and try all the tryme
	--chars in its place to see if that makes a good word
	--print("Call: BadChar()", "", tempSugCount, "Mem:", collectgarbage("count"))
	if utf8 == false then
		for sWord, editDistance in pairs(self:BadChar(currentWord)) do
			tempSuggestion[sWord] = editDistance
			tempSugCount = tempSugCount + 1
		end
	end

	--try swapping adjcent chars on by one
	--print("Call: SwapChar()", tempSugCount, "Mem:", collectgarbage("count"))
	for sWord, editDistance in pairs(self:SwapChar(currentWord)) do
		tempSuggestion[sWord] = editDistance
		tempSugCount = tempSugCount + 1
	end

	--try inserting a tryme character before every letter
	--print("Call: ForgotChar()", tempSugCount, "Mem:", collectgarbage("count"))
	if utf8 == false then
		for sWord, editDistance in pairs(self:ForgotChar(currentWord)) do
			tempSuggestion[sWord] = editDistance
			tempSugCount = tempSugCount + 1
		end
	end

	--try ommitting one char of word at a time
	--print("Call: ExtraChar()", tempSugCount, "Mem:", collectgarbage("count"))
	for sWord, editDistance in pairs(self:ExtraChar(currentWord)) do
		tempSuggestion[sWord] = editDistance
		tempSugCount = tempSugCount + 1
	end

	--split the string into two pieces after every char
	--if both pieces are good words mak them a suggestions
	--print("Call: TwoWords()", tempSugCount, "Mem:", collectgarbage("count"))
	for sWord, editDistance in pairs(self:TwoWords(currentWord)) do
		tempSuggestion[sWord] = editDistance
		tempSugCount = tempSugCount + 1
	end


	--Sort the associative table holding suggestions, by EditDistance.
	local sorted = {}
	for w, d in pairs(tempSuggestion) do
		table_insert(sorted, {Word = w, EditDistance = d})
	end
	table_sort(sorted, function(A, B) return A.EditDistance < B.EditDistance end)

	--print("Sorted Table:")
	--tprint(sorted)

	WordDict.suggestions = {}


	for i, v in ipairs(sorted) do
		if v.EditDistance < self.maxEditDistance then
			table_insert(WordDict.suggestions, {Word = v.Word, EditDistance = v.EditDistance})

			if #self.suggestions >= self.maxSuggestions then
				break
			end
		end
	end

	return self.suggestions
end

function WordDict:Init(locale)

	if locale == nil then
		self.locale = GetLocale()
	else
		self.locale = locale
	end

	--Load the dictionary

	--If we don't have a languange specific dictionary, load the US English dictionary
	if self.locale == "ruRU" then
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_ruRU()
		--utf8 = true
	elseif self.locale == "itIT" then		
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_itIT()
		--utf8 = true
	elseif self.locale == "enGB" then		
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_enGB()
		--utf8 = true
	elseif self.locale == "deDE" then
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_deDE()
		--utf8 = false
	elseif self.locale == "esES" then
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_esES()
		--utf8 = true
	elseif self.locale == "frFR" then
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_frFR()
		--utf8 = true
	elseif self.locale == "enUS" then
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_enUS()
		--utf8 = false
	else
		DICTIONARY_TO_LOAD_FROM = WordDict_GetDictionary_enUS()
		--utf8 = false
	end

	--set the dictionary functions to nil to free memory
	WordDict_GetDictionary_ruRU = nil
	WordDict_GetDictionary_itIT = nil
	WordDict_GetDictionary_enGB = nil
	WordDict_GetDictionary_deDE = nil
	WordDict_GetDictionary_esES = nil
	WordDict_GetDictionary_frFR = nil
	WordDict_GetDictionary_enUS = nil

	--Set the UTF8 flag
	utf8 = DICTIONARY_TO_LOAD_FROM.UTF8

	--Load the tryChars
	self.tryChars = DICTIONARY_TO_LOAD_FROM.Try[1]

	--Load the replaceChars
	for i, v in ipairs(DICTIONARY_TO_LOAD_FROM.Replace) do
		table_insert(self.replaceChars, v)
	end

	--Load Affix Rules
	self:LoadPrefixRules()
	self:LoadSuffixRules()

	--Some languanges just use the generic sounds like routine, while most have a phonetic code defined for each baseword.
	self.soundslike = DICTIONARY_TO_LOAD_FROM.Soundslike
	if self.soundslike == "Phonetic" then
		--Load Phonetic Rules
		self:LoadPhoneticRules()
	end

	--Load the Base Words
	self:LoadWords()

	--Free the table we loaded the dictionary from
	DICTIONARY_TO_LOAD_FROM = nil

	--Collect Garbage to free up memory
	collectgarbage("collect")

	return self.locale
end

--[[
--testing

function tprint (t, indent, done)
  -- in case we run it standalone
  local Note = Note or print
  local Tell = Tell or io.write

  -- show strings differently to distinguish them from numbers
  local function show (val)
    if type (val) == "string" then
      return '"' .. val .. '"'
    else
      return tostring (val)
    end -- if
  end -- show
  -- entry point here
  done = done or {}
  indent = indent or 1
  for key, value in pairs (t) do
    Tell(string_rep (" ", indent)) -- indent it
    if type (value) == "table" and not done [value] then
      done [value] = true
      Note (show (key), ":");
      tprint (value, indent + 2, done)
    else
      Tell (show (key), "=")
      print (show (value))
    end
  end
end

function GetLocale() return "frFR" end
--function GetLocale() return "deDE" end
--function GetLocale() return "esES" end
--function GetLocale() return "enUS" end



--require("bit")

require("Dict\\Dic_" .. GetLocale())

WordDict:Init()

function test1()
	print("UTF8", utf8)

	--testword = "applepie"
	--testword = "hermonóo"
	--testword = "helpin"
	testword = "procrea"
	testword = "contraórdenes"
	testword = "holla"
	testword = "hóla"
	testword = "Bahammas"
	testword = "mehrstelling"
	testword = "abbürtsen"
	testword = "angstrœm"
	testword = "d'angstrœm"
	testword = "angstrœms"
	testword = "qu'a"
	print("Contains")
	print("Testing: " ..testword, WordDict:Contains(testword), "Len", string_utf8len(testword))
    tprint(WordDict:Suggest(testword))

	--s, sc = WordDict:ReplaceChars("holla")
	--tprint(s)

	tprint(WordDict:ExpandWord("a", "Q"))

	print("Phonetic Code:", WordDict:PhoneticCode("angstrœm"))
--	print(WordDict.AffixUtility:RemovePrefix("procrea", WordDict.prefixRules["o"]))
end


test1()

--~ print(string.utf8byte("Đ"))

--~ cnt,cc,ccc = WordDict.AffixUtility:EncodeConditions("[^eioĐ]ño")
--~ print("Condition Count:", cnt)
--~ tprint(ccc, 4)



--tprint(WordDict:Suggest(testword))


--~ print("len:", #"hermanó")
--~ print(WordDict:Contains("hermanos"))
--~ print(WordDict:Contains("hermonóo"))
--~ tprint(WordDict:Suggest("hermonó0"))

--print("Memory:", collectgarbage("count"))
--Timmer now = os.clock(); test(); print(os.clock() - now)
---]]
