--
-- AlternativeTipTrigger
--
-- SFM-Modding
-- @author  Manuel Leithner
-- @date:		01/08/11
-- @version:	v1.5
-- @history:	v1.0 - initial implementation
-- 				v1.5 - FS13 Conversion and other stuff - Xentro (Marcus@Xentro.se)
--
--

-- saves the mod path and name on sourcing
g_alternativeTipTriggerDir = g_currentModDirectory;
g_alternativeTipTriggerName = g_currentModName;
g_isAlternativeTipTriggerLoaded = false;

AlternativeTipTrigger = {};
addModEventListener(AlternativeTipTrigger);

AlternativeTipTrigger.TRAILER = 0;
AlternativeTipTrigger.COMBINE = 1;
AlternativeTipTrigger.SHOVEL = 2;

function AlternativeTipTrigger:loadMap(mapName)
	if not g_isAlternativeTipTriggerLoaded then	
		g_currentMission.tipTriggerRangeThreshold = 4;
		
--******************************************** DURAL ************************************************
-- this values depend on the height difference between the "heap plane" and the "heap collision shape"
-- put a higher value to avoid heap not being detected
		--g_currentMission.shovelRayDistance = 0.6;
		--g_currentMission.shovelTerrainHeapHeightOffset = 0.75;
		--g_currentMission.combineTerrainHeapHeightOffset = 0.35;
		g_currentMission.shovelRayDistance = 0.8;
		g_currentMission.shovelTerrainHeapHeightOffset = 2;
		g_currentMission.combineTerrainHeapHeightOffset = 2;
--******************************************** END DURAL ************************************************
		
		g_currentMission.alternativeTipTrigger = self;
		
		self.referenceTipIndex = 1;
		self.minDistanceTriggers = 12.5;
		self.offset = 0.1;
		
		self.triggerNodePosition = nil;
		self.fixpointPosition = nil;
		self.adjustPosition = nil;
		
		self.forceUpdateShovel = false;
		self.updateShovel = false;
		
		self.triggers = {};
		
		-- load custom map heaps
		if g_currentMission.addAlternativeTippingHeaps ~= nil then
			self:loadXmlHeaps(g_currentMission.addAlternativeTippingHeaps["file"], g_currentMission.addAlternativeTippingHeaps["dir"]);
			g_currentMission.addAlternativeTippingHeaps = nil;
		end;
		
		self:loadXmlHeaps("map/script/AlternativeTipTrigger.xml", g_alternativeTipTriggerDir);
		
		self.overrideCombineFruitLimit = false;
		self.allowedCombineFruits = {};
		self:addCombineFruit(Fillable.FILLTYPE_SUGARBEET);
		self:addCombineFruit(Fillable.FILLTYPE_POTATO);
		
		self.overrideTrailerFruitLimit = false;
		self.allowedTrailerFruits = {};
		self:addTrailerFruit(Fillable.FILLTYPE_SUGARBEET);
		self:addTrailerFruit(Fillable.FILLTYPE_POTATO);
		self:addTrailerFruit(Fillable.FILLTYPE_SILAGE);
		self:addTrailerFruit(Fillable.FILLTYPE_MANURE);
		
		self:loadTriggers();
		g_isAlternativeTipTriggerLoaded = true;
	end;
end;

function AlternativeTipTrigger:deleteMap()	
	for fillType in pairs(self.allowedCombineFruits) do
		self:removeCombineFruit(fillType);
	end;
	
	for fillType in pairs(self.allowedTrailerFruits) do
		self:removeTrailerFruit(fillType);
	end;
	
	g_isAlternativeTipTriggerLoaded = false;
end;

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

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

function AlternativeTipTrigger:update(dt)
end;

function AlternativeTipTrigger:draw()
	if g_currentMission ~= nil then
		for _, trigger in pairs(g_currentMission.tipTriggers) do
			if trigger.draw ~= nil then
				trigger:draw(trigger);
			end;
		end;
	end;
end;

function AlternativeTipTrigger:loadXmlHeaps(file, directory)
	local xmlPath = directory .. file;
	local xmlFile = loadXMLFile("TipTriggerXML", xmlPath);
		
	local i = 0;
	while true do
		local triggerName = string.format("alternativeTipTrigger.triggers.trigger(%d)", i);
		if not hasXMLProperty(xmlFile, triggerName) then break; end;
		
		local trigger = {};
		trigger.fileName = getXMLString(xmlFile, triggerName .. "#fileName");
		if trigger.fileName == nil then
			break;
		end;
		trigger.directory = directory;
		trigger.xmlFile = xmlPath;
		trigger.key = triggerName;
		trigger.className = g_alternativeTipTriggerName .. "." .. Utils.getNoNil(getXMLString(xmlFile, triggerName .. "#className"), "ExtendedTipTrigger");
		trigger.fixpointStr = getXMLString(xmlFile, triggerName .. "#fixpoint");
		trigger.trigger = getXMLString(xmlFile, triggerName .. "#trigger");
	
		trigger.planes = {};
		local j = 0;
		while true do
			local planeName = string.format(triggerName .. ".plane(%d)", j);
			if not hasXMLProperty(xmlFile, planeName) then break; end;
			
			local plane = {};
			plane.type = getXMLString(xmlFile, planeName .. "#type");
			if plane.type == nil then
				break;
			end;
			plane.path = getXMLString(xmlFile, planeName .. "#index");
			table.insert(trigger.planes, plane);
			j = j + 1;
		end;
		
		table.insert(self.triggers, trigger);		
		i = i + 1;
	end;
	
	delete(xmlFile);
