note
	description: "[
		When a SD_FLOATING_TOOL_BAR_ZONE is resizing by user.
		SD_TOOL_BAR_GROUP_DIVIDER will calculate best grouping.
		It will get total minmum size.
	]"
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision$"

class
	SD_TOOL_BAR_GROUP_DIVIDER

create
	make_with_content

feature {NONE} -- Initlization

	make_with_content (a_content: SD_TOOL_BAR_CONTENT)
			-- Make with a_content
		require
			not_void: a_content /= Void
		local
			l_group_count: INTEGER
		do
			l_group_count := a_content.groups_count (False)
			content := a_content
			create algorithm.make (l_group_count, init_group_width (a_content.items_visible))
			create internal_refined_grouping.make
			init_grouping_infos
		ensure
			set: content = a_content
		end

	init_group_width (a_items: LIST [SD_TOOL_BAR_ITEM]): ARRAYED_LIST [INTEGER]
			-- Initlizatie a list with each items width in it
		local
			l_temp_group: ARRAYED_LIST [SD_TOOL_BAR_ITEM]
		do
			create Result.make (1)
			from
				create l_temp_group.make (1)
				a_items.start
			until
				a_items.after
			loop
				if attached {SD_TOOL_BAR_SEPARATOR} a_items.item then
					if l_temp_group.count > 0 then
						Result.extend (group_width (l_temp_group))
						create l_temp_group.make (1)
					end
				else
					l_temp_group.extend (a_items.item)
				end
				a_items.forth
			end
			if l_temp_group.count > 0 then
				Result.extend (group_width (l_temp_group))
			end
		ensure
			group_count_right: Result.count = content.groups_count (False)
		end

	init_grouping_infos
			-- Initlialize grouping infos
		local
			l_count: INTEGER
			l_max_group_count: INTEGER
			l_refined_grouping: like internal_refined_grouping
		do
			create grouping_algorithm.make (10)
			from
				l_count := 1
				l_max_group_count := content.item_count_except_sep (False)
				create group_infos.make (1)
			until
				l_count > l_max_group_count
			loop
				group_items (l_count)
				l_refined_grouping := internal_refined_grouping
				check l_refined_grouping /= Void then end -- Implied by postcondition of `group_items'
				check not_has: not group_infos.has_item (l_refined_grouping) end
				group_infos.put (l_refined_grouping, l_count)
				debug ("docking")
					print ("%N SD_TOOL_BAR_GROUP_DIVIDER init_grouping_infos: when group count is: " + l_count.out)
					print ("%N " + l_refined_grouping.out)
				end
				l_count := l_count + 1
			end
		end

feature -- Query

	group_width (a_group: ARRAYED_LIST [SD_TOOL_BAR_ITEM]): INTEGER
			-- Group total item width
		require
			not_void: a_group /= Void
		do
			from
				a_group.start
			until
				a_group.after
			loop
				Result := Result + tool_bar_item_width (a_group.item)
				a_group.forth
			end
		ensure
			valid: Result >= 0
		end

	group_item_width (a_items: LIST [SD_TOOL_BAR_ITEM]): ARRAYED_LIST [INTEGER]
			-- Initlizatie a list with each item width in it
		do
			create Result.make (1)
			from
				a_items.start
			until
				a_items.after
			loop
				if attached {SD_TOOL_BAR_SEPARATOR} a_items.item then
					check must_not_contain_separator: False end
				else
					Result.extend (tool_bar_item_width (a_items.item))
				end
				a_items.forth
			end
		ensure
			group_count_right: Result.count = a_items.count
		end

	tool_bar_item_width (a_tool_bar_item: SD_TOOL_BAR_ITEM): INTEGER
			-- Tool bar item width of `a_tool_bar_item'
		require
			not_void: a_tool_bar_item /= Void
		do
			Result := a_tool_bar_item.width
		ensure
			valid: Result >= 0
		end

	group_count: INTEGER
			-- Group count
		do
			check not_void: algorithm /= Void end
			Result := algorithm.group_count
		end

	best_grouping_by_width_to_right (a_width: INTEGER): SD_TOOL_BAR_GROUP_INFO
			-- Best groupinp, Result is calculated by `a_width'
		require
			valid: a_width > 0
		local
			l_list : ARRAYED_LIST [SD_TOOL_BAR_GROUP_INFO]
		do
			from
				last_group_index := group_infos.count
				l_list := group_infos.linear_representation
				l_list.finish
			until
				l_list.before or attached Result
			loop
				if (not l_list.item.has_any_sub_info and a_width < l_list.item.maximum_width) or
					(l_list.item.has_any_sub_info and a_width < l_list.item.maximum_width_sub) then
					Result := l_list.item
				else
					last_group_index := last_group_index - 1
					l_list.back
				end
			end
			if not attached Result then
				Result := l_list.first
				last_group_index := 1
			end
		ensure
			not_void: Result /= Void
			valid: last_group_index >= 1 and last_group_index <= group_infos.count
		end

	best_grouping_by_width_to_left (a_width: INTEGER): SD_TOOL_BAR_GROUP_INFO
			-- Best groupinp, Result is calculated by `a_width'
			-- When pointer is moving to left, use this feature
		require
			valid: a_width > 0
		local
			l_list : ARRAYED_LIST [SD_TOOL_BAR_GROUP_INFO]
		do
			from
				last_group_index := 1
				l_list := group_infos.linear_representation
				l_list.start
			until
				l_list.after or attached Result
			loop
				if (not l_list.item.has_any_sub_info and a_width > l_list.item.maximum_width) or
					(l_list.item.has_any_sub_info and a_width > l_list.item.maximum_width_sub) then
					Result := l_list.item
				else
					last_group_index := last_group_index + 1
					l_list.forth
				end
			end
			if not attached Result then
				Result := l_list.last
				last_group_index := l_list.count
			end
		ensure
			not_void: Result /= Void
			valid: last_group_index >= 1 and last_group_index <= group_infos.count
		end

	last_group_index: INTEGER
			-- Last grouping info index

	best_grouping (a_group_count: INTEGER): detachable SD_TOOL_BAR_GROUP_INFO
			-- Best grouping.
		require
			valid: a_group_count >= 1 and a_group_count <= content.item_count_except_sep (False)
		do
			Result := group_infos.item (a_group_count)
		end

	max_row_count: INTEGER
			-- Actual maximum row count
		do
			Result := content.item_count_except_sep (False)
		end

	content: SD_TOOL_BAR_CONTENT
			-- Tool bar content managed by Current

	algorithm: SD_HUFFMAN_ALGORITHM
			-- Algorithm for grouping

