require "Vortaro"
require "Vorto"

# The parser takes a single word and finds all of the possible esperanto words
# that it could be.
#
# This could be more efficient.  The parsing process uses recursion, and
# produces trees that need to be pruned and compressed.  While this is elegant
# logic, it is less efficient than other, more straight-forward, logic, and
# could in any case be optimized.
module EO
	class Parser
		VORTARO = Vortaro.new "esperant.eng"

		# Parses a word, returning all possible matches
		# For example, "amuzado" can be "amuz-ad-o", "am-uz-ad-o",
		# "am-uz-a-do", or "amuz-a-do".  The last two are not, technically, legal
		# esperanto words, and these sorts of matches can be filtered with either
		# filtered_parse() or filter().
		def Parser::parse(word)
			#puts "Parsing #{word}"
			original = word.clone	# In case we don't have any matches
			word.downcase!
			# Disassemble word into component parts
			parts = parts( word )
			# Compress does the same thing.
			# The array we get back from parts is complex, containing arrays of
			# arrays, which describe all of the possible legal permutations of
			# the word.  flatten() simplifies this array, removing sterile
			# branches.
			parts = flatten( parts )
			#parts = compress( parts )
			# If the flattened array is empty, then there were no complete
			# permutations of the various parts.  In that case, we assume that
			# the word is a proper name.
			if parts.nil?
				vorto = Vorto.new
				vorto << Radiko.new( original, "S", "S", original )
				rv = [ vorto ]
				return rv
			end
			# The flattened array still contains a tree, so compress will turn
			# the tree into real words.
			parts = compress( parts )
			# The next section goes through what we have left and turns them into
			# Vortoj, objects which represent the words.
			rv = []
			if parts.nil?
				rv << Vorto.new
				rv[-1] << Radiko.new( original, "S", "S", original )
				return rv
			end
			parts.each { |option|
				rv << Vorto.new
				option.each { |radiko| rv[-1] << radiko }
				rv[-1].fixate!	# tell the word we're done adding to it.
			}
			yield rv if block_given?	# Give the caller a chance to do something
			rv
		end

		# Parses the word, and then filters the results before returning them.
		# This is the same as calling filter( parse( word ) )
		def Parser::filtered_parse word
			filter( parse word )
		end

		# Filters "illegal" words from the array.  Illegal words are words that
		# don't have a legal suffix (n, v, adj, adv, etc).  Combinations such as
		# "amuz-a-do" are considered illegal.  This method relies on
		# Vorto.valid() for the validation check; word validity is set by
		# Vorto.fixate!, which is called by parse() (so you don't have to worry
		# about it).  This method only filters out invalid permutations if there
		# is at least one valid permatation in the array.
		def Parser::filter array
			return [] if array.size == 0
			array.sort! { |x,y| y.validity <=> x.validity }
			max = array[0].validity
			array.delete_if {|item| item.validity < max  }
		end

		private
		# Pretty prints an array
		def Parser::write_string x
			return "NIL" if x.nil?
			return x.to_s unless x.is_a? Array
			rv = "[ "
			max = x.size-1
			x.each_index { |c| 
				rv << write_string(x[c]) 
				rv << ", " unless c == max
			}
			rv << " ]"
			rv
		end

		# Breaks a word up into its component esperanto morphemes
		def Parser::parts word
			return [] if word.length == 0
			matches = VORTARO.matches( word )
			#puts "MATCHES = #{write_string matches}"
			return nil if matches.nil?
			arry = []
			for match in matches
				sub = [ match ]
				sub << parts(word[match.length..-1]) unless word == match
				arry << sub
			end
			arry
		end

		# Simplifies the array.  Turns [ "a", [ "b", [ "c" ] ] ] into
		# [ "a", "b", "c" ], and removing some sterile branches
		def Parser::flatten array
			return nil if array.nil?
			return array unless array.is_a? Array
			return flatten(array[0]) if array.length == 1
			tmp = []
			array.each { |x|
				tmp << flatten(x)
			}
			if !tmp[0].is_a? Array and tmp[1].is_a? Array
				if !tmp[1][0].is_a? Array
					rv = [tmp[0]]
					rv += tmp[1]
					return rv 
				end
			end
			tmp
		end

		# Somewhat inaccurately named.  Compress flattens the subarrays,
		# building permutations out of the array content.  Therefore,
		# [ "a", [ "b", "c" ] ] becomes [ ["a", "b"], [ "a", "c" ] ].  This
		# simplifies building words later.  Also, this method removes incomplete
		# permutations.  When "part"ing the word, it is possible to get parts
		# which aren't part of the original word; for example, "domo" could be
		# parted down to "do-mo", because "do" is an EO radical, but "mo" is not.
		# What we get in that case is [ "do", nil ].  This is a dead branch, and
		# can be pruned.  compress() does this as well.
		def Parser::compress array
			rv = []
			string = []

			for item in array
				if item.is_a? Array
					subitems = compress item if item.is_a? Array
					subitems.each { |substring| 
						rv << string+substring unless substring.nil?
					} unless subitems.nil?
				else
					return nil if item.nil?
					string << item
				end
			end
			rv.unshift string if string.length > 0 and rv.length == 0
			rv
		end
	end
end
