class "Profile"

function Profile:__init(name, save_path)
	self.name_ = name
	self.save_path_ = save_path
	self.level_to_load_ = ""
	self.active_ = false
	--self.actual_objects_ = {}
		
	self.found_objects_ = {}
    self.actual_tasks_ = {}
    self.game_state_ = {}
	self.functions_to_call_on_start_ = {}
	self.hint_delay_ = 0
	self.sound_volume = GetGameApp():GetSoundVolume()
	self.music_volume = GetGameApp():GetMusicVolume()
	self.fullscreen = GetGameApp():IsFullScreen()	
	self.custom_cursor_on = GetGameApp():IsCustomCursorVisible()
    
    self.cigars_found = {}
    for i = 1, GetLevelsCount() do
        self.cigars_found[i] = false
    end
	
	self.cigars_flying = {}
	for i = 1, GetLevelsCount() do
        self.cigars_flying[i] = false
    end
    
    self.meta_object_just_found = nil
    self.last_level_completed_ = ""
	self.is_missed_cigar_tutorial_shown = false
    self.is_meta_game_proceed_dialog_was_shown = false
    self.is_meta_game_complete_dialog_was_shown = false
    
    self.show_error_message_ = false
end

function Profile:OnRestart()
	self.level_to_load_ = ""
	self.found_objects_ = {}
    self.actual_tasks_ = {}
    self.game_state_ = {}
	self.functions_to_call_on_start_ = {}
	self.metaobject_just_found = nil
    self.last_level_completed_ = ""
	self.cigars_found = {}
	self.is_missed_cigar_tutorial_shown = false
	self.hint_delay_ = 0
	self.is_meta_game_proceed_dialog_was_shown = false
	self.is_meta_game_complete_dialog_was_shown = false
	self.show_error_message_ = false
end

function Profile:SetGameState(game_state)
    self.game_state_ = game_state
end

function Profile:AddFoundObject(object_name)
	table.insert(self.found_objects_, object_name)
end

function Profile:GetLevelToLoad()
	return self.level_to_load_
end

function Profile:SetLevelToLoad(level_to_load)
	self.level_to_load_ = level_to_load
end

function Profile:GetName()
	return self.name_
end

function Profile:GetSavePath()
	return self.save_path_
end

function Profile:AddStateChanger(function_name, silent)
	table.insert(self.functions_to_call_on_start_, function_name)	
	if silent == nil then
	    OnSceneStateChanged(play_field)
    end
end

function Profile:CallsFunction(name)
	if self.functions_to_call_on_start_ then
		for i, v in pairs(self.functions_to_call_on_start_) do
			if v == name then
				return true
			end
		end
	end
	return false
end

function toboolean(X)
    if type(X) == 'string' then
        if X == 'true' then
            return true
        else
            return false
        end
    end
    return not not X
end

function Profile:Load(element)
	self.name_ = element:GetAttribute("Name").value;
	self.save_path_ = element:GetAttribute("SavePath").value;
	self.last_level_completed_ = element:GetAttribute("LastLevelCompleted").value
	self.hint_delay_ = tonumber(element:GetAttribute("HintDelay").value)
	self.is_missed_cigar_tutorial_shown = toboolean(element:GetAttribute("IsMissedCigarTutorialShown").value)
    self.is_meta_game_proceed_dialog_was_shown = toboolean(element:GetAttribute("IsMetaGameProceedDialogWasShown").value)
    self.is_meta_game_complete_dialog_was_shown = toboolean(element:GetAttribute("IsMetaGameCompleteDialogWasShown").value)
		
	local elements = element:GetElements();
	for i = 0, elements:size() - 1 do
	    self:LoadOptions(elements:get(i))
	end

	self.level_to_load_ = element:GetAttribute("LevelToLoad").value;

	local save_doc = XMLDocument("");
	save_doc:Load(self.save_path_);
	local save_root = save_doc:GetRootElement();
	self.show_error_message_ = (GetChecksum(save_root) ~= tonumber(element:GetAttribute("Checksum").value))
		
	local error_id = save_doc:GetErrorId()
    
    if error_id ~= TIXML_ERROR_OPENING_FILE and error_id ~= TIXML_NO_ERROR then	   
	   self.show_error_message_ = true
	end
	
	local elements = save_root:GetElements()
	for i = 0, elements:size() - 1 do
		self:LoadOptions(elements:get(i))
		self:LoadTasks(elements:get(i))
		--self:LoadObjects(elements:get(i))
		self:LoadFoundObjects(elements:get(i))
		self:LoadGameState(elements:get(i))
		self:LoadStateChangers(elements:get(i))
		self:LoadMetaObjects(elements:get(i))
	end	
	
	self:SetActive(toboolean(element:GetAttribute("IsActive").value))
	if self.active_ then
	    GetProfileManager():SetCurrentProfile(self.name_)
	end