end;

function AlternativeTipTrigger:getTriggerByFilltype(fillType)
	local trigger;
	for _, v in pairs(self.triggers) do
		for _, plane in pairs(v.planes) do
			if plane.type == fillType then
				trigger = v;
				break;
			end;
		end;
		if trigger ~= nil then
			break;
		end;
	end;	
	
	return trigger;
end;

function AlternativeTipTrigger:loadTriggers()
	if g_currentMission.missionInfo.isValid then
		local xmlPath = g_currentMission.missionInfo.savegameDirectory .. "/alternativeTipTrigger.xml";
		local xmlFile = loadXMLFile("AlternativeTipTrigger", xmlPath);
		
		local i = 0;
		while true do
			local triggerName = string.format("alternativeTipTrigger.trigger(%d)", i);
			local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, triggerName .. "#position"));
			local xRot, yRot, zRot = Utils.getVectorFromString(getXMLString(xmlFile, triggerName .. "#rotation"));
			
			if x == nil or xRot == nil then
				break;
			end;
			
			local offset = getXMLFloat(xmlFile, triggerName .. "#offset");
			local fillType = getXMLString(xmlFile, triggerName .. "#fruit");
			
			self:createTrigger({x, y, z, offset}, {xRot, yRot, zRot}, fillType, Utils.getNoNil(getXMLFloat(xmlFile, triggerName .. "#fillLevel"), 0), nil, nil);
			i = i + 1;
		end;
		
		delete(xmlFile);
	end;
end;

function AlternativeTipTrigger:saveTriggers()
	local savegame = self.savegames[self.selectedIndex];
	if savegame ~= nil then
		local key = "alternativeTipTrigger";
		local xmlFile = createXMLFile("alternativeTipTrigger", savegame.savegameDirectory .. "/alternativeTipTrigger.xml", key);
		
		local i = 0;
		for _, trigger in pairs(g_currentMission.tipTriggers) do
			if trigger:isa(ExtendedTipTrigger) then
				local triggerKey = string.format(key .. ".trigger(%d)", i);
				
				local transform = trigger.transform;
				local x, y, z = getWorldTranslation(transform);
				local a, b, c = getWorldRotation(transform);
				local _, ay, _ = getTranslation(getChildAt(trigger.triggerId, 0));
				local fruit = Fillable.fillTypeIntToName[trigger.currentFillType];
			
				setXMLString(xmlFile, triggerKey .. "#position", x .. " " .. y .. " " .. z);
				setXMLString(xmlFile, triggerKey .. "#rotation", a .. " " .. b .. " " .. c);
				setXMLFloat(xmlFile, triggerKey .. "#offset", ay);
				setXMLFloat(xmlFile, triggerKey .. "#fillLevel", trigger.fillLevel);
				setXMLString(xmlFile, triggerKey .. "#fruit", fruit);
				i = i + 1;
			end;
		end;
		
		saveXMLFile(xmlFile);
		delete(xmlFile);
	end;
end;
CareerScreen.saveSelectedGame = Utils.appendedFunction(CareerScreen.saveSelectedGame, AlternativeTipTrigger.saveTriggers);

