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

--[[
    hidden_category.categories - список категорий для поиска. В каждой лежит список объектов, оставшихся для поиска. Также
    там лежат left_to_find и found - сколько осталось найти и сколько найдено, соответственно.
    
    hidden_category.object_to_take - ссылка на объект, который надо положить в инвентарь после прохождения.
    
    При старте хога находятся все категории, в которых есть доступный или помеченный как найденный объект. Они помещаются вернется
    hidden_category.categories.
    
    Если объект нашли (кликнули на него), то он становится недоступным, в userdata помещается "f", и он удаляется из
    hidden_category.categories.
    
    При запуске hidden_category подменяет собой таблицу hidden_object. После закрытия возвращает hidden_object в первоначальное 
    состояние. 
]]
hidden_category = {}

hidden_category.params = {}

-- запускает ХОГ на сцене
-- вызов должен быть в QE в "скрипте сцены" у ХОГ-сцены
-- 	_current_scene - сцена, в которой стартует ХОГ
--	_return_scene - id сцены, на которую игрок вернется после прохождения ХОГа
--  _categories_settings - таблица с всяческими настройками категорий объектов
--  _script_on_complete - скрипт, который выполнится после прохождения хога
function hidden_category.Start(_current_scene, _return_scene, _categories_settings, _script_on_complete)
    -- костыльное решение с подменой hidden_object
    -- надо бы переделать
    hidden_category.hidden_object = hidden_object
    hidden_category.lua_widget._this = hidden_object.lua_widget._this
    hidden_category.OnStart = hidden_object.OnStart
    hidden_category.OnEnd = hidden_object.OnEnd
    hidden_object = hidden_category
    
    -- callback на прохождение хога
    local callback, err = loadstring("return function() "..(_script_on_complete or "").." end")
	if not callback then __message("Error: "..err) end
	hidden_category.on_complete = callback()

	hidden_category.font = sf.core.g_Application.GetResourceManager().GetFont(hidden_category.params.label_font)
	__assert(hidden_category.font, "HOG: Шрифт '"..hidden_category.params.label_font.."' не найден!")
	
	hidden_category.scene = _current_scene
	__assert(hidden_category.scene)
    
    hidden_category.categories_settings = _categories_settings
	
	-- категория, для которой отображаем силуэт
    hidden_category.object_shape_category = nil
	
	-- id сцены, которая отобразится после прохождения ХОГа
	hidden_category.return_scene = _return_scene
	
	-- вытаскиваем текстуру фона выделенного объекта
	hidden_category.label_selected_back = sf.core.g_Application.GetResourceManager().GetTexture(
        hidden_category.params.label_selected_back)
	__assert(hidden_category.label_selected_back, "HOG: Текстура '"..hidden_category.params.label_selected_back..
        "', заданная как фон для выделенного оъекта в списке, не найдена!")
	
	hidden_category.timer = __create_timer()
	
	-- открыт ли сейчас хог?
	hidden_category.active = true
	
	-- отключаем отображение хинтов
	hidden_category.prev_disable_hints_value = quest.global_cursor.GetDisableHints()
	quest.global_cursor.SetDisableHints(true)
	
	-- прячем список действий
	tasks_lua_widget.Show(false)
	-- прячем доступные для взятия объекты
	take_objects_list.Hide()
    
    
    -- находим и группируем объекты по категориям
    -- проверяем, что для всех категорий указаны настройки
    -- выделяем и инициализируем категории для поиска
    -- заодно находим предмет для взятия в инвентарь, если он есть
    hidden_category.object_to_take = nil
    
    -- все объекты, сгруппированные по категориям
    hidden_category.all_categories = {}
    
    -- словарь: категория -> объекты, которые надо найти + кол-во уже найденных
    hidden_category.categories = {} 
    
    local obj_iter = hidden_category.scene.GetObjects()
    while not obj_iter.IsEnd() do
        local obj = obj_iter.Get()
        
        if hidden_category._IsObjectToTake(obj) then
            __assert(not hidden_category.object_to_take)
            hidden_category.object_to_take = obj
        end
        
        local category_id = hidden_category._GetObjectCategory(obj)
        if category_id then
            -- настройки категории должны быть
            __assert(_categories_settings[category_id], "Настройки группы "..tostring(category_id)..
                " не переданы в hidden_category.Start\nСцена "..tostring(hidden_category.scene.GetId())) 
            
            -- добавляем объект в список общих категорий
            if not hidden_category.all_categories[category_id] then
                hidden_category.all_categories[category_id] = {}
            end
            table.insert(hidden_category.all_categories[category_id], obj)
            
            -- если объект надо искать - добавляем в список категорий поиска
            -- если объект уже найден - отражаем это в категории
            if not hidden_category.categories[category_id] then
                -- создаем категорию
                hidden_category.categories[category_id] = 
                    { 
                        id = category_id,
                        left_to_find = 0, -- сколько осталось найти
                        found = 0 -- сколько найдено
                    }
            end            
            local category = hidden_category.categories[category_id]
            if not obj.ReadObjectFlag(obj.ObjectFlagDisable) then
                -- объект доступен, следовательно его надо искать
                obj.SetLuaScript("hidden_category.object_click()", obj.ScriptMouseClick)
                category.left_to_find = category.left_to_find + 1
                table.insert(category, obj)
            elseif  hidden_category._IsMarkedAsFound(obj) then
                -- объект уже был найден игроком
                category.found = category.found + 1
            end
        end
        obj_iter.Next()
    end
    
    -- выбираем из категорий необходимые для поиска
    -- заодно проверяем на неожиданные ситуации
    local categories = {}
    for id, category in pairs(hidden_category.categories) do
        if #category ~= 0 or category.found > 0 then
            -- есть ненайденный объект или есть найденные объекты
            -- следовательно, надо искать эту категорию
            __assert(#category == category.left_to_find)
            __assert(#category + category.found == #(hidden_category.all_categories[id]))
            table.insert(categories, category)
        end
    end 
    hidden_category.categories = categories
	
	hidden_category.lua_widget.Show(true)
	
	hidden_category.OnStart()
end

-- инициализирует сцену так, чтобы при запуске её как хога игрок должен
-- будет искать предметы из категорий, перечисленных в _categories_to_find
-- и после прохождения хога в инвентарь попадет объект _object_for_take
function hidden_category.InitHogScene(_scene_id, _object_for_take, _categories_to_find)
    -- трансформируем список категорий в карту: ид. категории => надо ли её искать
    local categories_to_find_map = {}
    for _, id in ipairs(_categories_to_find) do
        categories_to_find_map[id] = true
    end

    -- делаем доступными все объекты категорий для поиска
    -- а объекты остальных категорий делаем недоступными
    local scene = quest.global_played_level.GetSceneById(_scene_id)
    __assert(scene)
    local obj_iter = scene.GetObjects()
    while not obj_iter.IsEnd() do
        local obj = obj_iter.Get()
        local category = hidden_category._GetObjectCategory(obj)
        if category then
            __assert(not hidden_category._IsMarkedAsFound(obj))
            if categories_to_find_map[category] then
                obj.ClearObjectFlag(obj.ObjectFlagDisable)
            else
                obj.RaiseObjectFlag(obj.ObjectFlagDisable)
                obj.SetLuaScript("", obj.ScriptMouseClick)
            end
        end
        
        if obj.GetIdRef().compare(_object_for_take) == 0 then
            -- нашли объект для получения после прохождения хога
            -- помечаем его
            __assert(not category)
            __assert(obj.GetUserData().empty())
            obj.SetUserData("t")
            obj.RaiseObjectFlag(obj.ObjectFlagHidden)
        end
        
        obj_iter.Next()
    end
end

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

-- приводит все хог категории к первоначальному состоянию
function hidden_category._ResetObjects()
    for _, objects in pairs(hidden_category.all_categories) do
        for _, obj in ipairs(objects) do
            obj.RaiseObjectFlag(obj.ObjectFlagDisable)
            obj.ClearObjectFlag(obj.ObjectFlagHidden)
            obj.SetUserData("")
        end
    end
    
    if hidden_category.object_to_take then
        hidden_category.object_to_take.SetUserData("")
    end
end

-- возвращает номер категории объекта или nil, если объект не принадлежит
-- никакой категории
function hidden_category._GetObjectCategory(_obj)
    return tonumber(string.match(tostring(_obj.GetId()), "^__hog(%d+)"))
end

-- помечает объект как найденный
function hidden_category._MarkAsFound(_obj)
    __assert(hidden_category._GetObjectCategory(_obj))
    __assert(_obj.GetUserData().empty())
    _obj.SetUserData("f")
end

-- помечен ли объект как найденный?
function hidden_category._IsMarkedAsFound(_obj)
    local is_found = _obj.GetUserData().compare("f") == 0
    -- если объект найден, то он должен входить в категорию
    __assert(not is_found or hidden_category._GetObjectCategory(_obj))
    return is_found
end

-- помечен ли объект для получения в инвентарь?
function hidden_category._IsObjectToTake(_obj)
    local is_to_take = _obj.GetUserData().compare("t") == 0
    -- если объект для получения в инвентарь, то он НЕ должен входить в категорию
    __assert(not is_to_take or not hidden_category._GetObjectCategory(_obj))
    return is_to_take
end

-- вызывается по завершению ХОГа
function hidden_category._Complete()
	hidden_category.OnEnd()

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

-- закрывает ХОГ
function hidden_category.Close()
	-- FIXME: должны сначала скрыть сцену с ХОГ, потом сменить интерфейс на обычный (пока все черное), потом показать сцену
	-- сейчаc неправильно, если есть плавный переход
	hidden_category.lua_widget.Show(false)
	show_scene(hidden_category.return_scene)
	
	hidden_category.active = false
	
	-- возвращаем отображение хинтов в состояние до хога
	quest.global_cursor.SetDisableHints(hidden_category.prev_disable_hints_value)
	-- отображаем список действий
	tasks_lua_widget.Show(true)
	-- отображаем доступные для взятия объекты
	take_objects_list.Show()
    

    hidden_object = hidden_category.hidden_object
end

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

	-- пробегаем по всем позициям в списке категорий
	-- если находим объект - имитируем клик на него и начинаем обход заново
	-- если список пуст - то выходим из цикла
    
    local cycles = 0
    while true do
        local found = false
        for _, category in ipairs(hidden_category.categories) do
            if #category > 0 then
                local obj = category[1]
                quest.global_scene_widget.RunMouseCickScript(obj, obj.GetPos())
                threads_managment.InterruptClickScript()
                found = true
                break
            end
        end
        
        if not found then 
            break 
        end
        
        cycles = cycles + 1
        if cycles > 10000 then 
            __message("автоматическое прохождение хога зациклилось!")
            break 
        end
    end
	
	return true
end

function hidden_category.click() end

-- функция устанавливается на клик для каждого объекта, который надо найти
function hidden_category.object_click()
    local object = quest.global_current_script_object
    
    -- находим категорию объекта, различные проверки
    local category_id = hidden_category._GetObjectCategory(object)
    __assert(category_id)
    __assert(not hidden_category._IsMarkedAsFound(object))
    local object_category = nil
    for _, category in ipairs(hidden_category.categories) do
        if category_id == category.id then
            object_category = category
            break
        end
    end
    __assert(object_category)
    
    
    hidden_category._MarkAsFound(object)
    object.RaiseObjectFlag(object.ObjectFlagDisable)
    object.RaiseObjectFlag(object.ObjectFlagHidden)
    object_category.left_to_find = object_category.left_to_find - 1
    __assert(object_category.left_to_find >= 0)
    object_category.found = object_category.found + 1
    
    -- удаляем объект из категории для поиска
    local removed = false
    for i, o in ipairs(object_category) do
        if o.GetIdRef() == object.GetIdRef() then
            table.remove(object_category, i)
            removed = true
            break
        end
    end
    __assert(removed)
    
	neutral_click() -- клик на объекте - правильное действие
	
	-- если для найденого объекта отображали силуэт, то прячем силуэт
	-- if hidden_category.object_shape and hidden_category.object_shape.GetIdRef() == object.GetIdRef() then
		-- hidden_category.object_shape = nil
	-- end
	
	-- если есть не найденные объекты - выходим
    for _, category in ipairs(hidden_category.categories) do
        if category.left_to_find > 0 then 
            return true 
        end
	end
    -- всё найдено, завершаем хог
    hidden_category._Complete()
	
	return true
end

-- виджет для отображения списка категорий и силуэта
hidden_category.lua_widget = 
{
	_this = nil,
	last_object_shape_category = nil,
	
	-- показыват/прячет виджет с отображением гуи хога
	Show = function(_show)
		local this = hidden_category.lua_widget._this
		local inventory = objects_box_lua_widget.self
		__assert(this, "lua_widget с id = 'hidden_category' не был загружен из gui.xml")
		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))
			this.AddFlags(sf.misc.BitwiseOr(this.FlagDisabled, this.FlagHidden))
		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))
			this.RemFlags(sf.misc.BitwiseOr(this.FlagDisabled, this.FlagHidden))
		end
	end,

	Load = function(_this)
		local self = hidden_category.lua_widget
		
		self._this = _this
		_this.AddFlags(sf.misc.BitwiseOr(_this.FlagDisabled, _this.FlagHidden))
		
		self.effects = {}
	end,
	
	DoUpdate = function(_this)
		local self = hidden_category.lua_widget
		
		-- обновляем захардкоденные :-(  эффекты
		
		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
		
		return false
	end,
	
	DoDraw = function(_this, _renderer)
	
		local self = hidden_category.lua_widget
	
		-- отобаражем область расположения списка категорий
		local label_area = hidden_category.params.label_area
		if hidden_category.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_category.params.shape_area
		if hidden_category.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_category.font then return false end
		
		-- отображаем список категорий
        local categories_count = #hidden_category.categories
		local x, y = (label_area.x2 + label_area.x)/2, label_area.y
        local offy = ((label_area.y2 - label_area.y) - categories_count * hidden_category.font.PixHeight)/(categories_count + 1)
        y = y + offy
		local categories = hidden_category.categories
        for _, category in ipairs(categories) do
            local settings = hidden_category.categories_settings[category.id]
            local text = settings.text
            text = sf.misc.g_StringTable.Instance().FormatByStringsID(text)
            text = qe.StringFormat(text, category.found, category.left_to_find + category.found)
            local width = hidden_category.font.GetStringWidth(text)
            
            -- отрисовываем подложку выделенного объекта
            if category.id == hidden_category.object_shape_category or category.id == self.last_object_shape_category then
                _renderer.RenderTexture(hidden_category.label_selected_back.GetTexture(0), 
                    sf.misc.FloatRect(x - width/2, y, width, hidden_category.font.PixHeight), 
                    sf.misc.IntRect(0, 0, 0, 0), 
                    category.id == hidden_category.object_shape_category and self.label_back_color or self.last_label_back_color)
            end
            
            -- отображаем силуэт
            if category.id == hidden_category.object_shape_category then
                if #category == 0 then
                    hidden_category.object_shape_category = nil
                else
                    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 = category[1]
                    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
            end
            
            local color = hidden_category.params.label_color
            _renderer.RenderString(hidden_category.font, text, x, y, 0, -1, 1, color, 0)
            
            y = y + offy + hidden_category.font.PixHeight
        end
		
		return false
	end,
	
	OnMouseMove = function(_this, _pos, _state, _broadcast)
		if not hidden_category.font or _broadcast then return false end
		
		local font = hidden_category.font
		local label_area = hidden_category.params.label_area
	
		-- определяем, силуэт какого объекта надо показывать
        local categories_count = #hidden_category.categories
		local x, y = (label_area.x2 + label_area.x)/2, label_area.y
        local offy = ((label_area.y2 - label_area.y) - categories_count * hidden_category.font.PixHeight)/(categories_count + 1)
        y = y + offy
		local categories = hidden_category.categories
        for _, category in ipairs(categories) do
            local settings = hidden_category.categories_settings[category.id]
            local text = settings.text
            text = sf.misc.g_StringTable.Instance().FormatByStringsID(text)
            text = qe.StringFormat(text, category.found, category.left_to_find + category.found)
            
            local width = hidden_category.font.GetStringWidth(text)
            local height = hidden_category.font.PixHeight
                        
            if _pos.X >= x - width/2 and _pos.X <= x + width/2 and _pos.Y >= y and _pos.Y <= y + height then
                -- попали
                hidden_category.lua_widget._InitSelectEffect(category.id)
                return false
            end
            
            y = y + offy + hidden_category.font.PixHeight
        end
		
		hidden_category.lua_widget.select_effect = nil
		return false
	end,
	
	-- инициирует эффект ожидания перед выделением объекта
	_InitSelectEffect = function(_category) 
		local self = hidden_category.lua_widget
		
		if hidden_category.object_shape_category == _category or self.effects.select_effect and self.effects.select_effect.category == _category then return end
		
		-- эффекта плавного появления подложки
		self.effects.select_effect = 
		{
			category = _category,
			time = hidden_category.timer.Get().GetTime() + hidden_category.params.label_select_delay,
			
			Update = function(_self)
				if _self.time > hidden_category.timer.Get().GetTime() then return end
				
				local widget = hidden_category.lua_widget
				
				-- инциализируем начальное состояние эффекта плавного появления подложки
				hidden_category.object_shape_category = _self.category
				widget.label_back_color = sf.graphics.Color(0, 255, 255, 255)
				widget.object_shape_color = sf.graphics.Color(0, 0, 0, 0)
				
				-- создаем эффект плавного появления подложки на новом месте и исчезновения со старого
				hidden_category.lua_widget.effects.label_back_appear = 
				{
					time = hidden_category.timer.Get().GetTime() + hidden_category.params.select_appear_time,
					
					Update = function(_self)
						if _self.time > hidden_category.timer.Get().GetTime() then 
							-- интерполируем альфу 
							local k = (_self.time - hidden_category.timer.Get().GetTime())/hidden_category.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_category = nil
						return true
					end
				}
				
				return true
			end
		}
	end,
	
	effects = {},
	
	_actions = 
	{
		hog_close_button = 
		{
			pressed = function()
				hidden_category.Close()
				return true
			end
		}
	},
}

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