end

function Profile:Save(element)
	element:AddAttribute("Name", self.name_);
	element:AddAttribute("SavePath", self.save_path_);
	element:AddAttribute("IsActive", tostring(self.active_));
	element:AddAttribute("LevelToLoad", self.level_to_load_);
	element:AddAttribute("HintDelay", tostring(self.hint_delay_))
	element:AddAttribute("LastLevelCompleted", self.last_level_completed_)
	element:AddAttribute("IsMissedCigarTutorialShown", tostring(self.is_missed_cigar_tutorial_shown))
	element:AddAttribute("IsMetaGameProceedDialogWasShown", tostring(self.is_meta_game_proceed_dialog_was_shown))
	element:AddAttribute("IsMetaGameCompleteDialogWasShown", tostring(self.is_meta_game_complete_dialog_was_shown))
	
	self:SaveOptions(element);

--TODO: Uncomment for save
	local save_doc = XMLDocument(self.save_path_);
	local save_root = save_doc:GetRootElement();
	save_root:SetName("Profile");

	self:SaveTasks(save_root);
	--self:SaveObjects(save_root);
	self:SaveFoundObjects(save_root)
	self:SaveGameState(save_root)
	self:SaveStateChangers(save_root)	
	
	self:SaveMetaObjects(save_root)	
        
    element:AddAttribute("Checksum", tostring(GetChecksum(save_root)))
    
	save_doc:Save(self.save_path_);
end

function Profile:IsActive()
    return self.active_
end

function Profile:SetActive(active)
    self.active_ = active
    if active then
	    GetGameApp():SetSoundVolume(self.sound_volume)
	    GetGameApp():SetMusicVolume(self.music_volume)
	    if GetGameApp():IsFullScreen() ~= self.fullscreen then
	        GetGameApp():SetFullScreen(self.fullscreen)
	    end
		GetGameApp():SetCustomCursorVisible(self.custom_cursor_on)
		if menu then
		    local all_cigars_found = true
		    for i, v in ipairs(self.cigars_found) do
		        all_cigars_found = all_cigars_found and v
		    end
		    if all_cigars_found then
		        menu.reward_button:Go()
                menu:GetObject("Chains"):Hide()
		    else
		        menu.reward_button:SetAngle(0)
		        menu.reward_button:Stop()
                menu:GetObject("Chains"):Show()
		    end
		end
	end
end

function Profile:__tostring()
	return self.name_
end

function Profile:SetActualTasks(task_list)
    self.actual_tasks_ = {}
	for i = 0, task_list:size() - 1 do
		table.insert(self.actual_tasks_, task_list:get(i))
	end    
end

--[[function Profile:SetActualObjects(object_list)
	for i = 0, object_list:size() - 1 do
		local go = object_list:get(i)
		local info = {}
		info.x = go:GetX();
		info.y = go:GetY();
		info.z = go:GetZ();
		info.layer = go:GetLayer();
		info.angle = go:GetAngle();
		info.opacity = go:GetOpacity();
		info.scaleX = go:GetScaleX();
		info.scaleY = go:GetScaleY();		
		info.isActive = go:IsActive();
		info.isHidden = go:IsHidden();
		info.isPaused = go:IsPaused();
		info.isVisible = go:IsVisible();
		self.actual_objects_[go:GetName()] = info;
	end
end]]

