--
-- FMCMapTriggerV2
--
--
-- based on MapDoorTrigger by Tobias F. (John Deere 6930)
-- author:    	Xentro (www.ls-uk.info)
-- @version:    v2.0 Beta
-- @date:       2012-08-19 (v2.0)
-- @history:    v1.0 - inital implementation
-- 			    v1.2 - improved MP support
--              v1.2b - Fix for loadXMLFile() & createXMLFile() where first argument may not be 'nil' on a MAC.
-- 				v2.0 - new configuration setup
-- 
--

FMCMapTriggerV2 = {};
FMCMapTriggerV2.isLoaded = false;
FMCMapTriggerV2.triggers = {};

local Server_sendObjects_old = Server.sendObjects;

function Server:sendObjects(connection, x,y,z, viewDistanceCoeff)
	for _, trigger in pairs(FMCMapTriggerV2.triggers) do
		connection:sendEvent(setAnimationEventV2:new(trigger, trigger.animationState));
	end;
    Server_sendObjects_old(self, connection, x,y,z, viewDistanceCoeff);
end;

addModEventListener(FMCMapTriggerV2);

function FMCMapTriggerV2:loadMap(name)
    if not FMCMapTriggerV2.isLoaded then
		print("FMCMapTriggerV2 loaded");
	
		local xmlPath = SampleModMap.currentModDirectory .. "FMCMapTriggers.xml"
		local xmlFile = loadXMLFile("FMCMapTriggersXML", xmlPath);
		local worldRootNode = getChildAt(getRootNode(), 4);  -- 4 is the map
		
		local i = 0;
		while true do
			local path = string.format("FMCMapTriggers.trigger(%d)", i);
			if not hasXMLProperty(xmlFile, path) then break; end;
			
			local id = getXMLInt(xmlFile, path .. "#id");
			
			if id ~= nil then		
				local v = FMCMapTriggerV2.triggers[id];
				if v ~= nil then
					if v.isLoaded == nil then
						local inputKey = getXMLString(xmlFile, path .. ".input#inputKey");
						local positivText = getXMLString(xmlFile, path .. ".input#positivText");
						local negativText = getXMLString(xmlFile, path .. ".input#negativText");
						
						if InputBinding[inputKey] ~= nil and g_i18n:hasText(positivText) and g_i18n:hasText(negativText) then
							v.inputKey = InputBinding[inputKey];
							v.animTextNamePos = positivText;
							v.animTextNameNeg = negativText;
						end;
						
						local animationNode = Utils.indexToObject(worldRootNode, getXMLString(xmlFile, path .. ".animation#index"));
						if animationNode ~= nil and animationNode ~= 0 then
							v.animCharSet = 0;
							v.animCharSet = getAnimCharacterSet(animationNode);
							if v.animCharSet ~= 0 then
								local clip = getAnimClipIndex(v.animCharSet, getXMLString(xmlFile, path .. ".animation#clipName"));
								if clip ~= nil then
									assignAnimTrackClip(v.animCharSet, 0, clip);
									if getAnimTrackAssignedClip(v.animCharSet, 0) then
										setAnimTrackLoopState(v.animCharSet, 0, false);
										v.speedScale = Utils.getNoNil(getXMLFloat(xmlFile, path .. ".animation#speedScale"), 15);
										v.animDuration = getAnimClipDuration(v.animCharSet, clip);
										
										if g_client then
											local animSound = getXMLString(xmlFile, path .. ".animationSound#file");
											if animSound ~= nil then
												animSound = Utils.getFilename(animSound, SampleModMap.currentModDirectory);
												v.animSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, path .. ".animationSound#volume"), 1);
												v.animSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, path .. ".animationSound#outerRadius"), 50);
												v.animSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, path .. ".animationSound#innerRadius"), 10);
												v.clientAnimationSound = createAudioSource("clientAnimationSound", animSound, v.animSoundRadius, v.animSoundInnerRadius, v.animSoundVolume, 0);
												
												link(animationNode, v.clientAnimationSound);
												setVisibility(v.clientAnimationSound, false);
												
												v.playAnimationSoundEnabled = false;
											end;
										end;
									else
										print("Error: clipName (" .. tostring(clip) .. ") couldn't be assigned");
										break;
									end;
								else
									print("Error: can't find clipName "..tostring(clip).." for id ("..id..")");
									break;
								end;
							else
								print("Error: can't find animation on index "..tostring(animationNode).." for id "..id);
								break;
							end;
						else
							print("Error: animation index is nil for id (" .. tostring(id) .. ").");
							break;
						end;
						
						if g_client then
							local soundPath = getXMLString(xmlFile, path .. ".enterSound#file");
							if soundPath ~= nil then
								local langPath;
								if getXMLBool(xmlFile, path .. ".enterSound#switch") == true then
									soundPath = string.gsub(soundPath, "{lang}", getLanguageName(getSystemLanguage()));
									langPath = Utils.getFilename(soundPath, SampleModMap.currentModDirectory);
								else
									soundPath = string.gsub(soundPath, "{lang}", "English");
									langPath = Utils.getFilename(soundPath, SampleModMap.currentModDirectory);
								end;
								
								if langPath ~= nil then
									v.playSound = createSample("playSound");
									if loadSample(v.playSound, langPath, false) then
										v.langSound = true;
									else
										if getXMLBool(xmlFile, path .. ".enterSound#switch") == true then
											local soundPath = string.gsub(soundPath, "{lang}", "English");
											langPath2 = Utils.getFilename(soundPath, SampleModMap.currentModDirectory);
											if langPath2 ~= nil then
												print("Error: enterSound file path for id (" .. tostring(id) .. ") have been changed tp Default (English)");
												if loadSample(v.playSound, langPath2, false) then
													v.langSound = true;
												else
													print("Error: (attempt 2) enterSound file for id (" .. tostring(id) .. ") couldn't be loaded.");
												end;
											end;
										else
											print("Error: enterSound file for id (" .. tostring(id) .. ") couldn't be loaded.");
										end;
									end;
								else
									print("Error: enterSound file for id (" .. tostring(id) .. ") is not a valid path. path: " .. tostring(soundPath));
								end;
								
								v.playSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, path .. ".enterSound#volume"), 1);
								v.playOnLeave = Utils.getNoNil(getXMLBool(xmlFile, path .. ".enterSound#playOnLeave"), false);
							end;
						end;
						
						-- print(tostring(id) .. " have been loaded.");
						v.isLoaded = true;
						i = i + 1;
					else
						print("Error: the trigger (" .. tostring(id) .. ") have been loaded already. Id must be unique for each trigger.");
						break;
					end;
				else
					print("Error: the trigger (" .. tostring(id) .. ") can't be found.");
					break;
				end;
			else
				break;
			end;
		end;
		
		delete(xmlFile);
        self:loadTriggers();
        FMCMapTriggerV2.isLoaded = true;
    end;
