-- особый механизм хинтов для game
game_level_hints = {}

-- поисания хинтов для всех уровней должны находиться в scripts\game\hints\
game_level_hints.levels = {}
load_lua_scripts('scripts\\game\\hints\\')

function game_level_hints.Init()
	local level_id = tostring(quest.global_played_level.GetId())
	game_level_hints.hints_table = game_level_hints.levels[level_id]
	if not game_level_hints.hints_table then 
		game_level_hints._StandardInit()
		return
	end
	
	if level_hints._generate_hints then
		game_level_hints.hints_table = nil
		quest.global_quest_window.EndModal(2)
		return
	end
	
	local last_globals, env = {}, getfenv(0)
	for name,func in pairs(game_level_hints.funcs) do
		last_globals[name] = env[name]
		env[name] = func
	end
	game_level_hints.hints_table = game_level_hints.hints_table()
	for name,func in pairs(last_globals) do
		env[name] = last_globals[name]
	end
	return true
end

function game_level_hints.ShowHint()
	if not game_level_hints.hints_table then 
		game_level_hints._StandardShowHint()
		return
	end

	local context = {}
	context.hint = true
	local result = game_level_hints._WalkHintTable(context)
	if result then hint_to_object(result.obj, result.restore_hint) end
end

function game_level_hints.GetPossibleTakeObjects()
	if not game_level_hints.hints_table then 
		return game_level_hints._StandardGetPossibleTakeObjects()
	end

	local context = {}
	context.take_objects = {}
	game_level_hints._WalkHintTable(context)

	local result = {}
	for _,id in ipairs(context.take_objects) do
		local obj = quest.find_object_by_id(id, false, false)
		__assert(obj)
		table.insert(result, obj)
	end
	return result
end

function game_level_hints.GetCursorsTable()
	if not game_level_hints.hints_table then 
		return game_level_hints._StandardGetCursorsTable()
	end

	local context = {}
	context.action_objects = {}
	context.action_objects[qe.CLevelHints.ActionCanApply] = {}
	context.action_objects[qe.CLevelHints.ActionMinigame] = {}
	context.action_objects[qe.CLevelHints.ActionMiniscene] = {}
	context.action_objects[qe.CLevelHints.ActionCompleteLevel] = {}

	game_level_hints._WalkHintTable(context)
	
	local cursors = {}
	
	local obj_in_cursor = quest.global_cursor.GetCursorObject()
	if obj_in_cursor then
		local obj_id = obj_in_cursor.GetIdRef().c_str()
		--__message(obj_id, "in cursor")
		local cursor = level_hints._cursors[qe.CLevelHints.ActionCanApply]
		cursors[cursor] = {}
		for _,pair in ipairs(context.action_objects[qe.CLevelHints.ActionCanApply]) do
			if pair[1] == obj_id then
				--__message("can click on "..pair[2])
				cursors[cursor][pair[2]] = true
			end
		end
	else
		local cursor = level_hints._cursors[qe.CLevelHints.ActionMinigame]
		cursors[cursor] = {}
		for _,pair in ipairs(context.action_objects[qe.CLevelHints.ActionMinigame]) do
			--__message("can click on "..pair[2])
			cursors[cursor][pair[1]] = true
		end
		
		local cursor = level_hints._cursors[qe.CLevelHints.ActionMiniscene]
		cursors[cursor] = cursors[cursor] or {}
		for _,pair in ipairs(context.action_objects[qe.CLevelHints.ActionMiniscene]) do
			--__message("can click on "..pair[2])
			cursors[cursor][pair[1]] = true
		end
		
		local cursor = level_hints._cursors[qe.CLevelHints.ActionCompleteLevel]
		cursors[cursor] = cursors[cursor] or {}
		for _,pair in ipairs(context.action_objects[qe.CLevelHints.ActionCompleteLevel]) do
			--__message("can click on "..pair[2])
			cursors[cursor][pair[1]] = true
		end
	end
	return cursors
end

function game_level_hints._WalkHintTable(_context)
	for _,action in ipairs(game_level_hints.hints_table) do
		local result = action:Apply(_context)
		if result then
			__assert(type(result)=="table")
            __assert(result.obj)
			return result
		end
	end
end

function game_level_hints._IsObjectEnabled(_id)
	local obj = quest.find_object_by_id(_id, false, false)
	if not obj or sf.misc.BitwiseAnd(obj.GetObjectFlags(), __or(qe.CBaseSceneObject.ObjectFlagHidden, qe.CBaseSceneObject.ObjectFlagDisable))~=0 then
		return false
	end
	return true
end

function game_level_hints._ApplyActions(_actions, _context)
	if _actions.Apply then
		return _actions:Apply(_context)
	else
		for _,a in ipairs(_actions) do
			local result = a:Apply(_context)
			if result then return result end
		end					
	end
end

-- заменяем стандартный вариант хинтов на game
for _,func in ipairs({"Init", "ShowHint", "GetPossibleTakeObjects", "GetCursorsTable"}) do
	game_level_hints["_Standard"..func] = level_hints[func]
	level_hints[func] = game_level_hints[func]