function AlternativeTipTrigger:createTrigger(trans, rot, fillType, fillLevel, positionNode, directionNode)
	local trigger = self:getTriggerByFilltype(fillType);
	if trigger ~= nil then
		local triggerFile = trigger.fileName;
		local callString = "triggerClass = " .. trigger.className;
		loadstring(callString)();
		
		if triggerClass ~= nil then
			local triggerRoot = getChildAt(Utils.loadSharedI3DFile(triggerFile, trigger.directory), 0);
			local transform = getChildAt(triggerRoot, 0);
			trigger.transform = transform;
			local triggerId = Utils.indexToObject(triggerRoot, trigger.trigger);
			trigger.fixpoint = Utils.indexToObject(triggerRoot, trigger.fixpointStr);
			link(getRootNode(), triggerRoot);
			
			if positionNode ~= nil or (rot ~= nil and trans ~= nil) then
				if positionNode == nil and rot ~= nil and trans ~= nil then
					-- loading saved heap
					
					setRotation(transform,  worldDirectionToLocal(triggerRoot, unpack(rot)));
					local x, y, z, offset = unpack(trans);
					setTranslation(transform, worldToLocal(triggerRoot, x, y, z));
					local a, b, c = getTranslation(getChildAt(triggerId, 0));
					setTranslation(getChildAt(triggerId, 0), 0, offset, 0);
				else
					-- user creating new heap
					
					setTranslation(triggerRoot, getWorldTranslation(positionNode));
					
					local xUp, yUp, zUp = localDirectionToWorld(directionNode, 0, 1, 0);
					local dx, dy, dz = localDirectionToWorld(directionNode, 0, 0, 1);
					setDirection(triggerRoot, dx, dy, dz, xUp, yUp, zUp);
					
					self.triggerNodePosition = nil;
					local ax, ay, az = getWorldTranslation(trigger.transform);
					local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ax, ay, az);
					ay = math.max(ay, terrainHeight + 1.5);
					raycastAll(ax, ay, az, 0, -1, 0, "triggerNodeCallback", 10, self);
					if self.triggerNodePosition ~= nil then
						ax, ay, az = unpack(self.triggerNodePosition);
					end;
					ay = math.max(ay, terrainHeight);
					setTranslation(transform, worldToLocal(triggerRoot, ax, ay, az));	
					
					self.fixpointPosition = nil;			
					local bx, by, bz = getWorldTranslation(trigger.fixpoint);
					terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, bx, by, bz);
					by = math.max(by, terrainHeight + 1.5);
					raycastAll(bx, by, bz, 0, -1, 0, "fixpointCallback", 10, self);
					if self.fixpointPosition ~= nil then
						bx, by, bz = unpack(self.fixpointPosition);
					end;
					by = math.max(by, terrainHeight);
					local x, y, z = worldDirectionToLocal(getParent(transform), bx-ax, by-ay, bz-az);
					setDirection(transform, x, y, z, 0, 1, 0);
				end;

--************************************************** DURAL *****************************************************************
--** the "l" letter was missing for the g_client~=nil
			    --local triggerObj = triggerClass:new(g_server ~= nil, g_client ~= ni);				
				local triggerObj = triggerClass:new(g_server ~= nil, g_client ~= nil);
--*************************************************** END DURAL *************************************************************
				triggerObj:load(triggerId, trigger, Fillable.fillTypeNameToInt[fillType]);			
				triggerObj:register(true);
				triggerObj:updateMoving(Utils.getNoNil(fillLevel, 1));

				return triggerObj;
			else
				print("Error: positionNode, rot or trans is empty!");
			end;
		else
			print("Error: Class not found");
		end;
	else	
		print("Error: Trigger could not be loaded! " .. fillType .. " not found");
	end;
	
	return nil;	
end;

function AlternativeTipTrigger:addNewTrigger(vehicle, forceCreate, tipType, tipIndex, noEventSend)
	if g_server == nil and not forceCreate then
		-- request server to create trigger obj
		AddAlternativeTriggerEvent.sendEvent(vehicle, AddAlternativeTriggerEvent.TRIGGER_CREATE, -1, tipType, tipIndex, noEventSend);
	else
		if tipType == AlternativeTipTrigger.TRAILER then
			if vehicle.tipState ~= nil then
				if vehicle.tipState == Trailer.TIPSTATE_CLOSED and vehicle.fillLevel > 0 then
					local fillType = Fillable.fillTypeIntToName[vehicle.currentFillType];
					local triggerObj = self:createTrigger(nil, nil, fillType, 1, vehicle.triggerPlacement, vehicle.rootNode);
					if triggerObj ~= nil then
						self:adjustPlaneToGround(triggerObj);
						if g_currentMission.trailerTipTriggers[vehicle] == nil then
							g_currentMission.trailerTipTriggers[vehicle] = {};
						end;
						table.insert(g_currentMission.trailerTipTriggers[vehicle], triggerObj);
						g_currentMission.trailerInTipRange = vehicle;
						g_currentMission.currentTipTrigger = triggerObj;
						
						AddAlternativeTriggerEvent.sendEvent(vehicle, AddAlternativeTriggerEvent.TRIGGER_SEND, triggerObj.id, tipType, tipIndex, noEventSend);
						
						vehicle:onStartTip(g_currentMission.currentTipTrigger, tipIndex, true);
					end;
					
					return triggerObj;
				else
					print("Trailer empty or not closed");
				end;
			end;
		elseif tipType == AlternativeTipTrigger.COMBINE then
			if vehicle.grainTankFillLevel > 0 then
				local grainTankFillType = FruitUtil.fruitTypeToFillType[vehicle.currentGrainTankFruitType];
				
				local triggerObj = self:createTrigger(nil, nil, Fillable.fillTypeIntToName[grainTankFillType], 1, vehicle.triggerPlacement, vehicle.rootNode);
				if triggerObj ~= nil then
					self:adjustPlaneToGround(triggerObj);
					
					AddAlternativeTriggerEvent.sendEvent(vehicle, AddAlternativeTriggerEvent.TRIGGER_SEND, triggerObj.id, tipType, tipIndex, noEventSend);
				end;
				
				return triggerObj;
			else
				print("Combine is empty");
			end;
		elseif tipType == AlternativeTipTrigger.SHOVEL then
			-- print("Warning: Shovel is not allowed to create heaps!");
		end;
	end;
