note
	description:
		"Implementation for a GUI lookup interface which has direct access on EV_APPLICATION"
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision$"

class
	GUI_IMP

inherit
	GUI_I

feature -- Access

	application_under_test: EV_APPLICATION
			-- Application under test
		do
			Result := environment.application
		end

	screen: EV_SCREEN
			-- Screen
		once
			create Result
		end

	active_window: EV_WINDOW
			-- Active window

feature -- Element change

	set_active_window (a_window: like active_window)
			-- Set `active_window' to `a_window'.
		do
			active_window := a_window
		end

feature -- Window lookup

	window_by_identifier (an_identifier: READABLE_STRING_32): EV_WINDOW
			-- Window which has `an_identifier' as `identifier_name'.
		do
			Result ?= window ([an_identifier, Void])
		end

	windows_by_identifier (an_identifier: READABLE_STRING_32): LIST [EV_WINDOW]
			-- All windows which have `an_identifier' as `identifier_name'.
		do
			-- TODO: check if this cast is valid
			Result ?= windows ([an_identifier, Void])
		end

feature -- Identifiable lookup

	identifiable (a_pattern: READABLE_STRING_32): EV_IDENTIFIABLE
			-- Identifiable corresponding to `a_pattern'.
		local
--			l_tokens: LIST [READABLE_STRING_32]
--			l_token: READABLE_STRING_32
--			l_indirect: BOOLEAN

			l_list: LIST [EV_IDENTIFIABLE]
		do
			l_list := identifiables (a_pattern)
			if not l_list.is_empty then
				Result := l_list.first
			end

-- This algorithm is flawed in the case that it is greedy. As soon as it finds a path it will follow it
-- and stay on the path even if the end result will not be found this way. For example a search for:
-- 'a.b' will follow the first identifiable with name 'a' even if it does not have a child with name 'b'
-- and there exist a second identifiable with name 'a' which has a child with name 'b'.
-- Thus the current solution by searching for all and returning the first result is not the optimal
-- way but at least correct.
--	juliant, 22. November 2006

				-- check window syntax
--			l_tokens := a_pattern.split (':')
--			if l_tokens.count = 1 then
--				Result := active_window
--			elseif l_tokens.count = 2 then
--				Result := window (parse_info_token (l_tokens.first))
--			else
--				check
--					invalid_pattern: false
--				end
--			end
--				-- parse pattern
--			from
--				l_tokens := l_tokens.last.split ('.')
--				l_tokens.start
--				l_indirect := true
--			until
--				l_tokens.after or Result = Void
--			loop
--				l_token := l_tokens.item
--				if l_token.is_empty then
--						-- we have a double dot, hence the next child is a direct or indirect parent
--					check
--						indirect_not_set: not l_indirect -- three dots if l_indirect is alredy set and thus an invalid pattern
--					end
--					l_indirect := true
--				else
--						-- look for child
--					if l_indirect then
--						Result := identifiable_child_recursive (Result, parse_info_token (l_token))
--					else
--						Result := identifiable_child (Result, parse_info_token (l_token))
--					end
--					l_indirect := false
--				end
--				l_tokens.forth
--			end
		end

	identifiables (a_pattern: READABLE_STRING_32): LIST [EV_IDENTIFIABLE]
			-- All identifiablkes corresponding to `a_pattern.
		local
			l_active_elements: LIST [EV_IDENTIFIABLE]
			l_tokens: LIST [READABLE_STRING_32]
			l_token: READABLE_STRING_32
			l_token_info: TUPLE [name, type: READABLE_STRING_32]
			l_indirect: BOOLEAN
		do
				-- check window syntax
			l_tokens := a_pattern.split (':')
			if l_tokens.count = 1 then
				create {LINKED_LIST [EV_IDENTIFIABLE]}l_active_elements.make
				l_active_elements.extend (active_window)
			elseif l_tokens.count = 2 then
				l_active_elements := windows (parse_info_token (l_tokens.first))
			else
				check
					invalid_pattern: false	-- raise exception
				end
			end
				-- parse pattern
			from
				l_tokens := l_tokens.last.split ('.')
				l_tokens.start
				l_indirect := true
			until
				l_tokens.after or l_active_elements.count = 0
			loop
				l_token := l_tokens.item
				if l_token.is_empty then
						-- we have a double dot, hence the next child is a direct or indirect parent
					check
						indirect_not_set: not l_indirect -- three dots if l_indirect is alredy set and thus an invalid pattern
					end
					l_indirect := true
				else
						-- look for children of all active elements
					create {LINKED_LIST [EV_IDENTIFIABLE]}Result.make
					from
						l_token_info := parse_info_token (l_token)
						l_active_elements.start
					until
						l_active_elements.after
					loop
							-- Lookup next token with active elements
						if l_indirect then
							Result.append (identifiable_children_recursive (l_active_elements.item, l_token_info))
						else
							Result.append (identifiable_children (l_active_elements.item, l_token_info))
						end
						l_active_elements.forth
					end
					l_active_elements := Result
				end
				l_tokens.forth
			end
		end

