--
-- PoettingerSynkro3030nova
-- Specialization for Poettinger Synkro 3030 nova
--
-- @author    PES 4ever
-- @date      21/11/12
-- @version   1.0
-- @history   1.0 - Initial version
--
-- Copyright (C) PES 4ever, All Rights Reserved.
--

PoettingerSynkro3030nova = {};

function PoettingerSynkro3030nova.prerequisitesPresent(specializations)
    return true;--SpecializationUtil.hasSpecialization(cultivator, specializations);
end;

function PoettingerSynkro3030nova:load(xmlFile)

	--self.transLimit = SpecializationUtil.callSpecializationsFunction("transLimit");
	self.foldSideDiscs = SpecializationUtil.callSpecializationsFunction("foldSideDiscs");
	self.playerTriggerCallback = SpecializationUtil.callSpecializationsFunction("playerTriggerCallback");
	self.setHole = SpecializationUtil.callSpecializationsFunction("setHole");
	self.setNewLinearDrive = SpecializationUtil.callSpecializationsFunction("setNewLinearDrive");
	--self.updateJointLimits = SpecializationUtil.callSpecializationsFunction("updateJointLimits");
	
	self.playerTriggers = {};
	self.playerTriggersId = {};
	local i = 0;
	while true do
		local baseName = string.format("vehicle.playerTriggers.playerTrigger(%d)", i);
		if not hasXMLProperty(xmlFile, baseName) then
			break;
		end;		
		local node = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName.."#index"));
		if node ~= nil then
			local entry = {};
			entry.node = node;
			entry.playerInTrigger = false;			
			addTrigger(node, "playerTriggerCallback", self);			
			table.insert(self.playerTriggers, entry);
			self.playerTriggersId[node] = i + 1;
			--self.playerTriggers[node] = entry;
		end;
		i = i + 1;
	end;
	
	-- Side discs animation
	self.sideDiscsFolded = false;
	self.doFastMovement = false;
	self.sideDiscsAnim = "sideDiscs";
	
	-- Roll joint and coordinate system
	self.rollJoint = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.rollJoint#index"));
	self.rollJointRefFrame = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.rollJoint#referenceFrame"));
	self.rollLastZ = 0;
	
	-- Limit A and B
	self.limits = {};
	self.limits['A'] = {};
	self.limits['A'].node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.limitA#index"));
	self.limits['A'].hole = getXMLInt(xmlFile, "vehicle.rollMovement.limitA#hole");
	self.limits['B'] = {};
	self.limits['B'].node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.limitB#index"));
	self.limits['B'].hole = getXMLInt(xmlFile, "vehicle.rollMovement.limitB#hole");

	-- Parts
	self.adjustRod = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.parts#adjustRod"));
	self.adjustRodRefPoint = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.parts#adjustRodRefPoint"));
	self.leverTop = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.parts#leverTop"));
	local lengthPoint = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rollMovement.parts#leverBottomLengthPoint"));
	if lengthPoint ~= nil then
		self.leverLength = Utils.vector3Length(getTranslation(lengthPoint));
	end;
	
	-- Holes
	self.holes = {};
	self.holes.count = getXMLInt(xmlFile, "vehicle.rollMovement.holes#count");
	self.holes.minDelta = getXMLInt(xmlFile, "vehicle.rollMovement.holes#minDelta");
	self.holes.deltaZ = getXMLFloat(xmlFile, "vehicle.rollMovement.holes#deltaZ");
	
	-- Calculated values for animation
	self.varA = Utils.vector3Length(worldToLocal(self.adjustRodRefPoint, getWorldTranslation(self.leverTop)));
	self.varB = Utils.vector3Length(worldToLocal(self.adjustRod, getWorldTranslation(self.leverTop)));
	
	--self.varL = Utils.vector3Length(worldToLocal(self.adjustRodRefPoint, getWorldTranslation(self.adjustRod)));
	
	local xA, yA, zA = worldToLocal(self.adjustRod, getWorldTranslation(self.limits['A'].node));
	local xB, yB, zB = worldToLocal(self.adjustRod, getWorldTranslation(self.limits['B'].node));
	local x, y, z = 0, 0.5 * (yA + yB), 0.5 * (zA + zB);
	self.varL = Utils.vector2Length(y, z);
	
	
	self.forceLimit = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.jointForceLimits#roll"), 0.1);
	
	self.updateJointLimits = true;
	self.firstRun = true;
	self.brakeForce = 5;
	self.updateWheels = false;
