--[[
Реализация хиддн обжекта.
Описание здесь: http://wiki/HomePage/QuestEngine/HOG
]]

--[[
ХОГ реализован так:

Есть таблица - список объектов для взятия. 
Она имеет размер 'hidden_object.params.label_cols' на 'hidden_object.params.label_rows'.
В каждой её ячейке содержится либо nil, либо список с ссылками на QE-объекты (список может быть пустым).
Список в ячейке содержит либо один элемент, либо несколько элементов с одинаковыми хинтами. 
В последнем случае ячейка отображается в виде "N x Hint".

При запуске хога таблица заполняется случайно выбранными объектами с id вида "__hog...". При каждом добавлении 
объекта в ячейку происходит поиск объектов с таким же хинтом, как у добавляемого объекта. Все найденные объекты добавляются в список ячейки.
Когда объект добавляется в ячейку, его юзердата меняется на "hog,row_number, col_number", 
где row_number и col_number - позиция ячейки в таблице, в которую добавляют объект.

Когда игрок наводит на ячейку в таблице, в области предпросмотра отображается _первый_ объект из списка ячейки.

Когда игрок кликает на хог объект, проверяется юзердата объекта. Если она имеет вид "hog, row_number, col_number", 
значит объект находится в ячейке таблицы. Удаляем объект из списка ячейки.

Если список ячейки становится пустым, то заолняем его случайным __hog объектом со сцены. Продолжаем так, 
пока не достигнем указанного для хога кол-ва объектов. После этого, находим доступные последовательные объекты и отображем их подряд.
После того как игрок находит все последовательные объекты (если они есть), хог считается пройденным. Все состояния хог-объектов сбрасываются.

]]
hidden_object = hidden_object or {}

hidden_object.params = {}

-- функция, которая запускается когда хог открывается
function hidden_object.OnStart()
end

-- вызывается когда закрывают хог
function hidden_object.OnClose()
end

-- функция, которая запускается в конце хога
function hidden_object.OnEnd()
end

-- вызывается каждый апдейт ХОГа
function hidden_object.OnUpdate()
end