end

-- функции для описания действий на уровне
game_level_hints.funcs = {}

-- IF
function game_level_hints.funcs.IF(_condition, _action)
	return { condition = _condition, action = _action, 
		Apply = function(self, _context) 
			local cond_result = false
			if self.condition.Apply then
				cond_result = self.condition:Apply(_context) 
			else
				cond_result = true
				for _,c in ipairs(self.condition) do
					if not c:Apply(_context) then 
						cond_result = false
						break
					end
				end
			end
				
			if cond_result then 
				assert(type(self.action)=="table")
				return game_level_hints._ApplyActions(self.action, _context)
			end
		end
	}
end

-- scene
function game_level_hints.funcs.scene(_id)
	return { scene = _id,
		Apply = function(self)
			local widget = quest.get_scene_widget(self.scene)
			return widget~=nil and sf.misc.BitwiseAnd(widget.GetFlags(), widget.FlagDead)==0
		end
	}
end

-- state
function game_level_hints.funcs.state(_id, ...)
	return { object = _id, states = {(...)},
		Apply = function(self)
			for _,s in ipairs(self.states) do
				if state(self.object, s) then return true end
			end
		end 
	}
end

-- state_in_inv
function game_level_hints.funcs.state_in_inv(_id, ...)
	return { object = _id, states = {(...)},
		Apply = function(self)
			for _,s in ipairs(self.states) do
				if is_object_in_inventory(_id, s) then return true end
			end
		end 
	}
end

-- on_level
function game_level_hints.funcs.on_level(_id)
	return { object = _id,
		Apply = function(self)
			return is_object_on_level(self.object)
		end 
	}
end

-- in_inv
function game_level_hints.funcs.in_inv(_id)
	return { object = _id,
		Apply = function(self)
			return is_object_in_inventory(self.object)
		end
	}
end

-- removed
function game_level_hints.funcs.removed(_id)
	return { object = _id,
		Apply = function(self)
			local obj = quest.find_object_by_id(self.object, false, true)
			return obj and obj.ReadObjectFlag(obj.ObjectFlagRemoved) and not is_object_in_inventory(self.object)
		end
	}
end


-- disabled
function game_level_hints.funcs.disabled(_id)
	return { object = _id,
		Apply = function(self)
			local obj = quest.find_object_by_id(_id, false, false)
            return obj and sf.misc.BitwiseAnd(obj.GetObjectFlags(), qe.CBaseSceneObject.ObjectFlagDisable)~=0
		end
	}
end

-- hidden
function game_level_hints.funcs.hidden(_id)
	return { object = _id,
		Apply = function(self)
			local obj = quest.find_object_by_id(_id, false, false)
            return obj and sf.misc.BitwiseAnd(obj.GetObjectFlags(), qe.CBaseSceneObject.ObjectFlagHidden)~=0
		end
	}
end

-- disabled_in_hog_list
function game_level_hints.funcs.disabled_in_hog_list(_id)
	return { object = _id,
		Apply = function(self)
			local obj = quest.find_object_by_id(self.object, false, false)
            -- obj должен быть хог-объектом, находится в списке, быть disabled и при этом не hidden
            return obj 
                and (hidden_object._IsHOGObject(obj) or hidden_object._IsLastHOGObject(obj)) 
                and hidden_object._ExtractUserData(obj) and __and(obj.GetObjectFlags(), qe.CBaseSceneObject.ObjectFlagDisable)~=0
                and __and(obj.GetObjectFlags(), qe.CBaseSceneObject.ObjectFlagHidden)==0
		end
	}
end

-- task_completed
function game_level_hints.funcs.task_completed(_id)
	return { task = _id,
		Apply = function(self)
			return is_task_completed(self.task)
		end
	}
end

-- in_cursor
function game_level_hints.funcs.in_cursor(_id)
	return { object = _id,
		Apply = function(self)
            return cursor(self.object)
		end
	}
end

-- NOT
function game_level_hints.funcs.NOT(_action)
	return { action = _action,
		Apply = function(self, _context)
			return not self.action:Apply(_context)
		end
	}
end

-- miniscenes
function game_level_hints.funcs.miniscenes(_scenes)
	local scenes = {}
	for id,actions in pairs(_scenes) do
		scenes[id] = 
		{
			action = actions,
			
			Apply = function(self, _context)
				return game_level_hints._ApplyActions(self.action, _context)
			end
		}
	end
	
	return { scenes = scenes, 
		Apply = function(self, _context) 		
			assert(type(self.scenes)=="table")			
			_context.miniscenes = self.scenes
			
			if _context.action_objects then
				for _,actions in pairs(self.scenes) do
					actions:Apply(_context)
				end
			end
		end
	}
end

