--
-- AttacherJoint - HardPoint
--
-- @author:    	Xentro (Marcus@Xentro.se)
-- @website:	www.Xentro.se
-- @history:	v1.0   - 2015-09-20 - Initial implementation
--				v1.0.1 - 2015-10-03 - resetVehicles added to loadAttributesAndNodes function, TopArm got dirt support
--				v1.1   - 2015-10-24 - New allow check added
--

AttacherJoint = {};

function AttacherJoint:prerequisitesPresent(specializations)
	return true;
end;

-- pre check before attaching
function AttacherJoint:allowAdding(vehicle)
	return true;
end;

function AttacherJoint:allowRemoval(vehicle)
	local allow, txt = true, nil;
	
	if self.tableIds ~= nil then
		local allowDetachOnLocation = true;
		
		for _, i in ipairs(self.tableIds) do
			if vehicle:getImplementIndexByJointDescIndex(i) ~= nil then
				allowDetachOnLocation = false;
				break;
			end;
		end;
		
		if not allowDetachOnLocation then
			if g_i18n:hasText("ERROR_WE_ARE_ATTACHED") then
				txt = g_i18n:getText("ERROR_WE_ARE_ATTACHED");
			end;
			
			allow = false;
		end;
	end;
	
	return allow, txt;
end;

function AttacherJoint:load(xmlFile)
	local attacherJoints = AttacherJoint.loadJointXML(self, xmlFile, "attacherJoints", "attacherJoint");
	
	if attacherJoints ~= nil then
		local totalAttacherJoints = table.getn(self.vehicle.attacherJoints);
		local numNewJoints = table.getn(attacherJoints);
		
		self.tableIds = {};
		for i, v in ipairs(attacherJoints) do
			table.insert(self.tableIds, totalAttacherJoints + i);
			table.insert(self.vehicle.attacherJoints, v);
		end;
	end;
	
	if self.vehicle.schemaOverlay ~= nil then
		local i = 0;
		while true do
			local key = string.format("vehicle.schemaOverlay.attacherJoint(%d)", i);
			if not hasXMLProperty(xmlFile, key) then break; end;
			
			if self.tableIds[i + 1] ~= nil then
				local x, y = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#position"));
				local rotation = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, key .. "#rotation"), 0));
				local invertX = Utils.getNoNil(getXMLBool(xmlFile, key .. "#invertX"), false);
				
				self.vehicle.schemaOverlay.attacherJoints[self.tableIds[i + 1]] = {x = x, y = y, rotation = rotation, invertX = invertX};
				i = i + 1;
			else
				print("  HardPoint - Error: Not enough attacherJoints defined to create another schemaOverlay.attacherJoint");
				break;
			end;
		end;
	end;
end;