end;

function FMCMapTriggerV2:deleteMap()
    FMCMapTriggerV2.isLoaded = false;
	FMCMapTriggerV2.triggers = {}; -- empty table
end;

function FMCMapTriggerV2:mouseEvent(posX, posY, isDown, isUp, button)
end;

function FMCMapTriggerV2:keyEvent(unicode, sym, modifier, isDown)
end;

function FMCMapTriggerV2:update(dt)
end;

function FMCMapTriggerV2:draw()
	-- for k, v in pairs(FMCMapTriggerV2.triggers) do
		-- if v ~= nil then
			-- g_currentMission:addExtraPrintText("id: "..k.." "..tostring(FMCMapTriggerV2.triggers[k]).." bool: " .. tostring(FMCMapTriggerV2.triggers[k].animationState) .. " animationTime: " .. tostring(FMCMapTriggerV2.triggers[k].animationTime));
		-- end;
	-- end;
end;

function FMCMapTriggerV2:loadTriggers()
    if g_currentMission.missionInfo.isValid then
        local savegamePath = g_currentMission.missionInfo.savegameDirectory.."/FMCMapTriggerV2.XML";
        local xmlFile = loadXMLFile("FMCMapTriggerV2XML", savegamePath);
        local i = 0;
        while true do
            local tag = string.format("FMCMapTriggerV2.trigger(%d)", i);
            local id = getXMLInt(xmlFile, tag.."#id");
            local animationTime = getXMLFloat(xmlFile, tag.."#animationTime");
            local bool = getXMLBool(xmlFile, tag.."#bool");
			local trigger = FMCMapTriggerV2.triggers[id];
			
            if bool == nil or trigger == nil then break; end;
			
            trigger.animationState = bool;
			if animationTime ~= nil then
				trigger.animationTime = animationTime;
			end;
            i = i + 1;
        end;
        delete(xmlFile);
    end;