feature {NONE} -- Implementation

	insert_arrayed_list_to_group_info_sub_level (a_list: ARRAYED_LIST [ARRAYED_LIST [INTEGER]]; a_sub_group_index: INTEGER; a_widths: ARRAYED_LIST [INTEGER])
			-- Insert `a_list' to `internal_refined_grouping'
		require
			internal_refined_grouping_attached: internal_refined_grouping /= Void
			not_void: a_list /= Void
			valid: a_sub_group_index > 0 and a_sub_group_index <= content.groups_count (False)
			valid: list_and_width_equal (a_list, a_widths)
		local
			l_sub_group_info: SD_TOOL_BAR_GROUP_INFO
		do
			if attached internal_refined_grouping as l_refined_grouping then
				l_refined_grouping.go_i_th (a_sub_group_index)

					-- `position_groups_imp' from SD_FLOATING_TOOL_BAR_ZONE_ASSISTANT count base on total items in sub group.
				l_sub_group_info := convert_arrayed_list_to_group_info (a_list, False, a_widths)

				l_refined_grouping.set_sub_group_info (l_sub_group_info, a_sub_group_index)
			else
				check from_precondition_internal_refined_grouping_attached: False end
			end
		end

	list_and_width_equal (a_list: ARRAYED_LIST [ARRAYED_LIST [INTEGER]]; a_items_width: ARRAYED_LIST [INTEGER]): BOOLEAN
			-- Is items count in `a_list' equal to `a_items_width''s count?
		require
			not_void: a_list /= Void
			not_void: a_items_width /= Void
		local
			l_count: INTEGER
		do
			from
				a_list.start
			until
				a_list.after
			loop
				l_count := l_count + a_list.item.count
				a_list.forth
			end
			Result := l_count = a_items_width.count
		end

	convert_arrayed_list_to_group_info (a_list: ARRAYED_LIST [ARRAYED_LIST [INTEGER]]; a_count_clear: BOOLEAN; a_items_width: ARRAYED_LIST [INTEGER]): SD_TOOL_BAR_GROUP_INFO
			-- Only convert first level
			-- Item is one group which has several items
		require
			not_void: a_list /= Void
			not_void: a_items_width /= Void
			valid: list_and_width_equal (a_list, a_items_width)
		local
			l_one_group: ARRAYED_LIST [INTEGER]
			l_group_width: INTEGER
			l_item: HASH_TABLE [INTEGER, INTEGER]
			l_item_count: INTEGER
		do
			from
				l_item_count := 1
				create Result.make
				a_list.start
			until
				a_list.after
			loop
				from
					l_one_group := a_list.item
					create l_item.make (10)
					l_group_width := 0
					if a_count_clear then
						l_item_count := 1
					end
					l_one_group.start
				until
					l_one_group.after
				loop
					l_item.extend (a_items_width.i_th (l_one_group.item), l_item_count)
					l_group_width := l_one_group.item + l_group_width

					l_item_count := l_item_count + 1
					l_one_group.forth
				end
				Result.extend (l_item, a_list.index /= 1)
				a_list.forth
			end
		ensure
			not_void: Result /= Void
		end

	group_items (a_group_count: INTEGER)
			-- Group items
		local
			l_row_left: INTEGER
			l_group: ARRAYED_LIST [ARRAYED_LIST [INTEGER]]
		do
			if a_group_count >= 1 then
				if a_group_count > algorithm.max_group_count then
					l_group := algorithm.best_grouping_when (algorithm.max_group_count)
					internal_refined_grouping := convert_arrayed_list_to_group_info (l_group, False, algorithm.item_width)
				else
					l_group := algorithm.best_grouping_when (a_group_count)
					internal_refined_grouping := convert_arrayed_list_to_group_info (l_group, False, algorithm.item_width)
				end

				if a_group_count > content.groups_count (False) and a_group_count <= content.item_count_except_sep (False) then
					l_row_left := a_group_count - content.groups_count (False)
					debug ("docking")
						print ("%N SD_TOOL_BAR_GROUP_DIVIDER on_pointer_motion l_row_left is: " + l_row_left.out)
					end
					compute_extra_groups (l_row_left)
				end
			end
		ensure
			created: a_group_count > 1 implies internal_refined_grouping /= Void
		end

	grouping_algorithm_factory (a_max_width_group_index: INTEGER): SD_HUFFMAN_ALGORITHM
			-- Factory method of algorithm
		require
			valid: a_max_width_group_index > 0 and a_max_width_group_index <= content.groups_count (False)
		local
			l_sub_group_item_count: INTEGER
		do
			Result := grouping_algorithm.item (a_max_width_group_index)
			if not attached Result then
				l_sub_group_item_count := content.group_items (a_max_width_group_index, False).count
				create Result.make (l_sub_group_item_count,
					group_item_width (content.group_items (a_max_width_group_index, False)))
				grouping_algorithm.extend (Result, a_max_width_group_index)
			end
		ensure
			not_void: Result /= Void
		end

	compute_extra_groups (a_extra_group_count: INTEGER)
			-- Compute extra groups when all top groups are all in separated row
		require
			valid: a_extra_group_count > 0
			set: internal_refined_grouping /= Void
		local
			l_max_group_info: SD_TOOL_BAR_GROUP_INFO
			l_temp_algorithm: SD_HUFFMAN_ALGORITHM
			l_reduced: INTEGER
			l_stop: BOOLEAN
			l_max_info: TUPLE [max_group_index: INTEGER; max_row_item_count: INTEGER]
			l_refined_grouping: like internal_refined_grouping
		do
			from
				l_refined_grouping := internal_refined_grouping
				check l_refined_grouping /= Void end -- Implied by precondition `set'
			until
				l_reduced >= a_extra_group_count or l_stop
			loop
				l_max_group_info := l_refined_grouping.maximum_width_top_group
				l_max_group_info.start
				l_max_info := l_refined_grouping.maximum_width_group_index

				-- Is there only one item in max width row?
				if l_max_info.max_row_item_count <= 1 then
					l_stop := True
				end
				if not l_stop then
					l_temp_algorithm := grouping_algorithm_factory (l_max_info.max_group_index)

					if is_initialized_sub_group (l_max_group_info) and then l_temp_algorithm.max_group_count < l_max_group_info.group_count + 1 then
						-- The maximum width row is already one item per row
						l_stop := True
					else
						-- Should not pass a_extra
						l_reduced := l_reduced + 1
						-- Because if `l_max_group_info' is not sub group info, then group count will be 1 larger than actual
						if not is_initialized_sub_group (l_max_group_info) then
							-- Is not from a sub group info
							-- Or from sub group info which item count = 1
							if l_temp_algorithm.max_group_count < 2 then
								-- `l_temp_algorithm' is maximum width group which only have one item, so we stop here
								l_stop := True
							else
								insert_arrayed_list_to_group_info_sub_level
									(l_temp_algorithm.best_grouping_when (2), l_max_info.max_group_index, l_temp_algorithm.item_width)
							end
						else
							-- Is from a sub group info
							l_max_group_info.start
							insert_arrayed_list_to_group_info_sub_level
								(l_temp_algorithm.best_grouping_when (l_max_group_info.group_count + 1), l_max_info.max_group_index, l_temp_algorithm.item_width)
						end
					end
				end
			end
		end

	is_initialized_sub_group (a_group_info: SD_TOOL_BAR_GROUP_INFO): BOOLEAN
			-- If `a_group_info' which is subgroup infornation initlialized?
		require
			not_void: a_group_info /= Void
		local
			l_snapshot: SD_TOOL_BAR_GROUP_INFO
		do
			l_snapshot := a_group_info.deep_twin
			l_snapshot.start
			Result := not (l_snapshot.count = 1 and l_snapshot.is_new_group)
		end

	group_infos: HASH_TABLE [SD_TOOL_BAR_GROUP_INFO, INTEGER]
			-- Group informations

	grouping_algorithm: HASH_TABLE [SD_HUFFMAN_ALGORITHM, INTEGER]
			-- We store algorithm here, so it'll not need recompute
			-- 2nd INTEGER is sub group index count

	internal_refined_grouping: SD_TOOL_BAR_GROUP_INFO
			-- Refined grouping info

invariant

	not_void: algorithm /= Void

note
	library:	"SmartDocking: Library of reusable components for Eiffel."
	copyright:	"Copyright (c) 1984-2017, Eiffel Software and others"
	license:	"Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.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
		]"

end