-- on_scene
function game_level_hints.funcs.on_scene(_scene, _action)
	return { scene = _scene, action = _action,
		Apply = function(self, _context)
        
            -- проверяем, что on_scene не зациклились
            -- также, если мы уже проверяли эту сцену и её нельзя открыть, то выходим
            if not _context.cannot_open_scene then _context.cannot_open_scene = {} end
            if _context.scenes_stack and _context.scenes_stack[self.scene] or _context.cannot_open_scene[self.scene]  
            then
                return
            end
 
            -- помещаем сцену в стек
            if not _context.scenes_stack then
                _context.scenes_stack = {}
            end
            table.insert(_context.scenes_stack, self.scene)
            _context.scenes_stack[self.scene] = true
        
            -- проверяем, открыта ли сцена
			local widget = quest.get_scene_widget(self.scene)
			local scene_open = widget~=nil and sf.misc.BitwiseAnd(widget.GetFlags(), widget.FlagDead)==0
            if widget~=nil then
                if widget.IsDead() then
					scene_open = false
				end
            end
			
			local result = nil
            -- если сцена открыта, то проходим по действиям на ней
            -- если сцена не открыта, то все равно проходим по её действиям; если доступные действия есть,
            -- то значит сцену надо будет открыть
			if scene_open or _context.hint then 
				result = game_level_hints._ApplyActions(self.action, _context)
			end
			
            -- если сцена не открыта, и
            --      мы ищем объекты для взятия и возможные курсоры
            --      или
            --      мы ищем первое доступное действие и у нас есть доступные действия на закрытой сцене
            -- то проходимся по действиям для открытия сцены
			if not scene_open and (not _context.hint or result) then
				__assert(_context.miniscenes)
				result = _context.miniscenes[self.scene] and _context.miniscenes[self.scene]:Apply(_context)
                
                -- отмечаем результаты попытки открытия сцены в табличке
                -- результаты повторных попыток во время одного обхода хинтов должны совпадать
                __assert(_context.cannot_open_scene[self.scene] == nil 
                    or _context.cannot_open_scene[self.scene] == not result)
                _context.cannot_open_scene[self.scene] = not result
			end
            
            -- убираем сцену из стека
            table.remove(_context.scenes_stack)
            _context.scenes_stack[self.scene] = nil
			return result
		end
	}
end

-- hog_hints
function game_level_hints.funcs.hog_hints(_scene, _action)
    return { action = _action, 
        Apply = function(self, _context)
            if not _context.hint or _scene ~= hidden_object.GetActiveScene() then return end
            
            local result
            if self.action then 
                result = game_level_hints._ApplyActions(self.action, _context)
            end
            
            if not result then
                local hint_objs = {}
                if hidden_object.current_objects then
                    local objects = hidden_object.current_objects
                    for row = 1, objects.rows do
                        for col = 1, objects.cols do
                            for _,o in pairs(objects[row][col] or {}) do
                                table.insert(hint_objs, o)
                            end
                        end
                    end
                elseif hidden_object.categories then
                    for _, category in ipairs(hidden_object.categories) do
                        for _, obj in ipairs(category) do
                            table.insert(hint_objs, obj)
                        end
                    end
                end
                 
                if #hint_objs > 0 then
                   return hint_objs[math.random(#hint_objs)].GetIdRef().c_str()
                end
            end
            
            return result
        end
    }
end

-- take
function game_level_hints.funcs.take(_id, _what)
	return { object = _id, what = _what,
		Apply = function(self, _context) 
			if not is_object_on_level(self.object) 
            or not self.what and (not game_level_hints._IsObjectEnabled(self.object) or cursor(self.object))
			or self.what and (not is_object_on_level(self.what) or not game_level_hints._IsObjectEnabled(self.what))
            then 
                return 
            end
			
			if _context.take_objects then table.insert(_context.take_objects, self.object) 
			elseif _context.hint then return { obj = self.object } end
		end
	}
end

-- click, minigame, miniscene, end_level
local function click_base(_what, _with, _action_type, _restore_hint)
	return { object = _what, with = _with, action_type = _action_type, restore_hint = _restore_hint,
		Apply = function(self, _context)
			if not game_level_hints._IsObjectEnabled(self.object)
			or self.with and not is_object_in_inventory(self.with) and not cursor(self.with) then return end
			
			if _context.action_objects then
				table.insert(
					_context.action_objects[self.action_type], 
					self.with and {self.with, self.object} or {self.object})
			elseif _context.hint then
				return { obj = self.object, restore_hint = self.restore_hint }
			end
		end
	}
end

local object_actions = 
{ 
	click = { atype = qe.CLevelHints.ActionCanApply }, 
	minigame = { atype = qe.CLevelHints.ActionMinigame }, 
	miniscene = { atype = qe.CLevelHints.ActionMiniscene, restore_hint = true },
	end_level = { atype = qe.CLevelHints.ActionCompleteLevel, restore_hint = true }
}

for func, desc in pairs(object_actions) do
	game_level_hints.funcs[func] = 
        function(_what, _with) 
            return click_base(_what, _with, desc.atype, desc.restore_hint) 
        end
end