end;

function FMCMapTriggerV2:saveTriggers()
	local rootTag = "FMCMapTriggerV2";
	local xmlFile = createXMLFile("FMCMapTriggerV2XML", self.savegames[self.selectedIndex].savegameDirectory.."/FMCMapTriggerV2.XML", rootTag);
	local i=0;
	for _,v in pairs(FMCMapTriggerV2.triggers) do
		local tag = string.format(rootTag..".trigger(%d)", i);
		setXMLInt(xmlFile, tag.."#id", v.id);
		setXMLFloat( xmlFile, tag.."#animationTime", v.animationTime);
		setXMLBool( xmlFile, tag.."#bool", v.animationState);
		i = i + 1;
	end;
	saveXMLFile(xmlFile);
	delete(xmlFile);
end;

CareerScreen.saveSelectedGame = Utils.appendedFunction(CareerScreen.saveSelectedGame, FMCMapTriggerV2.saveTriggers);

-- onCreate function --
function FMCAnimation(self, id)
    local instance = FMCTriggerV2:new(g_server ~= nil, g_client ~= nil);
    local index = g_currentMission:addOnCreateLoadedObject(instance);
    instance:load(id);
    instance:register(true);
end;

-- fix initialization bug
if g_gameVersionLocal == nil or g_gameVersionLocal <= 1.023 then
    InitObjectClass = function(classObject, className)
        if g_currentMission ~= nil then -- this is the difference to the original
            print("Error: Object initialization only allowed at compile time");
            printCallstack();
            return;
        end;
        if ObjectIds.objectClasses[className] ~= nil then
            print("Error: Same class name used multiple times "..className);
            printCallstack();
            return;
        end;
        ObjectIds.objectClasses[className] = classObject;
    end;    
end;

FMCTriggerV2 = {};

local FMCTriggerV2_mt = Class(FMCTriggerV2, Object);

InitObjectClass(FMCTriggerV2, "FMCTriggerV2");

function FMCTriggerV2:new(isServer, isClient)
    local self = Object:new(isServer, isClient, FMCTriggerV2_mt);
    self.className = "FMCTriggerV2";
    return self;
end;

function FMCTriggerV2:load(id)
    self.triggerId = id;
	if self.isClient then
		addTrigger(id, "triggerCallback", self);
		self.isEnabled = true;
	end;
	for i=0, getNumOfChildren(id)-1 do
		local child = getChildAt(id, i);
		setCollisionMask(child, 0);
	end;
	self.assignRigidType = true;
	self.assignRigidTypeTimeOut = g_currentMission.time+4000;
	self.playersInTrigger = {};
	self.playersLeavingTrigger = {};
	self.PIT = {};
	
	self.id = getUserAttribute(id, "id");
		
    --[[
	local lightNode = getUserAttribute(id, "lightNode");
	if lightNode ~= nil and string.len(lightNode) >= 1 then
		local rootNode = getChild(id, lightNode)
		if rootNode ~= nil then
			self.light = rootNode;
			setVisibility(self.light, false)
			
			self.lightButtonActivated = Utils.getNoNil(getUserAttribute(id, "lightButtonActivated"), false);
			if self.inputKey ~= nil then self.lightButtonActivated = false; end;
			
			local maxTime = Utils.getNoNil(getUserAttribute(id, "lightTime"), 5);
			self.lightMaxTime = (1000 * maxTime);
			self.lightTimer = self.lightMaxTime;
		end;
	end;
	]]--
		
	self.animationTime = 0;
	self.animationState = false;
	
	if self.id ~= nil then
		print("FMCMapTriggerV2 trigger: "..tostring(self.id).." ("..id..") loaded");
		FMCMapTriggerV2.triggers[self.id] = self;
	else
		print("Error: id (" .. tostring(self.id) .. ") is not a valid value, trigger (" .. getName(id) .. ")");
		self.isEnabled = false;
	end;