function AttacherJoint.loadJointXML(self, xmlFile, tag, tag2)
	local joints;
	i = 0;
	while true do
		local baseName = string.format("vehicle." .. tag .. "." .. tag2 .. "(%d)", i);
		if not hasXMLProperty(xmlFile, baseName) then break; end;

		local node = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. "#node"));
		if node ~= nil then
			if joints == nil then
				joints = {};
			end;
			
			entry = {};
			entry.jointTransform = node;
			entry.jointOrigRot = { getRotation(entry.jointTransform) };
			entry.jointOrigTrans = { getTranslation(entry.jointTransform) };

			local jointTypeStr = getXMLString(xmlFile, baseName .. "#jointType");
			local jointType;
			if jointTypeStr ~= nil then
				jointType = Vehicle.jointTypeNameToInt[jointTypeStr];
				if jointType == nil then
					print("Warning: invalid jointType " .. jointTypeStr);
				end;
			end;
			if jointType == nil then
				jointType = Vehicle.JOINTTYPE_IMPLEMENT;
			end;
			entry.jointType = jointType;
			entry.allowsJointLimitMovement = Utils.getNoNil(getXMLBool(xmlFile, baseName .."#allowsJointLimitMovement"), true);
			entry.allowsLowering = Utils.getNoNil(getXMLBool(xmlFile, baseName .."#allowsLowering"), true);

			if jointType == Vehicle.JOINTTYPE_TRAILER or jointType == Vehicle.JOINTTYPE_TRAILERLOW then
				entry.allowsLowering = false;
			end;

			self.vehicle:loadPowerTakeoff(xmlFile, baseName, entry);

			entry.canTurnOnImplement = Utils.getNoNil(getXMLBool(xmlFile, baseName .."#canTurnOnImplement"), true);

			local rotationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. "#rotationNode"));
			if rotationNode ~= nil then
				entry.rotationNode = rotationNode;
				local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#maxRot"));
				entry.maxRot = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) };

				local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#minRot"));
				local rx,ry,rz = getRotation(rotationNode);
				entry.minRot = { Utils.getNoNilRad(x, rx), Utils.getNoNilRad(y, ry), Utils.getNoNilRad(z, rz) };

			end;
			local rotationNode2 = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. "#rotationNode2"));
			if rotationNode2 ~= nil then
				entry.rotationNode2 = rotationNode2;
				local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#maxRot2"));
				entry.maxRot2 = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) };

				local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#minRot2"));
				local rx,ry,rz = getRotation(rotationNode2);
				entry.minRot2 = { Utils.getNoNilRad(x, rx), Utils.getNoNilRad(y, ry), Utils.getNoNilRad(z, rz) };
			end;
			entry.maxRotDistanceToGround = Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#maxRotDistanceToGround"), 0.7);
			entry.minRotDistanceToGround = Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#minRotDistanceToGround"), 1.0);
			entry.maxRotRotationOffset = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#maxRotRotationOffset"), 0));
			entry.minRotRotationOffset = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#minRotRotationOffset"), 8));


			local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#maxRotLimit"));
            entry.maxRotLimit = {};
            entry.maxRotLimit[1] = math.rad(math.abs(Utils.getNoNil(x, 0)));
            entry.maxRotLimit[2] = math.rad(math.abs(Utils.getNoNil(y, 0)));
            entry.maxRotLimit[3] = math.rad(math.abs(Utils.getNoNil(z, 0)));

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#minRotLimit"));
            entry.minRotLimit = {};
            entry.minRotLimit[1] = math.rad(math.abs(Utils.getNoNil(x, 0)));
            entry.minRotLimit[2] = math.rad(math.abs(Utils.getNoNil(y, 0)));
            entry.minRotLimit[3] = math.rad(math.abs(Utils.getNoNil(z, 0)));

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#maxTransLimit"));
            entry.maxTransLimit = {};
            entry.maxTransLimit[1] = math.abs(Utils.getNoNil(x, 0));
            entry.maxTransLimit[2] = math.abs(Utils.getNoNil(y, 0));
            entry.maxTransLimit[3] = math.abs(Utils.getNoNil(z, 0));

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#minTransLimit"));
            entry.minTransLimit = {};
            entry.minTransLimit[1] = math.abs(Utils.getNoNil(x, 0));
            entry.minTransLimit[2] = math.abs(Utils.getNoNil(y, 0));
            entry.minTransLimit[3] = math.abs(Utils.getNoNil(z, 0));

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName .."#jointPositionOffset"));
            entry.jointPositionOffset = {};
            entry.jointPositionOffset[1] = Utils.getNoNil(x, 0);
            entry.jointPositionOffset[2] = Utils.getNoNil(y, 0);
            entry.jointPositionOffset[3] = Utils.getNoNil(z, 0);

            entry.transNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .."#transNode"));
            if entry.transNode ~= nil then
                entry.transNodeOrgTrans = {getTranslation(entry.transNode)};
                entry.transMinYHeight = Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#transMinYHeight"), entry.jointOrigTrans[2]);
                entry.transMaxYHeight = Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#transMaxYHeight"), entry.jointOrigTrans[2]);
            end;

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile,  baseName .."#rotLimitSpring"));
            local rotLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile,  baseName .."#rotLimitDamping"));
            local rotLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) };
            entry.rotLimitSpring = rotLimitSpring;
            entry.rotLimitDamping = rotLimitDamping;

            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile,  baseName .."#transLimitSpring"));
            local transLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) };
            local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile,  baseName .."#transLimitDamping"));
            local transLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) };
            entry.transLimitSpring = transLimitSpring;
            entry.transLimitDamping = transLimitDamping;

            entry.moveTime = Utils.getNoNil(getXMLFloat(xmlFile, baseName .."#moveTime"), 0.5)*1000;

            entry.enableCollision = Utils.getNoNil(getXMLBool(xmlFile, baseName .."#enableCollision"), false);

            local topArmFilename = getXMLString(xmlFile, baseName .. ".topArm#filename");
            if topArmFilename ~= nil then
                local baseNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".topArm#baseNode"));
                if baseNode ~= nil then
                    local i3dNode = Utils.loadSharedI3DFile(topArmFilename,self.baseDirectory, false, false, false);
                    if i3dNode ~= 0 then
                        local rootNode = getChildAt(i3dNode, 0);
                        link(baseNode, rootNode);
                        delete(i3dNode);
                        setTranslation(rootNode, 0,0,0);
                        local translationNode = getChildAt(rootNode, 0);
                        local referenceNode = getChildAt(translationNode, 0);
						
                        local topArm = {};
                        topArm.rotationNode = rootNode;
                        topArm.rotX, topArm.rotY, topArm.rotZ = 0,0,0;
                        topArm.translationNode = translationNode;

						if self.vehicle.addWashableNode ~= nil then
							topArm.hpDirtObjectAdded = true;
							self.vehicle:addWashableNode(rootNode);
						end;
						
                        local _,_,referenceDistance = getTranslation(referenceNode);
                        topArm.referenceDistance = referenceDistance;

                        topArm.zScale = 1;
                        local zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName .. ".topArm#zScale"), 1));
                        if zScale < 0 then
                            topArm.rotY = math.pi;
                            setRotation(rootNode, topArm.rotX, topArm.rotY, topArm.rotZ);
                        end

                        if getNumOfChildren(rootNode) > 1 then
                            topArm.scaleNode = getChildAt(rootNode, 1);
                            local scaleReferenceNode = getChildAt(topArm.scaleNode, 0);
                            local _,_,scaleReferenceDistance = getTranslation(scaleReferenceNode);
                            topArm.scaleReferenceDistance = scaleReferenceDistance;
                        end

                        topArm.toggleVisibility = Utils.getNoNil(getXMLBool(xmlFile, baseName .. ".topArm#toggleVisibility"), false);
                        if topArm.toggleVisibility then
                            setVisibility(topArm.rotationNode, false);
                        end;

                        entry.topArm = topArm;
                    end;
                end;
            else
                local rotationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".topArm#rotationNode"));
                local translationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".topArm#translationNode"));
                local referenceNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".topArm#referenceNode"));
                if rotationNode ~= nil then
                    local topArm = {};
                    topArm.rotationNode = rotationNode;
                    topArm.rotX, topArm.rotY, topArm.rotZ = getRotation(rotationNode);
                    if translationNode ~= nil and referenceNode ~= nil then
                        topArm.translationNode = translationNode;

                        local x,y,z = getTranslation(translationNode);
                        if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then
                            print("Warning: translation of topArm of attacherJoint "..i.." is not 0/0/0 in '"..self.configFileName.."'");
                        end;
                        local ax, ay, az = getWorldTranslation(referenceNode);
                        local bx, by, bz = getWorldTranslation(translationNode);
                        topArm.referenceDistance = Utils.vector3Length(ax-bx, ay-by, az-bz);
                    end;
                    topArm.zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName .. ".topArm#zScale"), 1));
                    topArm.toggleVisibility = Utils.getNoNil(getXMLBool(xmlFile, baseName .. ".topArm#toggleVisibility"), false);
                    if topArm.toggleVisibility then
                        setVisibility(topArm.rotationNode, false);
                    end;

                    entry.topArm = topArm;
                end;
            end;
            local rotationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".bottomArm#rotationNode"));
            local translationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".bottomArm#translationNode"));
            local referenceNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. ".bottomArm#referenceNode"));
            if rotationNode ~= nil then
                local bottomArm = {};
                bottomArm.rotationNode = rotationNode;
                bottomArm.rotX, bottomArm.rotY, bottomArm.rotZ = getRotation(rotationNode);
                if translationNode ~= nil and referenceNode ~= nil then
                    bottomArm.translationNode = translationNode;

                    local x,y,z = getTranslation(translationNode);
                    if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then
                        print("Warning: translation of bottomArm '"..getName(translationNode).."' of attacherJoint "..i.." is "..math.abs(x) .. "/" .. math.abs(y) .. "/" .. math.abs(z) .. "! Should be 0/0/0! ("..self.configFileName..")");
                    end;
                    local ax, ay, az = getWorldTranslation(referenceNode);
                    local bx, by, bz = getWorldTranslation(translationNode);
                    bottomArm.referenceDistance = Utils.vector3Length(ax-bx, ay-by, az-bz);
                end;
                bottomArm.zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName .. ".bottomArm#zScale"), 1));
                entry.bottomArm = bottomArm;
            end;
            
			-- rootNode must have collision.. 
			local rootNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName .. "#rootNode"));
			
			if rootNode ~= nil then
				local hasCollision = getRigidBodyType(rootNode);
				
				if hasCollision == "Dynamic" then
					entry.rootNode = rootNode;
				else
					print("  HardPoint - Error: rootNode aren't an collision component! for hardpoint object " .. self.object.name);
					rootNode = nil;
				end;
			end;
			
			if rootNode == nil then
				local componentId = getXMLInt(xmlFile, baseName .."#rootNodeVehicleComponentId");
				
				if componentId ~= nil then
					if self.vehicle.components[componentId] ~= nil then
						entry.rootNode = self.vehicle.components[componentId].node
					else
						print("  HardPoint - Error: rootNodeVehicleComponentId is invalid! for hardpoint object " .. self.object.name);
						break;
					end;
				else
					print("  HardPoint - Error: rootNode or rootNodeVehicleComponentId is nil! for hardpoint object " .. self.object.name);
					break;
				end;
			end;
			
			entry.jointIndex = 0;
            table.insert(joints, entry);
        end;

		i = i + 1;
	end;
	
	if self.vehicle.addWashableNode ~= nil then
		self.vehicle:setDirtAmount(self.vehicle:getDirtAmount(), true);
	end;
	
	return joints;