end;

function PoettingerSynkro3030nova:delete()
	for _, playerTrigger in pairs(self.playerTriggers) do
		removeTrigger(playerTrigger.node);
	end;
end;

function PoettingerSynkro3030nova:readStream(streamId, connection)
	self:setHole('A', streamReadInt8(streamId), true);
	self:setHole('B', streamReadInt8(streamId), true);
	
	self.doFastMovement = true;
    self:foldSideDiscs(streamReadBool(streamId), true);
end;

function PoettingerSynkro3030nova:writeStream(streamId, connection)
	streamWriteInt8(streamId, self.limits['A'].hole);
	streamWriteInt8(streamId, self.limits['B'].hole);
	
	streamWriteBool(streamId, self.sideDiscsFolded);
end;

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

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

function PoettingerSynkro3030nova:update(dt)
	if g_currentMission.currentVehicle == nil then
		if g_gui.currentGui == nil and not g_currentMission.isPlayerFrozen then
			if self.playerTriggers[1].playerInTrigger then
				local holeMinDelta = math.floor(math.abs(((self.holes.minDelta + 1) / 2 + 1) * self.holes.deltaZ) * 100) / 100;
				--local holeMinDelta = math.abs(((self.holes.minDelta + 1) / 2 + 1) * self.holes.deltaZ);
				if self.limits['A'].hole < self.holes.count - 1 then
					g_currentMission:addHelpButtonText(g_i18n:getText("LIMIT_A_PLUS"), InputBinding.LIMIT_A_PLUS);
					if InputBinding.hasEvent(InputBinding.LIMIT_A_PLUS) then
						self:setHole('A', self.limits['A'].hole + 1);				
					end;
				end;
				local x, y, z = worldToLocal(self.limits['A'].node, getWorldTranslation(self.adjustRodRefPoint));
				--print(math.abs(z));
				--print(holeMinDelta);
				if math.abs(z) >= holeMinDelta then
					g_currentMission:addHelpButtonText(g_i18n:getText("LIMIT_A_MINUS"), InputBinding.LIMIT_A_MINUS);
					if InputBinding.hasEvent(InputBinding.LIMIT_A_MINUS) then
						self:setHole('A', self.limits['A'].hole - 1);
					end;
				end;
				local x, y, z = worldToLocal(self.limits['B'].node, getWorldTranslation(self.adjustRodRefPoint));
				if math.abs(z) >= holeMinDelta then
					g_currentMission:addHelpButtonText(g_i18n:getText("LIMIT_B_PLUS"), InputBinding.LIMIT_B_PLUS);
					if InputBinding.hasEvent(InputBinding.LIMIT_B_PLUS) then
						self:setHole('B', self.limits['B'].hole + 1);
					end;
				end;
				if self.limits['B'].hole > 0 then
					g_currentMission:addHelpButtonText(g_i18n:getText("LIMIT_B_MINUS"), InputBinding.LIMIT_B_MINUS);
					if InputBinding.hasEvent(InputBinding.LIMIT_B_MINUS) then
						self:setHole('B', self.limits['B'].hole - 1);
					end;
				end;
			end;
			if self.playerTriggers[2].playerInTrigger then
				g_currentMission:addHelpButtonText(g_i18n:getText("SIDE_DISCS"), InputBinding.SIDE_DISCS);
				if InputBinding.hasEvent(InputBinding.SIDE_DISCS) then
					self:foldSideDiscs(not self.sideDiscsFolded);
				end;
			end;
		end;
	end;
	
	-- Brake roll if not lowered to stop wheel from endless spin(WheelUtil: bearings have no friction)
	if self:getIsActive() then
		if self.firstTimeRun and self.isServer then
			local brakeForce = 0;
		
			if not self:isLowered() then
			   brakeForce = 6;
			end;
			
			for _,wheel in pairs(self.wheels) do
				if not wheel.hasGroundContact then
					setWheelShapeProps(wheel.node, wheel.wheelShape, 0, brakeForce, wheel.steeringAngle);
				end;
			end;
		end;
	end;