end;

function FMCTriggerV2:delete()
    removeTrigger(self.triggerId);
    
    if self.playSound ~= nil then delete(self.playSound); end;
end;

function FMCTriggerV2:readStream(streamId, connection)
	self.animationTime = streamReadFloat32(streamId);
end;

function FMCTriggerV2:writeStream(streamId, connection)
	streamWriteFloat32(streamId, self.animationTime);
end;

function FMCTriggerV2:update(dt)
	for i=1, table.getn(self.playersLeavingTrigger) do
		local b = self.playersLeavingTrigger[i];
		if b ~= nil then
			local v = self.playersInTrigger[b.id];
			if v ~= nil then
				if v.playerInRange ~= nil or v.vehicleInRange ~= nil then
					if self.playOnLeave ~= nil then
						if self.playOnLeave then
							self:deleteTable(b.id, i);
						else
							if v.playSoundEnabled and self.langSound ~= nil then
								stopSample(self.playSound);
							end;
							self:deleteTable(b.id, i);
						end;
					else
						self:deleteTable(b.id, i);
					end;
				end;
			end;
		end;
	end;
	
	for k,v in pairs(self.playersInTrigger) do
		if (v.playerInRange ~= nil and g_currentMission.player ~= nil and v.playerInRange == g_currentMission.player.rootNode) or (v.vehicleInRange ~= nil and v.vehicleInRange:getIsActive()) then
			if self.isClient then
				if not v.playSoundEnabled and self.langSound ~= nil then
					if v.playerInRange ~= nil or v.vehicleInRange:getIsActiveForSound() then
						playSample(self.playSound, 1, self.playSoundVolume, 0);
					end;
					v.playSoundEnabled = true;
				end;
				if self.inputKey ~= nil then
					if self.animCharSet ~= nil and self.animDuration ~= nil then--or self.light ~= nil and self.lightButtonActivated then
						if v.playerInRange ~= nil or v.vehicleInRange:getIsActiveForInput() then
							if not self.animationState then
								g_currentMission:addHelpButtonText(g_i18n:getText(self.animTextNamePos), self.inputKey);
							else
								g_currentMission:addHelpButtonText(g_i18n:getText(self.animTextNameNeg), self.inputKey);
							end;
							if InputBinding.hasEvent(self.inputKey) then
								self:setAnimation(not self.animationState);
							end;
						end;
					end;
				end;
			end;
		end;
	end;
	    
    if self.animCharSet ~= nil and self.animDuration ~= nil then		
		local animationEnabled = isAnimTrackEnabled(self.animCharSet, 0);
		if animationEnabled then
			if self.isClient then
				if not self.playAnimationSoundEnabled and self.clientAnimationSound ~= nil then
					setVisibility(self.clientAnimationSound, true);
					self.playAnimationSoundEnabled = true;
				end;
			end;
		else
			if self.playAnimationSoundEnabled and self.clientAnimationSound ~= nil then
				setVisibility(self.clientAnimationSound, false);
				self.playAnimationSoundEnabled = false;
			end;
		end;
		
        if self.animationState then
            if self.animationTime < self.animDuration then
                self.animationTime = self.animationTime + self.speedScale;
            else
				self:setAnimationTime(self.animDuration, self.animCharSet, false);
			end;
        else
            if self.animationTime > 0 then
                self.animationTime = self.animationTime - self.speedScale;
			else
				self:setAnimationTime(0, self.animCharSet, false);
            end;
        end;
		
        if self.lastanimationTime ~= self.animationTime then
            if not self.track then
                enableAnimTrack(self.animCharSet, 0);
                self.track = true;
            end;
			
            setAnimTrackTime(self.animCharSet, 0, self.animationTime, false);
            self.lastanimationTime = self.animationTime;
        end;
    end;
	
	if self.isServer then
		if self.assignRigidType and self.assignRigidTypeTimeOut <= g_currentMission.time then
			for i=0, getNumOfChildren(self.triggerId)-1 do
				local child = getChildAt(self.triggerId, i)
				setCollisionMask(child, 2102);
			end;
			self.assignRigidType = false;
		end;
    end;