end;

function AttacherJoint:delete()
	if self.tableIds ~= nil then
		for _, i in ipairs(self.tableIds) do
			if self.vehicle.removeWashableNode ~= nil then
				local topArmNode = self.vehicle.attacherJoints[i].topArm;
				
				if topArmNode ~= nil and topArmNode.hpDirtObjectAdded ~= nil then
					self.vehicle:removeWashableNode(topArmNode.rootNode);
				end;
			end;
			
			table.remove(self.vehicle.attacherJoints, i);
			
			if self.vehicle.schemaOverlay ~= nil then
				table.remove(self.vehicle.schemaOverlay.attacherJoints, i);
			end;
		end;
	end;
end;

function AttacherJoint:loadAttributesAndNodes(xmlFile, key, resetVehicles)
	if not resetVehicles then
		local i = 0;
		while true do
			local hardKey = key .. string.format(".attacherJoint(%d)", i);
			if not hasXMLProperty(xmlFile, hardKey) then break; end;
			
			local id1 = getXMLInt(xmlFile, hardKey .. "#attachedTo");
			local jointIndex = getXMLInt(xmlFile, hardKey .. "#jointIndex");
			local inputJointDescIndex = Utils.getNoNil(getXMLInt(xmlFile, hardKey .. "#inputJointDescIndex"), 1);
			
			if id1 ~= nil and jointIndex ~= nil then
				local vehicle1 = g_currentMission.vehicles[id1];
				
				if vehicle1 ~= nil and vehicle1.inputAttacherJoints[inputJointDescIndex] ~= nil and self.vehicle.attacherJoints[jointIndex] ~= nil and self.vehicle.attacherJoints[jointIndex].jointIndex == 0 then
					self.vehicle:attachImplement(vehicle1, inputJointDescIndex, jointIndex, true);

					local moveDown = getXMLBool(xmlFile, hardKey .. "#moveDown");
					if moveDown ~= nil then
						self.vehicle:setJointMoveDown(jointIndex, moveDown, true);
					end;
				end;
			end;
			
			i = i + 1;
		end;
	end;