end;

function PoettingerSynkro3030nova:updateTick(dt)
	if self.firstRun then
		-- For damping the roll joint
		self:setNewLinearDrive(self.componentJoints[1], self.forceLimit, 1); -- y-axis
		self.firstRun = false;
	end;

	--if self:getIsActive() or self.isActive then
		if self.updateJointLimits then		
			-- Set allowed roll movement in y-direction
			local transLimitA = PoettingerSynkro3030nova.transLimit(self, self.varL + (self.limits['A'].hole - (self.holes.minDelta + 1)) * self.holes.deltaZ);
			local transLimitB = PoettingerSynkro3030nova.transLimit(self, self.varL + self.limits['B'].hole * self.holes.deltaZ);
			local transLimit = 0.5 * (transLimitA - transLimitB);
			local x, y, z = getTranslation(self.rollJoint);
			y = 0.5 * (transLimitA + transLimitB);
			setTranslation(self.rollJoint, x, y, z);
			if self.isServer then
				setJointTranslationLimit(self.componentJoints[1].jointIndex, 1, true, -transLimit, transLimit);
				setJointFrame(self.componentJoints[1].jointIndex, 0, self.componentJoints[1].jointNode);
			end;
			
			-- Set maximum depth of depth limiter
			self.depthLimiters[1].depthMax = transLimitA + self.varL * 0.034899; -- minimum at L * sin(2°)
			
			self.updateJointLimits = false;
			self:setMovingToolDirty(self.limits['A'].node);
			self:setMovingToolDirty(self.limits['B'].node);
		end;	
	
		-- Set roll position in z-direction
		local x, y, z = getTranslation(self.rollJoint);
		local xj, yj, zj = worldToLocal(self.rollJointRefFrame, getWorldTranslation(self.components[2].node));
		if self.leverLength > yj then
			z = -math.sqrt(self.leverLength^2 - yj^2);
			
			local lastZDec = math.floor(z * 10^4 + 0.5) / 10^4; -- makes it less sensitive for "updates", its still very sensitive for changes to z.
			
			if self.rollLastZ ~= lastZDec then 
				setTranslation(self.rollJoint, x, y, z);
				if self.isServer then
					setJointFrame(self.componentJoints[1].jointIndex, 0, self.componentJoints[1].jointNode);
				end;
				self:setMovingToolDirty(self.limits['A'].node);
				self:setMovingToolDirty(self.limits['B'].node);
				self.rollLastZ = lastZDec;
			end;
		end;
		
		-- Get fold animation time for speed rotating parts
		 self.foldAnimTime = self:getAnimationTime(self.sideDiscsAnim);		
	--end;
end;

function PoettingerSynkro3030nova:aiTurnOn()
	-- Move side discs down
	self:foldSideDiscs(true, true);
end;

function PoettingerSynkro3030nova:draw()
	if g_currentMission.showHelpText and self.isClient then
		g_currentMission:addExtraPrintText(g_i18n:getText("EXTRA_HELP"));
	end;
end;

function PoettingerSynkro3030nova.transLimit(self, l)	
	return self.leverLength / (2 * self.varA * self.varB) * (self.varA^2 + self.varB^2 - l^2);