function Profile:SaveTasks(element)
    --self:SetActualTasks(play_field:GetLevelScreen():GetTasks())    
	local tasksRoot = XMLElement("tasks");
	for i, v in ipairs(self.actual_tasks_) do
	    local taskElement = XMLElement("task");
	    local task = v
		taskElement:AddAttribute("singular", task:GetTextSingular());
		taskElement:AddAttribute("plural", task:GetTextPlural());
        for j = 0, v:GetObjectNames():size() - 1 do
            local objectElement = XMLElement("object");
			objectElement:AddAttribute("name", v:GetObjectNames():get(j));
			taskElement:AddElement(objectElement);
        end		
		tasksRoot:AddElement(taskElement);
	end	
	element:AddElement(tasksRoot);
end

function Profile:SaveFoundObjects(element)
	local objectsRoot = XMLElement("found_objects");
	for i, v in pairs(self.found_objects_) do	    
		local objectElement = XMLElement("found_object");		
		objectElement:AddAttribute("name", v);		
		objectsRoot:AddElement(objectElement);
	end	
	element:AddElement(objectsRoot);
end

--[[function Profile:SaveObjects(element)	
	local objectsRoot = XMLElement("objects");
	for i, v in pairs(self.actual_objects_) do
	    local info = v;
		local objectElement = XMLElement("object");
		objectElement:AddAttribute("x", tostring(info.x));
		objectElement:AddAttribute("y", tostring(info.y));
		objectElement:AddAttribute("z", tostring(info.z));
		objectElement:AddAttribute("layer", tostring(info.layer));
		objectElement:AddAttribute("scaleX", tostring(info.scaleX));
		objectElement:AddAttribute("scaleY", tostring(info.scaleY));
		objectElement:AddAttribute("angle", tostring(info.angle));
		objectElement:AddAttribute("name", i);
		objectElement:AddAttribute("opacity", tostring(info.opacity));
		objectElement:AddAttribute("isHidden", tostring(info.isHidden));
		objectElement:AddAttribute("isPaused", tostring(info.isPaused));
		objectElement:AddAttribute("isActive", tostring(info.isActive));
		objectElement:AddAttribute("isVisible", tostring(info.isVisible));
		objectsRoot:AddElement(objectElement);
	end	
	element:AddElement(objectsRoot);
end]]

local Contains = function(t, val)
    for i, v in ipairs(t) do
        if v == val then
            return true
        end
    end
    return false
end