feature {NONE} -- Implementation

	environment: EV_ENVIRONMENT
			-- Vision2 environment
		once
			create Result
		end

	children (an_identifiable: EV_IDENTIFIABLE): LINEAR [EV_IDENTIFIABLE]
			-- Linear representation of all direct children of `an_identifiable'.
		require
			an_identifiable_not_void: an_identifiable /= Void
		local
			l_window: EV_WINDOW
			l_container: EV_CONTAINER
			l_item_list: EV_ITEM_LIST [EV_ITEM]
			l_list: LINKED_LIST [EV_IDENTIFIABLE]
			l_linear: LINEAR [EV_IDENTIFIABLE]
		do
			l_window ?= an_identifiable
			if l_window = Void then
				l_container ?= an_identifiable
				if l_container = Void then
					l_item_list ?= an_identifiable
					if l_item_list = Void then
						create {LINKED_LIST [EV_IDENTIFIABLE]}Result.make
					else
						Result := l_item_list.linear_representation
					end
				else
					Result := l_container.linear_representation
				end
			else
				create l_list.make
				if l_window.menu_bar /= Void then
					l_list.extend (l_window.menu_bar)
				end
				l_linear := l_window.linear_representation
				l_linear.do_all (agent l_list.extend (?))
				Result := l_list
			end
		ensure
			result_not_void: Result /= Void
		end

	parse_info_token (a_token: READABLE_STRING_32): TUPLE [name, type: READABLE_STRING_32]
			-- Parse `a_token'.
		require
			a_token_not_void: a_token /= Void
			a_token_not_empty: not a_token.is_empty
		local
			l_brace_index: INTEGER
		do
			create Result
			l_brace_index := a_token.index_of ('}', 1)
			if l_brace_index > 0 then
				Result.type := a_token.substring (2, l_brace_index - 1)
				if l_brace_index /= a_token.count then
					Result.name := a_token.substring (l_brace_index+1, a_token.count)
				else
					Result.name := Void
				end
			else
				Result.name := a_token
				Result.type := Void
			end
		ensure
			result_not_void: Result /= Void
			result_has_name_or_type: Result.name /= Void or Result.type /= Void
		end

	matches_info (an_identifiable: EV_IDENTIFIABLE; an_info: TUPLE [name, type: READABLE_STRING_32]): BOOLEAN
			-- Does `an_identifiable' match `an_info's name and type?
		require
			an_identifiable_not_void: an_identifiable /= Void
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		do
			-- TODO: regexp match of name
			if an_info.name /= Void and an_info.type /= Void then
				Result := an_identifiable.identifier_name.is_equal (an_info.name) and an_identifiable.generating_type.out.same_string_general (an_info.type)
			elseif an_info.name /= Void then
				Result := an_identifiable.identifier_name.is_equal (an_info.name)
			else
				check
					has_type: an_info.type /= Void
				end
				Result := an_identifiable.generating_type.out.same_string_general (an_info.type)
			end
		end

	window (an_info: TUPLE [name, type: READABLE_STRING_32]): EV_IDENTIFIABLE
			-- Window which has `a_name' and `a_type'
		require
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_windows: LIST [EV_IDENTIFIABLE]
		do
			l_windows := windows (an_info)
			if not l_windows.is_empty then
				Result := l_windows.first
			end
		end

	windows (an_info: TUPLE [name, type: READABLE_STRING_32]): LIST [EV_IDENTIFIABLE]
			-- Windows which have `a_name' and `a_type'
		require
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_windows: LINEAR [EV_WINDOW]
			l_window: EV_WINDOW
		do
			l_windows := application_under_test.windows
			from
				create {LINKED_LIST [EV_IDENTIFIABLE]}Result.make
				l_windows.start
			until
				l_windows.after
			loop
				l_window := l_windows.item
				if matches_info (l_window, an_info) then
					Result.extend (l_window)
				end
				l_windows.forth
			end
		ensure
			result_not_void: Result /= Void
		end

	identifiable_child (a_parent: EV_IDENTIFIABLE; an_info: TUPLE [name, type: READABLE_STRING_32]): EV_IDENTIFIABLE
			-- Child identifiable of `a_parent' which match `an_info's name and type
		require
			a_parent_not_void: a_parent /= Void
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_children: LINEAR [EV_IDENTIFIABLE]
			l_child: EV_IDENTIFIABLE
		do
			from
				l_children := children (a_parent)
				l_children.start
			until
				l_children.after or Result /= Void
			loop
				l_child := l_children.item
				if matches_info (l_child, an_info) then
					Result := l_child
				end
				l_children.forth
			end
		end

	identifiable_children (a_parent: EV_IDENTIFIABLE; an_info: TUPLE [name, type: READABLE_STRING_32]): LIST [EV_IDENTIFIABLE]
			-- All child identifiables of `a_parent' which match `an_info's name and type
		require
			a_parent_not_void: a_parent /= Void
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_children: LINEAR [EV_IDENTIFIABLE]
			l_child: EV_IDENTIFIABLE
		do
			from
				l_children := children (a_parent)
				l_children.start
				create {LINKED_LIST [EV_IDENTIFIABLE]}Result.make
			until
				l_children.after
			loop
				l_child := l_children.item
				if matches_info (l_child, an_info) then
					Result.extend (l_child)
				end
				l_children.forth
			end
		ensure
			result_not_void: Result /= Void
		end

	identifiable_child_recursive (a_parent: EV_IDENTIFIABLE; an_info: TUPLE [name, type: READABLE_STRING_32]): EV_IDENTIFIABLE
			-- Identifiable matching `an_info's name and type recursive searched starting at `a_parent'.
		require
			a_parent_not_void: a_parent /= Void
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_queue: QUEUE [EV_IDENTIFIABLE]
			l_item: EV_IDENTIFIABLE
		do
			from
				create {LINKED_QUEUE [EV_IDENTIFIABLE]}l_queue.make
				children (a_parent).do_all (agent l_queue.extend (?))
			until
				l_queue.is_empty or Result /= Void
			loop
				l_item := l_queue.item
				l_queue.remove
				if matches_info (l_item, an_info) then
					Result := l_item
				else
					children (l_item).do_all (agent l_queue.extend (?))
				end
			end
		end

	identifiable_children_recursive (a_parent: EV_IDENTIFIABLE; an_info: TUPLE [name, type: READABLE_STRING_32]): LIST [EV_IDENTIFIABLE]
			-- All identifiables matching `an_info's name and type recursive searched starting at `a_parent'.
		require
			a_parent_not_void: a_parent /= Void
			an_info_not_void: an_info /= Void
			a_name_or_a_type_set: an_info.name /= Void or an_info.type /= Void
			an_info_name_not_empty: an_info.name /= Void implies not an_info.name.is_empty
			an_info_type_not_empty: an_info.type /= Void implies not an_info.type.is_empty
		local
			l_queue: QUEUE [EV_IDENTIFIABLE]
			l_item: EV_IDENTIFIABLE
		do
			from
				create {LINKED_LIST [EV_IDENTIFIABLE]}Result.make
				create {LINKED_QUEUE [EV_IDENTIFIABLE]}l_queue.make
				children (a_parent).do_all (agent l_queue.extend (?))
			until
				l_queue.is_empty
			loop
				l_item := l_queue.item
				l_queue.remove

				if matches_info (l_item, an_info) then
					Result.extend (l_item)
				else
					children (l_item).do_all (agent l_queue.extend (?))
				end
			end
		ensure
			result_not_void: Result /= Void
		end

note
	copyright:	"Copyright (c) 1984-2023, Eiffel Software"
	license:	"GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)"
	source: "[
			Eiffel Software
			5949 Hollister Ave., Goleta, CA 93117 USA
			Telephone 805-685-1006, Fax 805-685-6869
			Website http://www.eiffel.com
			Customer support http://support.eiffel.com
		]"
	licensing_options: "http://www.eiffel.com/licensing"
	copying: "[
			This file is part of Eiffel Software's Eiffel Development Environment.
			
			Eiffel Software's Eiffel Development Environment is free
			software; you can redistribute it and/or modify it under
			the terms of the GNU General Public License as published
			by the Free Software Foundation, version 2 of the License
			(available at the URL listed under "license" above).
			
			Eiffel Software's Eiffel Development Environment is
			distributed in the hope that it will be useful, but
			WITHOUT ANY WARRANTY; without even the implied warranty
			of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
			See the GNU General Public License for more details.
			
			You should have received a copy of the GNU General Public
			License along with Eiffel Software's Eiffel Development
			Environment; if not, write to the Free Software Foundation,
			Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
		]"

end