-- RealPlanes.lua for skinned fill planes
-- @ Autor: raffnix & FireAndIce (www.bm-modding.de) 
-- @ Last Edit  13/01/2013

RealPlanes = {};

function RealPlanes.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Fillable, specializations) and SpecializationUtil.hasSpecialization(Trailer, specializations);
end;

function RealPlanes:load(xmlFile)
	self.setFillLevel = Utils.overwrittenFunction(self.setFillLevel, RealPlanes.setFillLevel);
	
	self.getClosestPlaneJoint = RealPlanes.getClosestPlaneJoint;
	self.raiseDynamicPlane = RealPlanes.raiseDynamicPlane;
	self.lowerDynamicPlane = RealPlanes.lowerDynamicPlane;
	self.setDynamicPlaneFillLevel = RealPlanes.setDynamicPlaneFillLevel;
	self.handleDynamicPlaneUpdate = RealPlanes.handleDynamicPlaneUpdate;
	self.getCurrentFiller = RealPlanes.getCurrentFiller;
	
	self.hasRealDynamicPlane = true;
	self.maxPlaneFillDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.dynamicPlanes#maxPlaneFillDistance") , 2);

	self.planeMinJointY = getXMLFloat(xmlFile, "vehicle.dynamicPlanes#minJointY");
	self.planeMaxJointY = getXMLFloat(xmlFile, "vehicle.dynamicPlanes#maxJointY");
	self.planeJointMoveSpace = self.planeMaxJointY - self.planeMinJointY;
	
	self.autoAimNodeMinZ, self.autoAimNodeMaxZ = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.dynamicPlanes#fillAutoAimTargetNodeMoveSpace"));
	
	self.nextTimeAmount = 0;
	
	self.mainJointFillFactor = math.min(1, Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.dynamicPlanes#mainJointFillFactor"), 0.4)); 
	
	self.planeJoints = {};
	local parentNodeId = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.dynamicPlanes#jointGroup"));
	local numOfChildren = getNumOfChildren(parentNodeId);
	for i=1, numOfChildren do
		local joint = {}
		joint.nodeId = getChildAt(parentNodeId, i-1);
		joint.fillLevel = 0;
		joint.capacity = self.capacity / numOfChildren;
		joint.baseY = self.planeMinJointY;
		local x, y, z = getRotation(joint.nodeId);
		joint.emptyRot = z;
		table.insert(self.planeJoints, joint);
		
		local x, y, z = getTranslation(joint.nodeId);
		setTranslation(joint.nodeId, x, self.planeMinJointY, z);
	end;
	
	self.planeJointsNum = table.getn(self.planeJoints);
	
	-- planes
	self.dynamicPlanes = {}
	local num = 0;
	while true do
		local index = getXMLString(xmlFile, "vehicle.skinnedNodes.skinnedNode("..tostring(num)..")#index");
		if index ~= nil then
			local fillTypeStr = getXMLString(xmlFile, "vehicle.skinnedNodes.skinnedNode("..tostring(num)..")#fillType");
			local fillType = Fillable.fillTypeNameToInt[fillTypeStr];
			if fillType ~= nil then
				local planeNodeId = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.skinnedNodes.skinnedNode("..tostring(num)..")#index"));
				self.dynamicPlanes[fillType] = planeNodeId;
			end;
			num = num + 1;
		else
			break;
		end;
	end;
	
	local specialUnloadOrderstr = getXMLString(xmlFile, "vehicle.dynamicPlanes#specialUnloadOrder");
	self.hasSpecialUnloadOrder = specialUnloadOrderstr ~= nil;
	if self.hasSpecialUnloadOrder then
		self.specialUnloadOrder = {}
		local specialUnloadOrder = Utils.splitString(" ", specialUnloadOrderstr);
		for a=1, table.getn(specialUnloadOrder) do
			table.insert(self.specialUnloadOrder, tonumber(specialUnloadOrder[a]));
		end;
		self.specialUnloadOrderMinAmount = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.dynamicPlanes#specialUnloadOrderMinAmount"), 0);
	end;
	
	self.realPlanesDirtyFlag = self:getNextDirtyFlag();
end;

function RealPlanes:delete()

end;

function RealPlanes:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	local fillLevelsStr = getXMLString(xmlFile, key.."#dynamicPlanesFillLevels");
	local fillLevels = Utils.splitString(";", fillLevelsStr);
	
	for a=1, self.planeJointsNum do
		self:setDynamicPlaneFillLevel(a, fillLevels[a]);
	end;
	return BaseMission.VEHICLE_LOAD_OK;
end;

function RealPlanes:getSaveAttributesAndNodes(nodeIdent)
	local attributes = "";
	local nodes = "";
	
	attributes = 'dynamicPlanesFillLevels="';
	for a=1, self.planeJointsNum do
		attributes = attributes..tostring(math.floor(self.planeJoints[a].fillLevel))..';';
	end;
	attributes = attributes..'"';
	
	return attributes, nodes;
end;


function RealPlanes:writeStream(streamId, connection)
	for a=1, self.planeJointsNum do
		streamWriteFloat32(streamId, self.planeJoints[a].fillLevel);
	end;
end;

function RealPlanes:readStream(streamId, connection)
	for a=1, self.planeJointsNum do
		local fillLevel = streamReadFloat32(streamId);
		self:setDynamicPlaneFillLevel(a, fillLevel);
	end;
end;

function RealPlanes:writeUpdateStream(streamId, connection, dirtyMask)
	if not connection:getIsServer() then
		if streamWriteBool(streamId, bitAND(dirtyMask, self.realPlanesDirtyFlag) ~= 0) then
			print("self.realPlanesDirtyFlag: ", self.realPlanesDirtyFlag);
			print("bitAND(dirtyMask, self.realPlanesDirtyFlag): ", tostring(bitAND(dirtyMask, self.realPlanesDirtyFlag)));
			for a=1, self.planeJointsNum do
				local joint = self.planeJoints[a];
				
				local percent = 0;
				if joint.capacity ~= 0 then
					percent = math.min(1, math.max(0, joint.fillLevel / joint.capacity));
				end;
				streamWriteUInt16(streamId, math.floor(percent*65535));
			end;
		end;
	end;
end;

function RealPlanes:readUpdateStream(streamId, timestamp, connection)
	if connection:getIsServer() then
		if streamReadBool(streamId) then
			for a=1, self.planeJointsNum do
				local joint = self.planeJoints[a];
				
				local fillLevel = streamReadUInt16(streamId)/65535*joint.capacity;
				self:setDynamicPlaneFillLevel(a, fillLevel);
			end;
		end;
	end;
end;

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

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

function RealPlanes:update(dt)

end;
	
function RealPlanes:updateTick(dt)
	
end;
 
function RealPlanes:draw()
	
end;

function RealPlanes:setFillLevel(oldFunc, fillLevel, fillType, force)
	if fillType ~= self.currentFillType then
		for fillType, planeId in pairs(self.dynamicPlanes) do
			setVisibility(planeId, false);
		end;
	end;

	if self.dynamicPlanes[fillType] ~= nil then
		setVisibility(self.dynamicPlanes[fillType], true);
	end;
	
	if fillLevel <= 0 then
		for fillType, planeId in pairs(self.dynamicPlanes) do
			setVisibility(planeId, false);
		end;
	end;
	
	local deltaFillLevel = fillLevel - self.fillLevel;
	oldFunc(self, fillLevel, fillType, force);
	
	if self.isServer then
		if deltaFillLevel > 0 then
			local currentFiller, currentFillNode = self:getCurrentFiller();
			
			if currentFiller ~= nil and currentFillNode ~= nil then 
				self:handleDynamicPlaneUpdate(currentFillNode, deltaFillLevel, currentFiller);
			else -- default
				for a=1, self.planeJointsNum do
					self:raiseDynamicPlane(a, deltaFillLevel/self.planeJointsNum); 
				end;
			end;
		elseif deltaFillLevel < 0 then
			self:lowerDynamicPlane(-deltaFillLevel);
		end;
		
		-- maybee the joints and the real fill level are 100% synchron any more. 
		if self.fillLevel == 0 then
			for a=1, self.planeJointsNum do 
				self:setDynamicPlaneFillLevel(a, 0);
			end;
		end;
	end;
	
	self:raiseDirtyFlags(self.realPlanesDirtyFlag);
end;

function RealPlanes:handleDynamicPlaneUpdate(fillNode, fillAmount, currentFiller)
	if currentFiller.grainTankCapacity ~= nil and currentFiller.grainTankCapacity == 0 then -- chopper
		for a=1, self.planeJointsNum do
			if self.planeJoints[a].fillLevel < self.planeJoints[a].capacity then
				self:raiseDynamicPlane(a, fillAmount);
				
				-- move the ai pipe target
				local x, y, z = getTranslation(self.fillAutoAimTargetNode);
				local wjx, wjy, wjz = getWorldTranslation(self.planeJoints[a].nodeId);
				local _, _, z = worldToLocal(getParent(self.fillAutoAimTargetNode), wjx, wjy, wjz);
				z = math.min(z, self.autoAimNodeMaxZ);
				z = math.max(z, self.autoAimNodeMinZ);
				setTranslation(self.fillAutoAimTargetNode, x, y, z);
				break;
			end;
		end;
	else
		local px,py,pz = getWorldTranslation(fillNode);
		local closestJointIndex, allowed = self:getClosestPlaneJoint(px, py, pz);
		if closestJointIndex ~= nil then 
			self:raiseDynamicPlane(closestJointIndex, fillAmount);
		end;
	end;
end;

function RealPlanes:getClosestPlaneJoint(x, y, z)
	local closestJointIndex = nil;
	local lastJointDist = 100;
	local closestPlaneJointIsNearEnough = false;
	for a=1, self.planeJointsNum do
		local nodeId = self.planeJoints[a].nodeId;
		local wx, wy, wz = getWorldTranslation(nodeId);
		local distance = Utils.vector2Length(x-wx, z-wz);
		if distance < lastJointDist and self.planeJoints[a].fillLevel < self.planeJoints[a].capacity then
			lastJointDist = Utils.vector2Length(x-wx, z-wz);
			closestJointIndex = a;
			if distance < self.maxPlaneFillDistance then
				closestPlaneJointIsNearEnough = true;
			end;
		end;
	end;
	return closestJointIndex, closestPlaneJointIsNearEnough;
end;

function RealPlanes:raiseDynamicPlane(closestJointIndex, fillDelta)
	local start = math.max(1, closestJointIndex - 3);
	local stop = math.min(closestJointIndex+3, self.planeJointsNum);
	local numJoints = stop-start;
	
	fillDelta = fillDelta + self.nextTimeAmount;
	self.nextTimeAmount = 0;
	local mainJointAmount = fillDelta * self.mainJointFillFactor; 
	local borderingJointAmount = (fillDelta * (1-self.mainJointFillFactor))/numJoints;
	local amount = fillDelta;
	for a=start, stop do
		local joint = self.planeJoints[a];
		if joint.fillLevel < joint.capacity and a ~= closestJointIndex then
			local transfer = math.min(joint.capacity-joint.fillLevel, borderingJointAmount);
			transfer = math.min(amount, transfer);
			
			amount = amount - transfer;
			self:setDynamicPlaneFillLevel(a, joint.fillLevel + transfer);
		end;
	end;

	-- main joint
	local joint = self.planeJoints[closestJointIndex];
	
	local transfer = math.min(joint.capacity-joint.fillLevel, mainJointAmount);
	transfer = math.min(amount, transfer); 
	
	amount = amount - transfer;
	self:setDynamicPlaneFillLevel(closestJointIndex, joint.fillLevel + transfer);
	
	self.nextTimeAmount = self.nextTimeAmount + amount;
end;

function RealPlanes:lowerDynamicPlane(fillDelta)
	local closestJointIndex = 0;

		
	if self.hasSpecialUnloadOrder then
		for a=1, table.getn(self.specialUnloadOrder) do
			local num = self.specialUnloadOrder[a];
			if self.planeJoints[num].fillLevel > self.specialUnloadOrderMinAmount then
				closestJointIndex = num;
				break;
			end;
		end;
	end;
	
	if not self.hasSpecialUnloadOrder or closestJointIndex == 0 then
		for a=1, self.planeJointsNum do
			if self.planeJoints[a].fillLevel > 0 then
				closestJointIndex = a;
				break;
			end;
		end;
	end;
	
	if closestJointIndex > 0 then
		local start = math.max(1, closestJointIndex - 3);
		local stop = math.min(closestJointIndex+3, self.planeJointsNum);
		local numJoints = stop-start;
		
		fillDelta = fillDelta + self.nextTimeAmount;
		self.nextTimeAmount = 0;
		local mainJointAmount = fillDelta * self.mainJointFillFactor;
		local borderingJointAmount = (fillDelta * (1-self.mainJointFillFactor))/numJoints;
		local amount = fillDelta;
		
		for a=start, stop do
			local joint = self.planeJoints[a];
			if joint.fillLevel > 0 and a ~= closestJointIndex then
				local transfer = math.min(joint.fillLevel, borderingJointAmount);
				transfer = math.min(amount, transfer);
				
				amount = amount - transfer;
				self:setDynamicPlaneFillLevel(a, joint.fillLevel - transfer);
			end;
		end;

		-- main joint
		local joint = self.planeJoints[closestJointIndex];
		
		local transfer = math.min(joint.fillLevel, mainJointAmount);
		transfer = math.min(amount, transfer);
		
		amount = amount - transfer;
		self:setDynamicPlaneFillLevel(closestJointIndex, joint.fillLevel - transfer);
		
		self.nextTimeAmount = self.nextTimeAmount + amount;
	end;
end;

function RealPlanes:setDynamicPlaneFillLevel(jointIndex, newLevel)
	local joint = self.planeJoints[jointIndex];

	local newLevel = math.min(newLevel, joint.capacity);
	newLevel = math.max(newLevel, 0);
	joint.fillLevel = newLevel;
	local x, y, z = getTranslation(joint.nodeId);
	setTranslation(joint.nodeId, x, joint.baseY+((joint.fillLevel/joint.capacity)*self.planeJointMoveSpace), z);
	
	local x, y, z = getRotation(joint.nodeId);
	setRotation(joint.nodeId, x, y, joint.emptyRot * (1-(joint.fillLevel/joint.capacity)));
end;

function RealPlanes:getCurrentFiller()
	local filler = nil;

	if g_currentMission.lastFillingSiloTrigger ~= nil then
		filler = g_currentMission.lastFillingSiloTrigger;
		return filler, filler.rootNode;	
	end;
	
	if g_currentMission.lastFillingCombine ~= nil then
		filler = g_currentMission.lastFillingCombine;
		return filler, filler.pipeRaycastNode;		
	end;

	return nil;
end;

local oldCombineFindTrailerToUnload = Combine.findTrailerToUnload; -- Combine
Combine.findTrailerToUnload = function(self, fruitType)
	local trailer, distance = oldCombineFindTrailerToUnload(self, fruitType);
	if trailer ~= nil and trailer.hasRealDynamicPlane ~= nil and trailer.hasRealDynamicPlane then
		g_currentMission.lastFillingCombine = self;
	end;
	return trailer, distance;
end;

local oldSiloTriggerUpdate = SiloTrigger.update; -- Silo
SiloTrigger.update = function(self, dt)
	g_currentMission.lastFillingSiloTrigger = self;
	oldSiloTriggerUpdate(self, dt);
	g_currentMission.lastFillingSiloTrigger = nil;
end;