end;

function AlternativeTipTrigger:adjustPlaneToGround(trigger)
	local adjustNode = getChildAt(trigger.triggerId, 0);
	local x, y, z = getWorldTranslation(adjustNode);
	local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, y, z);	
	local dirX, dirY, dirZ =  localDirectionToWorld(adjustNode, 0, -1, 0);
	
	self.adjustPosition = nil;
	raycastClosest(x, y, z, dirX, dirY, dirZ, "adjustCallback", 10, self);
	
	if self.adjustPosition ~= nil then
		x, y, z = unpack(self.adjustPosition);		
		y = math.max(y, terrainHeight);
	else
		y = terrainHeight;
	end;	
	
	local node = getChildAt(trigger.triggerId, 0);
	x, y, z = worldToLocal(getParent(node), x, y, z);
	setTranslation(node, 0, y + 0.3, 0);
end;

function AlternativeTipTrigger:isCallbackValid(transformId)
	local isValid = true;
	local triggerObj;
	
	for k, vehicle in pairs(g_currentMission.vehicles) do	
		for j, component in pairs(vehicle.components) do
			if component.node == transformId then
				isValid = false;
				break;
			end;
		end;
		if not isValid then
			break;
		end;
	end;
	
	if isValid then
		for _, trigger in pairs(g_currentMission.tipTriggers) do	
			if trigger.isExtendedTrigger then
				if trigger.triggerId == transformId then
					isValid = false;
					break;
				elseif trigger.shovelNode ~= nil and trigger.shovelNode == transformId then
					isValid = false;
					triggerObj = trigger;
					break;
				end;
			end;
		end;
	end;
	
	return isValid, triggerObj;
end;

function AlternativeTipTrigger:triggerNodeCallback(transformId, x, y, z, distance)
	if transformId == g_currentMission.terrainRootNode or self:isCallbackValid(transformId) then
		self.triggerNodePosition = {x, y, z};	
		return false;
	else
		return true;
	end;
end;

function AlternativeTipTrigger:fixpointCallback(transformId, x, y, z, distance)	
	if transformId == g_currentMission.terrainRootNode or self:isCallbackValid(transformId) then
		self.fixpointPosition = {x, y, z};
		return false;
	else
		return true;
	end;
end;

function AlternativeTipTrigger:adjustCallback(transformId, x, y, z, distance)	
	if transformId == g_currentMission.terrainRootNode or self:isCallbackValid(transformId) then
		self.adjustPosition = {x, y, z};
		return false;
	else
		return true;
	end;
end;

-- Combine
function AlternativeTipTrigger:isCombineFruitAllowed(fillType)
	if self.overrideCombineFruitLimit then
		return true;
	end;
	
	if fillType ~= nil then
		if self.allowedCombineFruits[fillType] ~= nil then
			return true;
		end;
	end;
	
	return false;
end;

function AlternativeTipTrigger:addCombineFruit(fillType)	
	if fillType ~= nil then
		local fillName = Fillable.fillTypeIntToName[fillType];
		if fillName ~= nil then
			if self:getTriggerByFilltype(fillName) ~= nil then
				self.allowedCombineFruits[fillType] = fillName;
			end;
		end;
	end;
end;

function AlternativeTipTrigger:removeCombineFruit(fillType)
	self.allowedCombineFruits[fillType] = nil;
end;

-- Trailer
function AlternativeTipTrigger:isTrailerFruitAllowed(fillType)
	if self.overrideTrailerFruitLimit then
		return true;
	end;
	
	if fillType ~= nil then
		if self.allowedTrailerFruits[fillType] ~= nil then
			return true;
		end;
	end;
	
	return false;
end;

function AlternativeTipTrigger:addTrailerFruit(fillType)	
	
	if fillType ~= nil then
		local fillName = Fillable.fillTypeIntToName[fillType];
		if fillName ~= nil then
			if self:getTriggerByFilltype(fillName) ~= nil then						
				self.allowedTrailerFruits[fillType] = fillName;
			end;
		end;
	end;
end;

function AlternativeTipTrigger:removeTrailerFruit(fillType)
	self.allowedTrailerFruits[fillType] = nil;
end;