-- запускает ХОГ на сцене
-- вызов должен быть в QE в "скрипте сцены" у ХОГ-сцены
-- 	_current_scene - сцена, в которой стартует ХОГ
--	_number_of_objects_to_find - кол-во объектов, которые надо найти на сцене
--	_return_scene - id сцены, на которую игрок вернется после прохождения ХОГа
--   	_find_unique_objects - true - предметы для поиска не будут повторяться; по умолчанию - true
--	(пока убрал) _on_click - строка с кодом, который выполняется каждый раз при 
function hidden_object.Start(_current_scene, _number_of_objects_to_find, _return_scene, _find_unique_objects--[[, _on_click]])
	_number_of_objects_to_find = _number_of_objects_to_find or 3
    if _find_unique_objects == nil then _find_unique_objects = true end

	hidden_object.font = sf.core.g_Application.GetResourceManager().GetFont(hidden_object.params.label_font)
	__assert(hidden_object.font, "HOG: Шрифт '"..hidden_object.params.label_font.."' не найден!")
	
	hidden_object.scene = _current_scene
	__assert(hidden_object.scene)
	
	-- кол-во объектов, которые осталось найти в ХОГе (текущее кол-во объектов в списке сюда не входит)
	hidden_object.objects_left = _number_of_objects_to_find
	
	-- объект, для которого отображаем силуэт
	hidden_object.object_shape = nil
	
	-- id сцены, которая отобразится после прохождения ХОГа
	hidden_object.return_scene = _return_scene
	
	hidden_object.find_unique_objects = _find_unique_objects
	
	-- вытаскиваем текстуру фона выделенного объекта
	hidden_object.label_selected_back = sf.core.g_Application.GetResourceManager().GetTexture(hidden_object.params.label_selected_back)
	__assert(hidden_object.label_selected_back, "HOG: Текстура '"..hidden_object.params.label_selected_back.."', заданная как фон для выделенного оъекта в списке, не найдена!")
	
	hidden_object.timer = __create_timer()
	
	-- открыт ли сейчас хог?
	hidden_object.active = true
	
	-- устанавливаем пользовательский колбэк на взятие хог объекта (ПОКА УБРАЛ)
	--[[local callback, err = loadstring("return function(_object, _objects_left) "..(_on_click or "").." end")
	if not callback then __message("Error: "..err) end
	hidden_object.user_code_on_click = callback]]
	
	-- отключаем отображение хинтов
	hidden_object.prev_disable_hints_value = quest.global_cursor.GetDisableHints()
	quest.global_cursor.SetDisableHints(true)
	
	-- прячем список действий
	tasks_lua_widget.Show(false)
	-- прячем доступные для взятия объекты
	take_objects_list.Hide()
	
	-- инициализируем nil-ом объекты в списке ХОГа
	hidden_object.current_objects = {}
	hidden_object.current_objects.cols = hidden_object.params.label_cols
	hidden_object.current_objects.rows = hidden_object.params.label_rows
	
	hidden_object.current_objects_state = {}
	hidden_object.current_objects_state.cols = hidden_object.params.label_cols
	hidden_object.current_objects_state.rows = hidden_object.params.label_rows
	for row = 1, hidden_object.current_objects.rows do
		hidden_object.current_objects[row] = { }
		hidden_object.current_objects_state[row] = { }
	end

	-- если в хог уже играли, восстанавливаем его состояние 
	
	-- находим все доступные хог-объекты на уровне и группируем их по хинту
	local hog_objects, hint_to_number_lookup = {}, {}
	local obj_iter = hidden_object.scene.GetObjects()
	while not obj_iter.IsEnd() do
		local object = obj_iter.Get()
		
		if (hidden_object._IsHOGObject(object) or  hidden_object._IsLastHOGObject(object))then
			local hint = object.GetHint()
			local number = nil -- индекс группы объектов с хинтом hint в hog_objects
			for i,v in ipairs(hint_to_number_lookup) do
				if v.hint == hint then
					number = v.number
					break
				end
			end
			
			if not number then 
				-- объектов с таким хинтом до того не было,  добавляем в конец hog_objects
				number = #hog_objects + 1
				table.insert(hint_to_number_lookup, {hint = hint, number = number})
				hog_objects[number] = {}
			end
			table.insert(hog_objects[number], object)
		end
		
		obj_iter.Next()
	end
	
	-- находим все найденные объекты и объекты в списке
	for _, objects in ipairs(hog_objects) do
		local index, size = 1, #objects
		while index <= size do
			local obj = objects[index]
			if not obj.GetUserData().empty() and obj.GetUserData().c_str() ~= "hog,disabled" then 
				local row, col = hidden_object._ExtractUserData(obj)
				__assert(row and col, "UserData хог объекта содержит данные в неподдерживаемом формате!")
				if not obj.ReadObjectFlag(obj.ObjectFlagHidden) then				
					-- объект должен быть в таблице, добавляем
					if not hidden_object.current_objects[row][col] then hidden_object.current_objects[row][col] = {} end
					table.insert(hidden_object.current_objects[row][col], obj)
				end
				table.remove(objects, index)
				size = size - 1
				index = index - 1
			end
			index = index + 1
		end
	end
	
	-- определяем, сколько объектов осталось найти
	for _, objects in ipairs(hog_objects) do
		if #objects == 0 then
			-- в hog_objects лежат объекты, сгруппированные по хинту
			-- уже найденные или находящиеся в таблице объекты мы удалил в пред. цикле
			-- значит, если группа - элемент hog_objects пустая, значит все объекты с этим хинтом найдены/в списке
			hidden_object.objects_left = hidden_object.objects_left - 1
		end
	end

	-- делаем состояние всех объектов непоказанными
	for i = 1, #hidden_object.current_objects do
		for j = 1, #hidden_object.current_objects[i] do
			hidden_object.current_objects_state[i][j] = "new_object"
		end
	end
	
	hidden_object._FillObjectList()
	
	hidden_object.lua_widget.Show(true)
	
	hidden_object.OnStart()
    
    -- запускаем отложенное открытие виджета, чтобы побороть баг с неизменяющейся гуей после загрузки в ХОГ
    form_event([[hidden_object.lua_widget.Show(hidden_object.lua_widget.visible)]])
end

function hidden_object.GetActiveScene()
    return hidden_object.active and hidden_object.scene.GetId().c_str()
end