end;

function PoettingerSynkro3030nova:setHole(limitName, hole, noEventSend)

	local limit = self.limits[limitName];
	if limit ~= nil then
		if hole ~= limit.hole then
			limit.hole = hole;
			setTranslation(limit.node, 0, 0, limit.hole * self.holes.deltaZ);	
			SetHoleEvent.sendEvent(self, limitName, hole, noEventSend);
			self.updateJointLimits = true;		
		end;
	end;
end;

function PoettingerSynkro3030nova:playerTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
	if onEnter or onStay then
		if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
			self.playerTriggers[self.playerTriggersId[triggerId]].playerInTrigger = true;
		end;
	elseif onLeave then
		self.playerTriggers[self.playerTriggersId[triggerId]].playerInTrigger = false;
	end;
end;


function PoettingerSynkro3030nova:foldSideDiscs(isFolded,noEventSend)
	FoldSideDiscsEvent.sendEvent(self, isFolded, noEventSend);
	
	local name = self.sideDiscsAnim;
	local animation = self.animations[name];
	if animation ~= nil then			
		local speed = 1;
		if self.doFastMovement then
			speed = 100;
			self.doFastMovement = false;
		end;
		
		local animTime = nil;
		if self:getIsAnimationPlaying(name) then
			animTime = self:getAnimationTime(name);
		end;
		
		if isFolded then
			self:playAnimation(name, 1*speed, animTimeTank, true);	
		else
			self:playAnimation(name, -1*speed, animTimeTank, true);
		end;
		
		self.sideDiscsFolded = isFolded;
	end;

end;

function PoettingerSynkro3030nova:setNewLinearDrive(jointDesc, forceLimit, axis)

	if self.isServer then
		simulatePhysics(false);
		removeJoint(jointDesc.jointIndex);

		local index1 = jointDesc.componentIndices[1];
		local index2 = jointDesc.componentIndices[2];
		
		local constr = JointConstructor:new();
		constr:setActors(self.components[index1].node, self.components[index2].node);
		--constr:setJointTransforms(jointDesc.jointNode, jointDesc.jointNode);
		constr:setJointTransforms(jointDesc.jointNode, self.components[index2].node);

		for i = 1, 3 do
			local rotLimit = jointDesc.rotLimit[i];
			--print("rotLimit "..i..": "..rotLimit);
			if rotLimit >= 0 then
				constr:setRotationLimit(i - 1, -rotLimit, rotLimit);			
			end;
			local transLimit = jointDesc.transLimit[i];
			--print("transLimit "..i..": "..transLimit);
			if transLimit >= 0 then
				constr:setTranslationLimit(i - 1, true, -transLimit, transLimit);
			else
				constr:setTranslationLimit(i - 1, false, 0, 0);
			end;
		end;
		if forceLimit ~= nil and axis >= 0 and axis <= 2 then		
			-- axis, usePos, useVel, spring, damper, forceLimit, posTarget, velTarget
			constr:setLinearDrive(axis, false, true, 0, 0, forceLimit, 0, 0);			
			--print("forceLimit: "..forceLimit);
		end;
		
		--[[if jointDesc.isBreakable then
			constr:setBreakable(jointDesc.breakForce, jointDesc.breakTorque);
			print("breakForce: "..jointDesc.breakForce);
			print("breakTorque: "..jointDesc.breakTorque);
		end;]]
		
		jointDesc.jointIndex = constr:finalize();
		simulatePhysics(true);
	end;
	
end;

function PoettingerSynkro3030nova:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	local sideDiscsFolded = false;
	
	if not resetVehicles then
		self:setHole('A', Utils.getNoNil(getXMLInt(xmlFile, key.."#limitA"), self.limits['A'].hole), true);
		self:setHole('B', Utils.getNoNil(getXMLInt(xmlFile, key.."#limitB"), self.limits['B'].hole), true);
		sideDiscsFolded = Utils.getNoNil(getXMLBool(xmlFile, key .. "#sideDiscsFolded"), false);
	end;
	
	self.doFastMovement = true;
	self:foldSideDiscs(sideDiscsFolded, true);
	
	return BaseMission.VEHICLE_LOAD_OK;