end;

function AttacherJoint:getSaveAttributesAndNodes(nodeIdent)
	local nodes = "";
	
	if self.tableIds ~= nil then
		for _, i in ipairs(self.tableIds) do
			local implementIndex = self.vehicle:getImplementIndexByJointDescIndex(i);
			if implementIndex ~= nil then
				local implement = self.vehicle.attachedImplements[implementIndex];
				local object = implement.object;
				
				if object ~= nil then
					local couldBeId; -- = g_currentMission.savedVehiclesToId[object];
					
					if couldBeId == nil then -- not all seem to be stored in savedVehiclesToId
						for i, v in pairs(g_currentMission.vehicles) do
							if v == object then
								couldBeId = i;
								break;
							end;
						end;
					end;
					local jointDescIndex = implement.jointDescIndex;
					local jointDesc = self.vehicle.attacherJoints[jointDescIndex];
					local inputJointDescIndex = implement.object.inputAttacherJointDescIndex;
					local moveDown = jointDesc.moveDown;
					
					nodes = nodes .. nodeIdent .. '    <attacherJoint attachedTo="' .. couldBeId .. '" inputJointDescIndex="' .. inputJointDescIndex .. '" jointIndex="' .. jointDescIndex .. '" moveDown="' .. tostring(moveDown) .. '" />';
				end;
			end;
		end;
	end;
	
	return nil, nodes;