-- заполняет список объектов
function hidden_object._FillObjectList()
	if hidden_object.objects_left <= 0 and hidden_object._TotalObjectsLeft() == 0 then
		-- если нащли все объекты, начинаем отображать последовательность в конце ХОГа
		
		-- находим первый по порядку объект из доступных в последовательности
		local obj_iter = hidden_object.scene.GetObjects()
		local first_object, first_number = nil, 1000000
		while not obj_iter.IsEnd() do
			local object = obj_iter.Get()
			if 	hidden_object._IsLastHOGObjectValid(object) then
				local number = hidden_object._GetLastHOGObjectNumber(object)
				__assert(number ~= first_number, "Два доступных объекта с одинаковыми идентификаторами '"..tostring(object.GetIdRef().c_str()).."'")
				if number < first_number then
					first_number = number
					first_object = object
				end
			end
			obj_iter.Next()
		end	
		
		if first_object then
			-- есть доступный объект, добавляем его в позицию (1, 1)
			hidden_object._AddToObjectList(1, 1, first_object)
		end
		
		return
	end

	-- не все объекты найдены, пытаемся заполнить дырки в списке объектов
	
	-- находим все доступные хог-объекты на уровне
	local hog_objects, hint_to_number_lookup = {}, {}
	local obj_iter = hidden_object.scene.GetObjects()
	while not obj_iter.IsEnd() do
		local object = obj_iter.Get()
		
		if 	hidden_object._IsHOGObjectValid(object) then
		
			local hint = object.GetHint()
			local number = nil
			for i,v in ipairs(hint_to_number_lookup) do
				if v.hint == hint then
					number = v.number
					break
				end
			end
			
			if not number then
				number = #hog_objects + 1
				table.insert(hint_to_number_lookup, {hint = hint, number = number})
				hog_objects[number] = {}
			end
			table.insert(hog_objects[number], object)
		end
		
		obj_iter.Next()
	end

	
	if #hog_objects == 0 then return end
	
	-- заполняем дыры в списке объектов
	local current_objects = hidden_object.current_objects
	for row = 1, current_objects.rows do
		local obj_row = hidden_object.current_objects[row]
		for col = 1, current_objects.cols do
			if not obj_row[col] or #obj_row[col]==0 then
				-- нашли дырку, заполняем случайным объектом из hog_objects
				if #hog_objects == 0 or hidden_object.objects_left <= 0 then
					-- доступных хог-объектов не осталось, оставляем все дырки в списке
					return
				end
				local rnd_obj = math.random(#hog_objects)
				hidden_object._AddToObjectList(row, col, hog_objects[rnd_obj])
				hidden_object.objects_left = hidden_object.objects_left - 1
				table.remove(hog_objects, rnd_obj)
			end
		end
	end
end

-- добавляет объект или список объектов в ячейку таблицы поиска
-- также, добавляет все объекты с таким же хинтом (названием)
function hidden_object._AddToObjectList(row, col, objects)
	assert(not hidden_object.current_objects[row][col] or #hidden_object.current_objects[row][col] == 0)
	
	if type(objects) ~= "table" then
		objects = { objects }
	end
	
	for _,obj in ipairs(objects) do
		obj.SetUserData("hog,"..row..","..col) -- помечаем объект как находящийся в списке
	end
	hidden_object.current_objects[row][col] = objects
	
	hidden_object.current_objects_state[row][col]= "new_object" -- отметка, что объект новый
end

-- вытаскивает из userdata объекта сохраненные строку и столбец объекта в списке хога
-- возвращает в виде пары row, col (числа)
-- если вытащить не получается(объект не хоговский, например), то возвращает false
function hidden_object._ExtractUserData(_object)
	local udata = tostring(_object.GetUserData())
	local i, j, row, col = string.find(udata, "hog,(%d+),(%d+)")
	if i then
		return tonumber(row), tonumber(col)
	else 
		return false
	end
end

-- восстанавливает все хог объекты на уровне, чтобы они были доступны для клика
function hidden_object._ResetObjects()
	local obj_iter = hidden_object.scene.GetObjects()
	while not obj_iter.IsEnd() do
		local object = obj_iter.Get()
		if hidden_object._IsHOGObject(object) or hidden_object._IsLastHOGObject(object) then
			object.ClearObjectFlag(object.ObjectFlagHidden)
			
			if hidden_object._IsLastHOGObject(object) then
				-- дефолтное состояние __lasthog - disabled
				object.RaiseObjectFlag(object.ObjectFlagDisable)
				object.SetUserData("")
			elseif not object.GetUserData().empty() then
				if hidden_object.find_unique_objects then 
					object.SetUserData("hog,disabled")
				else
					object.SetUserData("")
				end
			end
		end
		obj_iter.Next()
	end
end

-- вызывается по завершению ХОГа
-- _take_object - объект, который пользователь кладет в инвентарь при прохождении ХОГа
function hidden_object._OnComplete(_take_object)
	hidden_object.OnEnd()

	-- если надо положить предмет в инвентарь, то
	if _take_object then
		-- сначала меняем интерфейс с ХОГа на обычный, потом берем предмет
		hidden_object.lua_widget.Show(false)
		take(_take_object) 
	end
	-- закрываем хог и восстанавливаем состояние предметов
	hidden_object.Close()
	hidden_object._ResetObjects()
end

-- закрывает ХОГ по кнопке
function hidden_object.Close()
	-- FIXME: должны сначала скрыть сцену с ХОГ, потом сменить интерфейс на обычный (пока все черное), потом показать сцену
	-- сейча неправильно
	hidden_object.lua_widget.Show(false)
	show_scene(hidden_object.return_scene)
	
	hidden_object.active = false
	
	if hidden_object.clicks_table then -- удаляем виджеты, созданные по правильному клику, если надо
		for i, click in ipairs(hidden_object.clicks_table) do
			hidden_object.lua_widget._this.RemoveWidget(click.clip)
			hidden_object.lua_widget._this.RemoveWidget(click.object)
		end
		hidden_object.clicks_table = nil
	end
	
	-- возвращаем отображение хинтов в состояние до хога
	quest.global_cursor.SetDisableHints(hidden_object.prev_disable_hints_value)
	-- отображаем список действий
	tasks_lua_widget.Show(true)

    difficulty.ApplyDifLevel()
    
    hidden_object.OnClose()
end

-- автоматически проходит текущий ХОГ
function hidden_object.CompleteUsingCheat()
	-- хог сейчас не открыт
	if not hidden_object.active then return end

	-- пробегаем по всем позициям в списке поиска объектов
	-- если находим объект - имитируем клик на него и начинаем обход заново
	-- если список пуст - то выходим из цикла
	local objects = hidden_object.current_objects
	while true do
		local found_object = false
		for row = 1, objects.rows do
			for col = 1, objects.cols do
				-- берем первый объект из ячейки
				local obj = objects[row][col] and objects[row][col][1] or nil
				if obj then
					quest.global_scene_widget.RunMouseCickScript(obj, obj.GetPos())
					threads_managment.InterruptClickScript()
					
					found_object = true
					break
				end
			end
			if found_object then 
				break 
			end
		end
		
		-- список пуст? хог закрыт? завершаем автоматическое прохождение
		if not found_object or not hidden_object.active then 
			break 
		end
	end
	
	return true
end

-- функция для использования в QE
-- её вызов должен быть в скриптах каждого хог-объекта
--  _take_object - не обязателен; id объекта, который берется в инвентарь после клика по объекту
function hidden_object.click(_take_object)
	local object, found = quest.global_current_script_object, nil
	local row, col = hidden_object._ExtractUserData(object)
	if not row then return end
	
	local objects = hidden_object.current_objects
	assert(objects[row] and objects[row][col])
	local found = false
	for i,o in ipairs(objects[row][col]) do
		if o.GetIdRef() == object.GetIdRef() then
			if not hidden_object.clicks_table then
				hidden_object.clicks_table = {}
			end
			if hidden_object.params.click_clip_id then
				-- создаем клип с эффектом хога
				local clip = sf.gui.CClipWidget("", 1, 0)
				clip.GetClip().Load(hidden_object.params.click_clip_id)
				local off = sf.misc.FloatVector(0,0)
				off.X = o.GetPos().X --+ o.GetSize().X/2
				off.Y = o.GetPos().Y --+ o.GetSize().Y/2
				local size = clip.GetClip().GetViewSize()
				clip.SetOffset(off.X - size.X/2, off.Y - size.Y/2)
				hidden_object.lua_widget._this.AddWidget(clip)
				if hidden_object.params.sound_on_click then
					sound(hidden_object.params.sound_on_click)
				end
				-- создаем виджет с таким же рисунком, который постепенно уведем в альфу
				local img_id = o.GetSourceId()
				local img = sf.gui.CImageWidget(img_id, "", 0, 0)
				local size = nil
				if hidden_object.params.object_increase_on_click then
					size = o.GetSize() * hidden_object.params.object_increase_on_click
				else
					size = o.GetSize()
				end
				--img.SetSize(size.X, size.Y)
				img.SetOffset(o.GetPos().X - o.GetSize().X/2, o.GetPos().Y - o.GetSize().Y/2)
				hidden_object.lua_widget._this.AddWidget(img)
				-- запоминаем созданные виджеты в таблицу с учетом начала проигрывания
				local click_obj = {
									object = img, 
									start = hidden_object.timer.Get().GetTime(),
									max_size = size, -- максимальный размер
									start_size = sf.misc.FloatVector(o.GetSize().X, o.GetSize().Y), -- начальный размер
									clip = clip
								}
				table.insert(hidden_object.clicks_table, click_obj)
			end
			found = true
			hidden_object.current_objects_state[row][col] = "remove_object"
			table.remove(hidden_object.current_objects[row][col], i)
			break
		end
	end
	assert(found)
    
    local all_cell_objects_found = #objects[row][col] == 0 -- true если все объекты из ячейки найдены
	
	object.RaiseObjectFlag(object.ObjectFlagHidden)
	
	hidden_object._FillObjectList()
	neutral_click() -- клик на объекте - правильное действие
	
	-- если для найденого объекта отображали силуэт, то прячем силуэт
	if hidden_object.object_shape and hidden_object.object_shape.GetIdRef() == object.GetIdRef() then
		hidden_object.object_shape = nil
	end

	-- вызываем callback пользователя (ПОКА УБРАЛ)
	if hidden_object.user_code_on_click then
		hidden_object.user_code_on_click(tostring(object.GetIdRef()), objects_left)
	end
	
	-- если все нашли - завершаем ХОГ
	if hidden_object._TotalObjectsLeft() <= 0 then 
		hidden_object._OnComplete(_take_object) 
		_take_object = nil
	end
	
	__assert(not _take_object, "Положить предмет в инвентарь можно только при клике на последний объект ХОГа. Объект '"..tostring(object.GetIdRef().c_str()).."' - не последний.")
	
	return true, all_cell_objects_found
end

-- подсчитываем кол-во оставшихся объектов
function hidden_object._TotalObjectsLeft()
	local objects_left = hidden_object.objects_left
	if objects_left < 0 then objects_left = 0 end
	for row = 1, hidden_object.current_objects.rows do
		for col = 1, hidden_object.current_objects.cols do
			if hidden_object.current_objects[row][col] then
				objects_left = objects_left + #hidden_object.current_objects[row][col]
			end
		end
	end
	return objects_left
end

-- проверяет, что объект является объектом хога
function hidden_object._IsHOGObject(_obj)
	return _obj.GetIdRef().find("__hog", 0) == 0
end

-- проверяет, что объект является объектом из последовательности в конце хога
function hidden_object._IsLastHOGObject(_obj)
	return _obj.GetIdRef().find("__lasthog", 0) == 0
end

-- возвращает порядковый номер объекта в последовательности в конце хога (или false, если объект не входит в последовательность)
function hidden_object._GetLastHOGObjectNumber(_object)
	local id = tostring(_object.GetIdRef().c_str())
	local i, j, number = string.find(id, "__lasthog(%d+)")
	if i then
		return tonumber(number)
	else 
		return false
	end
end

-- проверяет, что объект является ДОСТУПНЫМ ДЛЯ ПОПАДАНИЯ В СПИСОК объектом хога
function hidden_object._IsHOGObjectValid(_obj)
	-- дизабленные объекты валидны для попадания в список, они помечаются особым цветом
	return hidden_object._IsHOGObject(_obj)
			and not _obj.ReadObjectFlag(_obj.ObjectFlagRemoved) 
			and not _obj.ReadObjectFlag(_obj.ObjectFlagHidden)
			and _obj.GetUserData().empty()
end

-- проверяет, что объект из последовательности в конце хога является ДОСТУПНЫМ ДЛЯ ПОПАДАНИЯ В СПИСОК
function hidden_object._IsLastHOGObjectValid(_obj)
	return hidden_object._IsLastHOGObject(_obj)
		and not _obj.ReadObjectFlag(_obj.ObjectFlagRemoved) 
		and not _obj.ReadObjectFlag(_obj.ObjectFlagHidden)
		and not _obj.ReadObjectFlag(_obj.ObjectFlagDisable)
		and _obj.GetUserData().empty()
end

-- виджет для отображения списка предметов и силуэта
hidden_object.lua_widget = 
{
	_this = nil,
	last_object_shape = nil,
	
	-- показыват/прячет виджет с отображением гуи хога
	Show = function(_show)
		local inventory = objects_box_lua_widget.self
		if not _show then
			if difficulty.GetDifLevel() == 0 then
				set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagHidden, false)
				set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagDisabled, false)
				set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagHidden, true)
				set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagDisabled, true)
			end
			inventory.RemFlags(__or(inventory.FlagHidden, inventory.FlagDisabled))
			hidden_object.lua_widget.HideWidget()
            hidden_object.lua_widget.visible = false
		else
			set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagHidden, true)
			set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagDisabled, true)
			set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagHidden, false)
			set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagDisabled, false)
			inventory.AddFlags(__or(inventory.FlagHidden, inventory.FlagDisabled))
			hidden_object.lua_widget.ShowWidget()
            hidden_object.lua_widget.visible = true
		end
	end,
    
    -- показывает виджет
    ShowWidget = function()
        local this = hidden_object.lua_widget._this
        __assert(this, "lua_widget с id = 'hidden_object' не был загружен из gui.xml")
        this.RemFlags(sf.misc.BitwiseOr(this.FlagDisabled, this.FlagHidden))
    end,
    
    -- прячет виджет
    HideWidget = function()
        local this = hidden_object.lua_widget._this
        __assert(this, "lua_widget с id = 'hidden_object' не был загружен из gui.xml")
        this.AddFlags(sf.misc.BitwiseOr(this.FlagDisabled, this.FlagHidden))
    end,

	Load = function(_this)
		local self = hidden_object.lua_widget
		
		self._this = _this
		_this.AddFlags(sf.misc.BitwiseOr(_this.FlagDisabled, _this.FlagHidden))
		
		self.effects = {}
		
		hidden_object.visual_effects_list = visual_effects.CreateEffectsList()
	end,
	
	DoUpdate = function(_this)
		local self = hidden_object.lua_widget
		
        -- постоянно применяем гую как в сложном режиме, 
        -- т.к. во время хога игрок может сменить сложность
        -- прячем доступные для взятия объекты
        take_objects_list.Hide()
        set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagHidden, true)
		set_widget_flag_value(difficulty.easy_panel, sf.gui.CBaseWidget.FlagDisabled, true)
		set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagHidden, false)
		set_widget_flag_value(difficulty.hard_panel, sf.gui.CBaseWidget.FlagDisabled, false)
        
		-- обновляем захардкоденные :-(  эффекты
		
		if self.effects.select_effect and self.effects.select_effect:Update() then
			self.effects.select_effect = nil
		end
		
		if self.effects.label_back_appear and self.effects.label_back_appear:Update() then
			self.effects.label_back_appear = nil
		end
		
		
		hidden_object.visual_effects_list:Update()
		hidden_object.lua_widget.UpdateStates()
		
        hidden_object.OnUpdate()
        
		return false
	end,
	
	
	DoDraw = function(_this, _renderer)
		hidden_object.visual_effects_list:PreDraw(_renderer)
        hidden_object.visual_effects_list:PostDraw(_renderer)
		local self = hidden_object.lua_widget
	
		-- отобаражем область расположения списка объектов
		local label_area = hidden_object.params.label_area
		if hidden_object.params.debug_render then
			sf.graphics.RenderRect(_renderer, sf.misc.FloatRect(label_area.x, label_area.y, label_area.x2 - label_area.x, label_area.y2 - label_area.y), 
				sf.graphics.Color(255, 255, 255, 255))
		end
		
		-- отображаем область силуэта
		local shape_area = hidden_object.params.shape_area
		if hidden_object.params.debug_render then
			sf.graphics.RenderRect(_renderer, sf.misc.FloatRect(shape_area.x, shape_area.y, shape_area.x2 - shape_area.x, shape_area.y2 - shape_area.y), 
				sf.graphics.Color(255, 255, 255, 0))
		end
		
		if not hidden_object.font then return false end
		
		-- отображаем список объектов
		local x, y = label_area.x, label_area.y
		local offx, offy = (label_area.x2 - label_area.x)/hidden_object.params.label_cols, (label_area.y2 - label_area.y)/hidden_object.params.label_rows
		local objects = hidden_object.current_objects
		for row = 1, objects.rows do
			x = label_area.x
			for col = 1, objects.cols do
				local obj = objects[row][col] and objects[row][col][1] or nil
				if obj then
					-- //FIXME: надо бы закешировать где-то FormatByStringsID
					local text = sf.misc.g_StringTable.Instance().FormatByStringsID(obj.GetHint())
					
					-- если в ячейке больше одного объекта -  надо отображать их как N x Name
					if #objects[row][col] > 1 then
						text = tostring(#objects[row][col]) .. " x "..text
					end
				
					local string_width = hidden_object.font.GetStringWidth(text)
					local label_x = x + (offx - string_width)/2
					
					-- отрисовываем подложку выделенного объекта
					if obj == hidden_object.object_shape or obj == self.last_object_shape then
						_renderer.RenderTexture(hidden_object.label_selected_back.GetTexture(0), sf.misc.FloatRect(x, y, offx, hidden_object.font.PixHeight), 
							sf.misc.IntRect(0, 0, 0, 0), 
							obj == hidden_object.object_shape and self.label_back_color or self.last_label_back_color)
					end
					
					-- выбираем цвет в зависимости от того, disabled объект или нет
					local color = sf.graphics.Color(hidden_object.params.label_color_normal)
					if obj.ReadObjectFlag(obj.ObjectFlagDisable) then color = sf.graphics.Color(hidden_object.params.label_color_disabled) end
					
				
					_renderer.RenderString(hidden_object.font, text, label_x, y, -1, -1, 1, color, 0)
				end
				x = x + offx
			end
			y = y + offy
		end
		
		-- отображаем силуэт
		if hidden_object.object_shape then
			local x, y = shape_area.x, shape_area.y -- (x,y) - центр области силуэта
			local width, height = shape_area.x2 - shape_area.x + 1, shape_area.y2 - shape_area.y + 1
			if shape_area.x2 and shape_area.y2 then
				x = (shape_area.x2 + x)/2
				y = (shape_area.y2 + y)/2
			end
			
			_renderer.PushState()
			
			local obj = hidden_object.object_shape
			local objsize = obj.GetSize()
			if objsize.X > width or objsize.Y > height then
				local k, ky = width/objsize.X, height/objsize.Y
				if ky < k then k = ky end
				_renderer.ApplyMatrix(sf.misc.MatrixScale(k, k)*sf.misc.MatrixTranslation( x,  y ))
			else
				_renderer.ApplyMatrix(sf.misc.MatrixTranslation( x,  y ))
			end
		
			_renderer.SetColor(self.object_shape_color)
			obj.Draw(_renderer)
			
			_renderer.PopState()
		end
		
		if hidden_object.clicks_table then
			-- рисуем объект, по которому кликнули
			local now = hidden_object.timer.Get().GetTime()
			local need_delete = false
			for i, click in ipairs(hidden_object.clicks_table) do
				local clip_time = qe.GetClipTime(click.clip.GetClip())
				--__message("start="..click.start.." now="..now.." clip_time="..clip_time)
				local color = sf.graphics.Color(click.object.GetColor())
				local cur_time = (now - click.start)/clip_time
				if cur_time > 1 then 
					cur_time = 1 
				end
				local koef = 1
				if hidden_object.params.increase_time and hidden_object.params.increase_time >= cur_time then
					local time_inc = (1 - 1/hidden_object.params.object_increase_on_click)*cur_time/hidden_object.params.increase_time
					koef = 1/hidden_object.params.object_increase_on_click + time_inc
				elseif hidden_object.params.increase_time then
					local time_inc = (cur_time - hidden_object.params.increase_time )/ (1 - hidden_object.params.increase_time)
					koef = 1 - time_inc
				else
					koef = 1 - cur_time
				end
				--__message("koef="..koef)
				local size = click.max_size * koef
				local off = click.object.GetOffset() + click.object.GetSize()/2 - size/2
				click.object.SetSize(size.X, size.Y)
				click.object.SetOffset(off.X, off.Y)
				
				color.Alpha = (1 - cur_time) * 255
				click.object.SetColor(color)
				if (now - click.start >= clip_time) then
					need_delete = true
				end
			end
			
			if need_delete then -- удаляем все ненужные уже клип-виджеты
				local i,n = 1, #hidden_object.clicks_table 
				while i <= n do 
					local clip_time = qe.GetClipTime(hidden_object.clicks_table[i].clip.GetClip())
					if (now - hidden_object.clicks_table[i].start < clip_time) then
						i = i + 1
					else
						n = n - 1
						local clip = hidden_object.clicks_table[i].clip
						local img = hidden_object.clicks_table[i].object
						hidden_object.lua_widget._this.RemoveWidget(clip)
						hidden_object.lua_widget._this.RemoveWidget(img)
						--hidden_object.clicks_table[i].object.RaiseObjectFlag(hidden_object.clicks_table[i].object.ObjectFlagHidden)
						table.remove(hidden_object.clicks_table, i)
					end
				end
			end
		end
		
		return false
	end,
	
	-- апдейт эффектов
	UpdateStates = function()
		local label_area = hidden_object.params.label_area
		local x, y = label_area.x, label_area.y
		local offx, offy = (label_area.x2 - label_area.x)/hidden_object.params.label_cols, (label_area.y2 - label_area.y)/hidden_object.params.label_rows
		local objects = hidden_object.current_objects
		for row = 1, objects.rows do
			x = label_area.x
			for col = 1, objects.cols do
				local obj = objects[row][col] and objects[row][col][1] or nil
				if obj or hidden_object.current_objects_state[row][col] == "remove_object" or hidden_object.current_objects_state[row][col] == "update_complete" then
					-- вычисляем размеры нужного эффекта
					local string_width
					if obj then
						local text = sf.misc.g_StringTable.Instance().FormatByStringsID(obj.GetHint())
						-- если в ячейке больше одного объекта -  надо отображать их как N x Name
						if #objects[row][col] > 1 then
							text = tostring(#objects[row][col]) .. " x "..text
						end
				
						string_width = hidden_object.font.GetStringWidth(text)
						
					end	
					if not string_width then
						string_width = offx
					end
					local label_x = x + (offx - string_width)/2
					
					hidden_object.lua_widget.CheckState(row, col, string_width, offy, label_x, y)
				end
				x = x + offx
			end
			y = y + offy
		end
		
		-- удаляем мертвые эффекты
		if hidden_object.current_objects_effects then
			local i,n = 1, #hidden_object.current_objects_effects
			while n > i do
				local need_remove = false
				if hidden_object.current_objects_effects[i].dead then 
					need_remove = true
					hidden_object.current_objects_effects[i].effect:Kill(false)
				end
				local now = hidden_object.timer.Get().GetTime()
				local start = hidden_object.current_objects_effects[i].start
				if hidden_object.params.appear_time and (now - start >= hidden_object.params.appear_time) then
					need_remove = true
					if not hidden_object.current_objects_effects[i].dead then
						hidden_object.current_objects_effects[i].effect:Kill(false)
						hidden_object.current_objects_effects[i].dead = true
					end
				end
				if need_remove then
					table.remove(hidden_object.current_objects_effects, i)
					n = n - 1
				else
					i = i + 1
				end
			end
		end
	end,
	
	-- появление/затухание эффектов в зависимости от состояния объекта
	-- _row, _col указывает в какой колонке будет эффект
	-- _string_width, _string_height - величина эффекта
	-- _x, _y - место появления эффекта
	CheckState = function(_row, _col, _string_width, _string_height, _x, _y )
		-- проверки состояний 
		if hidden_object.current_objects_state[_row][_col] == "new_object" or hidden_object.current_objects_state[_row][_col] == "remove_object" then
			local pre_effect = hidden_object.params.pre_appear_effect
			if hidden_object.current_objects_state[_row][_col] == "remove_object" then
				pre_effect = hidden_object.params.pre_completed_effect
			end
			if pre_effect then
				if not hidden_object.current_objects_effects then
					hidden_object.current_objects_effects = {}
				end
				local eff_offset = sf.misc.FloatVector(_x + _string_width/2, _y + hidden_object.font.PixHeight/2)
				local eff = hidden_object.visual_effects_list:CreateEffect(pre_effect, eff_offset)
							local rect_effect = eff:GetChildEffect("rect_systems")
				if rect_effect then
								local size = sf.misc.FloatVector(_string_width, _string_height)
					rect_effect:SetEmiterSize(size)
				end
				
				local start = hidden_object.timer.Get().GetTime()
				table.insert(hidden_object.current_objects_effects, {effect = eff, start = start, row = _row, col = _col, dead = false})
			end
			
			local post_effect = hidden_object.params.post_appear_effect
			if hidden_object.current_objects_state[_row][_col] == "remove_object" then
				post_effect = hidden_object.params.post_completed_effect
			end
			if post_effect then
				if not hidden_object.current_objects_effects then
					hidden_object.current_objects_effects = {}
				end
				local eff_offset = sf.misc.FloatVector(_x + _string_width/2, _y + hidden_object.font.PixHeight/2)
				local eff = hidden_object.visual_effects_list:CreateEffect(post_effect, eff_offset)
				local rect_effect = eff:GetChildEffect("rect_systems")
				if rect_effect then
					local size = sf.misc.FloatVector(_string_width, _string_height)
					rect_effect:SetEmiterSize(size)
				end
				
				local start = hidden_object.timer.Get().GetTime()
				table.insert(hidden_object.current_objects_effects, {effect = eff, start = start, row = _row, col = _col, dead = false})
			end
			
			if hidden_object.current_objects_state[_row][_col] == "remove_object" then
				hidden_object.current_objects_state[_row][_col] = "update_complete"
			else
				hidden_object.current_objects_state[_row][_col] = "update_appearance"
			end
			
		elseif hidden_object.current_objects_state[_row][_col] == "update_appearance" or hidden_object.current_objects_state[_row][_col] == "update_complete" then
			if hidden_object.current_objects_effects then
				for i, effect in ipairs(hidden_object.current_objects_effects) do
					if effect.row == _row and effect.col == _col then
						-- плавно выводим из альфы строку
						local now = hidden_object.timer.Get().GetTime()
						
						local koef = (now - effect.start)/hidden_object.params.appear_time
						if koef > 1 then
							koef = 1
						end
						if koef == 1 then -- убиваем эффект
							hidden_object.current_objects_state[_row][_col]  = "normal"
							effect.effect:Kill(false)
							effect.dead = true
						end
					end
				end
			end
		end 
	end,
	
	OnMouseMove = function(_this, _pos, _state, _broadcast)
		if not hidden_object.font or _broadcast then 
            return false 
        end
		
		local font = hidden_object.font
		local label_area = hidden_object.params.label_area
	
		-- определяем, силуэт какого объекта надо показывать
		local x, y = label_area.x, label_area.y
		local height = font.PixHeight
		local offx, offy = (label_area.x2 - label_area.x)/hidden_object.params.label_cols, (label_area.y2 - label_area.y)/hidden_object.params.label_rows
		local objects = hidden_object.current_objects
		for row = 1, objects.rows do
			x = label_area.x
			for col = 1, objects.cols do
				local obj = objects[row][col] and objects[row][col][1] or nil
				if obj then
					local text = sf.misc.g_StringTable.Instance().FormatByStringsID(obj.GetHint())
					local width = font.GetStringWidth(text)
					local label_x = x + (offx - width)/2
					if _pos.X >= label_x and _pos.X <= label_x + width and _pos.Y >= y and _pos.Y <= y + height then
						-- попали в объект
						hidden_object.lua_widget._InitSelectEffect(obj)
						return false
					end
				end
				x = x + offx
			end
			y = y + offy
		end	
		
		hidden_object.lua_widget.effects.select_effect = nil
		return false
	end,
	
	-- инициирует эффект ожидания перед выделением объекта
	_InitSelectEffect = function(_object) 
		local self = hidden_object.lua_widget
		
		if hidden_object.object_shape == _object or self.effects.select_effect and self.effects.select_effect.object == _object then return end
		
		-- эффекта плавного появления подложки
		self.effects.select_effect = 
		{
			object = _object,
			time = hidden_object.timer.Get().GetTime() + hidden_object.params.label_select_delay,
			
			Update = function(_self)
				if _self.time > hidden_object.timer.Get().GetTime() then return end
				
				local widget = hidden_object.lua_widget
				
				-- инциализируем начальное состояние эффекта плавного появления подложки
				hidden_object.object_shape = _self.object
				widget.label_back_color = sf.graphics.Color(0, 255, 255, 255)
				widget.object_shape_color = sf.graphics.Color(0, 0, 0, 0)
				
				-- создаем эффект плавного появления подложки на новом месте и исчезновения со старого
				hidden_object.lua_widget.effects.label_back_appear = 
				{
					time = hidden_object.timer.Get().GetTime() + hidden_object.params.select_appear_time,
					
					Update = function(_self)
						if _self.time > hidden_object.timer.Get().GetTime() then 
							-- интерполируем альфу 
							local k = (_self.time - hidden_object.timer.Get().GetTime())/hidden_object.params.select_appear_time
							widget.label_back_color.Alpha = (1 - k)*255
							
							widget.object_shape_color.Alpha = (1 - k)*255
							return
						end
							
						-- конечное состояние
						widget.label_back_color.Alpha = 255
						widget.object_shape_color.Alpha = 255
						widget.last_object_shape = nil
						return true
					end
				}
				
				return true
			end
		}
	end,
	
	effects = {},
	
	_actions = 
	{
		hog_close_button = 
		{
			pressed = function()
				hidden_object.Close()
				return true
			end
		}
	},
	
	__CreateInstance = function() return "hidden_object.lua_widget" end
}

__inherite(hidden_object.lua_widget, null_lua_widget_handler(hidden_object.lua_widget._actions))