function Profile:SaveGameState(element)
	local ignored = {}
	table.insert(ignored, 'zones')
	--TODO: fix it by placing all the chains into the container
	table.insert(ignored, 'chain1')
	table.insert(ignored, 'chain2')
	table.insert(ignored, 'chain3')
	table.insert(ignored, 'chain4')
	table.insert(ignored, 'chain5')
	table.insert(ignored, 'chain6')
	table.insert(ignored, 'chain7')
	table.insert(ignored, 'actual_objects')
	table.insert(ignored, 'tools')
	table.insert(ignored, 'sheriffs_eye_positions')
	table.insert(ignored, 'locker')
	--In game state only some parameters should be saved
	table.insert(ignored, 'inventory_zones')
	local gameStateRoot = XMLElement("game_state");
    for i, v in pairs(self.game_state_) do
        if not Contains(ignored, i) then
			self:SaveData(i, v, gameStateRoot)
		elseif i == 'inventory_zones' then
            local inventoryZonesRoot = XMLElement('table')
            inventoryZonesRoot:AddAttribute('id', 'inventory_zones')
            for k, w in ipairs(self.game_state_.inventory_zones) do
                
                local inventoryZoneElement = XMLElement('table')
                inventoryZoneElement:AddAttribute('id', tostring(k))
                
                local lockedElement = XMLElement("boolean");
                lockedElement:AddAttribute("id", "locked")
                lockedElement:AddAttribute("value", tostring(w.locked))
                inventoryZoneElement:AddElement(lockedElement)
                                    
                local item1FoundElement = XMLElement("boolean");
                item1FoundElement:AddAttribute("id", "item_1_found")
                item1FoundElement:AddAttribute("value", tostring(w.item_1_found))
                inventoryZoneElement:AddElement(item1FoundElement)
                                    
                local item2FoundElement = XMLElement("boolean");
                item2FoundElement:AddAttribute("id", "item_2_found")
                item2FoundElement:AddAttribute("value", tostring(w.item_2_found))
                inventoryZoneElement:AddElement(item2FoundElement)
                                    
                local item3FoundElement = XMLElement("boolean");
                item3FoundElement:AddAttribute("id", "item_3_found")
                item3FoundElement:AddAttribute("value", tostring(w.item_3_found))
                inventoryZoneElement:AddElement(item3FoundElement)
                
                local itemsToUseElement = XMLElement("number");
                itemsToUseElement:AddAttribute("id", "items_to_use")
                itemsToUseElement:AddAttribute("value", tostring(w.items_to_use))
                inventoryZoneElement:AddElement(itemsToUseElement)
                                    
                inventoryZonesRoot:AddElement(inventoryZoneElement)
            end
            gameStateRoot:AddElement(inventoryZonesRoot)		  
        --[[elseif i == 'locker' then
            local lockerRoot = XMLElement('locker')
            lockerRoot:AddAttribute('nreferences', tostring(v:GetNReferences()))
            gameStateRoot:AddElement(lockerRoot)		  ]]
		end
    end
    element:AddElement(gameStateRoot)
end

function Profile:SaveData(name, value, element)
	local t = type(value)
	local subElement = XMLElement(t)
	subElement:AddAttribute("id", tostring(name))
    if t ~= 'table' and t ~= 'userdata' and t ~= 'function' then
        subElement:AddAttribute("value", tostring(value))
    else
        if t == 'table' then
            for i, v in pairs(value) do
                self:SaveData(i, v, subElement)                
            end
        else
            return
        end
    end
    element:AddElement(subElement)    
end

function Profile:LoadGameState(element)
    if (element:GetName() ~= "game_state") then
		return;
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do
	    self:LoadData(elements:get(i), self.game_state_)
	    --self:LoadLocker(elements:get(i), self.game_state_)
	end
end

function Profile:LoadStateChangers(element)
	if element:GetName() ~= "state_changers" then
		return
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do
	    self:AddStateChanger(elements:get(i):GetAttribute('name').value, true)
	end
end

function Profile:SaveStateChangers(element)
	local stateChangersRoot = XMLElement("state_changers");
	for i, v in pairs(self.functions_to_call_on_start_) do	    
		local stateChangerElement = XMLElement("state_changer");		
		stateChangerElement:AddAttribute("name", v);		
		stateChangersRoot:AddElement(stateChangerElement);
	end	
	element:AddElement(stateChangersRoot);
end

function Profile:LoadData(element, node)
     if element:HasAttribute('value') then
        local string_val = element:GetAttribute('value').value;
        local t = element:GetName();
        
        local key = element:GetAttribute('id').value
        local numkey = tonumber(key)
        if numkey ~= nil then
            key = numkey
        end
        
        if t == 'number' then
            node[key] = tonumber(element:GetAttribute('value').value);
        else 
            if t == 'boolean' then
                node[key] = toboolean(element:GetAttribute('value').value);
            else
                node[key] = element:GetAttribute('value').value; 
            end
        end        
    elseif element:HasAttribute('id') then
        local key = element:GetAttribute('id').value
        local numkey = tonumber(key)
        if numkey ~= nil then
            key = numkey
        end
        node[key] = {}
        local elements = element:GetElements()
        for i = 0, elements:size() - 1 do
            self:LoadData(elements:get(i), node[key])
        end
    end
end

function Profile:LoadLocker(element, node)
    if (element:GetName() ~= "locker") then
		return;
	end
    node.locker = ReferenceCounter()
    node.locker.nreferences = tonumber(element:GetAttribute('nreferences').value)