end;

function AttacherJoint:readStream(streamId, connection)
	if self.tableIds ~= nil then
		for _, i in ipairs(self.tableIds) do
			local attachedImplementIndex = streamReadInt8(streamId);
			
			if attachedImplementIndex ~= 0 then
				local implementId = streamReadInt32(streamId);
				local inputJointDescIndex = streamReadInt8(streamId);
				local jointDescIndex = streamReadInt8(streamId);
				local moveDown = streamReadBool(streamId);
				
				local object = networkGetObject(implementId);
				if object ~= nil then
					self.vehicle:attachImplement(object, inputJointDescIndex, jointDescIndex, true, attachedImplementIndex);
					self.vehicle:setJointMoveDown(jointDescIndex, moveDown, true);
				end;
			end;
		end;
	end;
end;

function AttacherJoint:writeStream(streamId, connection)
	if self.tableIds ~= nil then
		for _, i in ipairs(self.tableIds) do
			local attachedImplementIndex = self.vehicle:getImplementIndexByJointDescIndex(i);
			
			if attachedImplementIndex == nil then
				attachedImplementIndex = 0;
			end;
			
			streamWriteInt8(streamId, attachedImplementIndex);
			
			if attachedImplementIndex ~= 0 then
				local implement = self.vehicle.attachedImplements[attachedImplementIndex];
				local inputJointDescIndex = implement.object.inputAttacherJointDescIndex;
				local jointDescIndex = implement.jointDescIndex;
				local jointDesc = self.vehicle.attacherJoints[jointDescIndex];
				local moveDown = jointDesc.moveDown;
				streamWriteInt32(streamId, networkGetObjectId(implement.object));
				streamWriteInt8(streamId, inputJointDescIndex);
				streamWriteInt8(streamId, jointDescIndex);
				streamWriteBool(streamId, moveDown);
			end;
		end;
	end;
end;

function AttacherJoint:update(dt)
end;

function AttacherJoint:updateTick(dt)
	if self.controlUnitVersion == nil then -- fallback for older versions
		if self.tableIds ~= nil then
			local allowDetachOnLocation = true;
			
			for _, i in ipairs(self.tableIds) do
				if self.vehicle:getImplementIndexByJointDescIndex(i) ~= nil then
					allowDetachOnLocation = false;
					break;
				end;
			end;
			
			self.vehicle.hardPoints[self.locationId].allowRemoval = allowDetachOnLocation;
		end;
	end;
end;

function AttacherJoint:draw()
end;


-- overrides we need to work in mp
if not Utils.HPAddonJointMPFix20150920 then
	Utils.HPAddonJointMPFix20150920 = true;
	
	local attachImplementOld = Vehicle.attachImplement;
	Vehicle.attachImplement = function(self, object, inputJointDescIndex, jointDescIndex, noEventSend, index, startLowered)
		local jointDesc = self.attacherJoints[jointDescIndex];
		
		if jointDesc ~= nil then
			attachImplementOld(self, object, inputJointDescIndex, jointDescIndex, noEventSend, index, startLowered);
		else
			-- we are addon joint
		end;
	end;
	
	local setJointMoveDownOld = Vehicle.setJointMoveDown;
	Vehicle.setJointMoveDown = function(self, jointDescIndex, moveDown, noEventSend)
		local jointDesc = self.attacherJoints[jointDescIndex];
		
		if jointDesc ~= nil then
			setJointMoveDownOld(self, jointDescIndex, moveDown, noEventSend);
		else
			-- we are addon joint
		end;
	end;
end;