end;

function FMCTriggerV2:updateTick(dt)
	if self.isServer then
		--[[
		if self.light ~= nil then
			if self.lightButtonActivated then
				if self.animationState then
					setVisibility(self.light, true)
				else
					setVisibility(self.light, false)
				end;
			else
				if self.playerInRange or self.vehicleInRange ~= nil then
					setVisibility(self.light, true)
					self.lightTimer = self.lightMaxTime;
				else
					self.lightTimer = self.lightTimer - dt;
					if self.lightTimer < 0 then
						setVisibility(self.light, false)
					end;
				end;
			end;
		end;
		]]--
		if self.inputKey == nil then
			if table.getn(self.PIT) > 0 then
				if not self.animationState then
					self:setAnimation(true);
				end;
			else
				if self.animationState then
					self:setAnimation(false);
				end;
			end;
		end;
	end;
end;

function FMCTriggerV2:setAnimation(state, noEventSend)
    if state ~= nil then
		setAnimationEventV2.sendEvent(self, state, noEventSend);
		self.animationState = state;
	end;
end;

function FMCTriggerV2:setAnimationTime(time, clip, bool)
	self.animationTime = time;
	disableAnimTrack(clip, 0);
	self.track = bool;
end;

function FMCTriggerV2:deleteTable(id, i)
	if id ~= nil and i ~= nil then
		self.playersInTrigger[id] = nil;
		table.remove(self.playersLeavingTrigger, i);
	end;
end;

function FMCTriggerV2:triggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if self.isEnabled then
		local entry = {};
		if onEnter then
			entry.playSoundEnabled = false;
			if g_currentMission.controlledVehicle ~= nil and otherId == g_currentMission.controlledVehicle.components[1].node then
				entry.vehicleInRange = g_currentMission.nodeToVehicle[otherId];
				self.playersInTrigger[otherId] = entry;
				if self.inputKey == nil then table.insert(self.PIT, {id = otherId}); end;
			elseif g_currentMission.player ~= nil and g_currentMission.controlPlayer and otherId == g_currentMission.player.rootNode then
				entry.playerInRange = otherId;
				self.playersInTrigger[otherId] = entry;
				if self.inputKey == nil then table.insert(self.PIT, {id = otherId}); end;
			end;
		elseif onLeave then
			if (g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode) or (g_currentMission.controlledVehicle ~= nil and otherId == g_currentMission.controlledVehicle.components[1].node) then
				entry.id = otherId;
				table.insert(self.playersLeavingTrigger, entry);
				if self.inputKey == nil then table.remove(self.PIT); end;
			end;
		end;
	end;
end;

-- Event --
setAnimationEventV2 = {};
setAnimationEventV2_mt = Class(setAnimationEventV2, Event);

InitEventClass(setAnimationEventV2, "setAnimationEventV2");

function setAnimationEventV2:emptyNew()
    local self = Event:new(setAnimationEventV2_mt);
    self.className = "setAnimationEventV2";
    return self;
end;

function setAnimationEventV2:new(object, state)
	local self = setAnimationEventV2:emptyNew()
	self.object = object; 

	self.animationState = state;
	return self;
end;

function setAnimationEventV2:readStream(streamId, connection)
	local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.animationState = streamReadBool(streamId);
    self:run(connection);
end;

function setAnimationEventV2:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteBool(streamId, self.animationState);
end;

function setAnimationEventV2:run(connection)
	self.object:setAnimation(self.animationState, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(setAnimationEventV2:new(self.object, self.animationState), nil, connection, self.object);
	end;
end;

function setAnimationEventV2.sendEvent(object, state, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(setAnimationEventV2:new(object, state), nil, nil, object);
		else
			g_client:getServerConnection():sendEvent(setAnimationEventV2:new(object, state));
		end;
	end;
end;