end

function Profile:LoadTasks(element)
    if (element:GetName() ~= "tasks") then
		return;
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do	
	    local taskElement = elements:get(i)
	    local task = Task(taskElement:GetAttribute("singular").value,
						  taskElement:GetAttribute("plural").value)		
        
		local task_elements = taskElement:GetElements()
        for j = 0, task_elements:size() - 1 do
            task:AddObject(task_elements:get(j):GetAttribute("name").value)
        end
        table.insert(self.actual_tasks_, task)
	end
end

function Profile:LoadOptions(element)
    if (element:GetName() ~= "options") then
		return;
	end
	if element:HasAttribute("SoundVolume") then
		self.sound_volume = tonumber(element:GetAttribute("SoundVolume").value)
	end
	if element:HasAttribute("MusicVolume") then
		self.music_volume = tonumber(element:GetAttribute("MusicVolume").value)
	end
	if element:HasAttribute("FullScreen") then
		self.fullscreen = toboolean(element:GetAttribute("FullScreen").value)
	end
	if element:HasAttribute("CustomCursor") then
		self.custom_cursor_on = toboolean(element:GetAttribute("CustomCursor").value)
	end	
end

function Profile:SaveOptions(element)
    --self:SetActualTasks(play_field:GetLevelScreen():GetTasks())    
	local optionsRoot = XMLElement("options");
	optionsRoot:AddAttribute("SoundVolume", tostring(self.sound_volume));
	optionsRoot:AddAttribute("MusicVolume", tostring(self.music_volume));
	optionsRoot:AddAttribute("FullScreen", tostring(self.fullscreen));
	optionsRoot:AddAttribute("CustomCursor", tostring(self.custom_cursor_on));
		
	element:AddElement(optionsRoot);
end

--[[function Profile:LoadObjects(element)
    if (element:GetName() ~= "objects") then
		return;
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do
	    local objectElement = elements:get(i)
	    local info = {};
		info.x = objectElement:GetAttribute("x").value;
		info.y = objectElement:GetAttribute("y").value;
		info.z = objectElement:GetAttribute("z").value;
		info.layer = objectElement:GetAttribute("layer").value;
		info.scaleX = objectElement:GetAttribute("scaleX").value;
		info.scaleY = objectElement:GetAttribute("scaleY").value;
		info.angle = objectElement:GetAttribute("angle").value;
		info.opacity = objectElement:GetAttribute("opacity").value;
		info.isHidden = objectElement:GetAttribute("isHidden").value;
		info.isPaused = objectElement:GetAttribute("isPaused").value;
		info.isActive = objectElement:GetAttribute("isActive").value;
		info.isVisible = objectElement:GetAttribute("isVisible").value;
		self.actual_objects_[objectElement:GetAttribute("name").value] = info;
	end
end]]

function Profile:LoadFoundObjects(element)
    if (element:GetName() ~= "found_objects") then
		return;
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do
	    local objectElement = elements:get(i)
		self:AddFoundObject(objectElement:GetAttribute("name").value)	    
	end
end

function Profile:SaveMetaObjects(element)
	local metaObjectsRoot = XMLElement("meta_objects");
	for i, v in ipairs(self.cigars_found) do
		local metaObjectElement = XMLElement("meta_object");		
		metaObjectElement:AddAttribute("level", tostring(i));
		self.cigars_found[i] = v or self.cigars_flying[i]
		metaObjectElement:AddAttribute("found", tostring(v or self.cigars_flying[i]));
		metaObjectsRoot:AddElement(metaObjectElement);
	end
	
	element:AddElement(metaObjectsRoot);
end

function Profile:LoadMetaObjects(element)
	if (element:GetName() ~= "meta_objects") then
		return;
	end
	local elements = element:GetElements()
	for i = 0, elements:size() - 1 do	
	    local metaObjectElement = elements:get(i)
		self.cigars_found[tonumber(elements:get(i):GetAttribute("level").value)] = toboolean(elements:get(i):GetAttribute("found").value)	    
	end
end