end;

function PoettingerSynkro3030nova:getSaveAttributesAndNodes(nodeIdent)
	local attributes = 'limitA="'..tostring(self.limits['A'].hole)..'" '..'limitB="'..tostring(self.limits['B'].hole)..'" '..'sideDiscsFolded="'..tostring(self.sideDiscsFolded)..'"';
	return attributes, nil;
end;

--
-- SetHoleEvents
-- Networkevent for Poettinger Synkro 3030 nova
--
-- @author    PES 4ever
-- @date      27/03/13
-- @version   1.0
-- @history   1.0 - Initial version
--
-- Copyright (C) PES 4ever, All Rights Reserved.
--

SetHoleEvent = {};
SetHoleEvent_mt = Class(SetHoleEvent, Event);

InitEventClass(SetHoleEvent, "SetHoleEvent");

function SetHoleEvent:emptyNew()
    local self = Event:new(SetHoleEvent_mt);
    return self;
end;

function SetHoleEvent:new(vehicle, name, hole)
    local self = SetHoleEvent:emptyNew()
    self.vehicle = vehicle;
	self.name = name; -- here 'A' or 'B'
	self.hole = hole;
    return self;
end;

function SetHoleEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.vehicle = networkGetObject(id);
	self.name = streamReadString(streamId);
	self.hole = streamReadInt8(streamId);
    self:run(connection);
end;

function SetHoleEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));	
	streamWriteString(streamId, self.name);
	streamWriteInt8(streamId, self.hole);
end;

function SetHoleEvent:run(connection)
	self.vehicle:setHole(self.name, self.hole, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(SetHoleEvent:new(self.vehicle, self.name, self.hole), nil, connection, self.vehicle);
	end;
end;

function SetHoleEvent.sendEvent(vehicle, name, hole, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(SetHoleEvent:new(vehicle, name, hole), nil, nil, vehicle);
		else
			g_client:getServerConnection():sendEvent(SetHoleEvent:new(vehicle, name, hole));
		end;
	end;
end;


--
-- FoldSideDiscsEvent
-- Networkevent for Poettinger Synkro 3030 nova
--
-- @author    farmer joe
-- @date      31/07/13
-- @version   1.0
-- @history   1.0 - Initial version
--
-- Copyright (C) farmer joe, All Rights Reserved.
--

FoldSideDiscsEvent = {};
FoldSideDiscsEvent_mt = Class(FoldSideDiscsEvent, Event);

InitEventClass(FoldSideDiscsEvent, "FoldSideDiscsEvent");

function FoldSideDiscsEvent:emptyNew()
    local self = Event:new(FoldSideDiscsEvent_mt);
    return self;
end;

function FoldSideDiscsEvent:new(vehicle, isActive)
    local self = FoldSideDiscsEvent:emptyNew()
    self.vehicle = vehicle;
	self.isActive = isActive;
    return self;
end;

function FoldSideDiscsEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.isActive = streamReadBool(streamId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function FoldSideDiscsEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteBool(streamId, self.isActive);
end;

function FoldSideDiscsEvent:run(connection)
	self.vehicle:foldSideDiscs(self.isActive, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(FoldSideDiscsEvent:new(self.vehicle, self.isActive), nil, connection, self.vehicle);
    end;
end;

function FoldSideDiscsEvent.sendEvent(vehicle, isActive, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(FoldSideDiscsEvent:new(vehicle, isActive), nil, nil, vehicle);
		else
			g_client:getServerConnection():sendEvent(FoldSideDiscsEvent:new(vehicle, isActive));
		end;
	end;
end;