--
-- BaleGrab
-- Class for a balegrab tool
--
-- @author  Manuel Leithner / Stefan Geiger
-- @date  07/08/13
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

ExtendedBaleGrab = {};

function ExtendedBaleGrab.prerequisitesPresent(specializations)
    return true;
end;

function ExtendedBaleGrab:load(xmlFile)
    self.dynamicMountTriggerCallback = ExtendedBaleGrab.dynamicMountTriggerCallback;
    self.addDynamicMountedObject = ExtendedBaleGrab.addDynamicMountedObject;
    self.removeDynamicMountedObject = ExtendedBaleGrab.removeDynamicMountedObject;
	self.setGrabIsClosed = SpecializationUtil.callSpecializationsFunction("setGrabIsClosed"); 
	self.setDoNotAttachTimer = SpecializationUtil.callSpecializationsFunction("setDoNotAttachTimer"); 
	
	self.usesTranslations = getXMLBool(xmlFile, "vehicle.baleGrab#usesTranslations");
	
    if self.isServer then
		self.raycastNodeGroup1 = {};
		local i = 0;
		while true do
			local key = string.format("vehicle.baleGrab.raycastGroup1.node(%d)", i);
			if not hasXMLProperty(xmlFile, key) then
				break;
			end;
			local index = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
			local distance = getXMLFloat(xmlFile, key.."#distance");

			table.insert(self.raycastNodeGroup1, {index=index, distance=distance});
			i=i+1;
		end;
		
		self.raycastNodeGroup2 = {};
		local i = 0;
		while true do
			local key = string.format("vehicle.baleGrab.raycastGroup2.node(%d)", i);
			if not hasXMLProperty(xmlFile, key) then
				break;
			end;
			local index = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
			local distance = getXMLFloat(xmlFile, key.."#distance");

			table.insert(self.raycastNodeGroup2, {index=index, distance=distance});
			i=i+1;
		end;	
	
        local attacherTriggerTriggerNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGrab#triggerNode"));
        local attacherTriggerRootNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGrab#rootNode"));
        local attacherTriggerJointNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGrab#jointNode"));
        local attacherJointTypeString = Utils.getNoNil(getXMLString(xmlFile, "vehicle.baleGrab#jointType"), "TYPE_AUTO_ATTACH_Y");
        local attacherJointType = DynamicMountUtil.TYPE_AUTO_ATTACH_Y;
        if attacherJointTypeString == "TYPE_FORK" then
            attacherJointType = DynamicMountUtil.TYPE_FORK;
        elseif attacherJointTypeString == "TYPE_AUTO_ATTACH_XZ" then
            attacherJointType = DynamicMountUtil.TYPE_AUTO_ATTACH_XZ;
        elseif attacherJointTypeString == "TYPE_AUTO_ATTACH_XYZ" then
            attacherJointType = DynamicMountUtil.TYPE_AUTO_ATTACH_XYZ;
        end;
        if attacherTriggerTriggerNode ~= nil and attacherTriggerRootNode ~= nil and attacherTriggerJointNode ~= nil then
            local forceAcceleration = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleGrab#forceAcceleration"), 20);
            addTrigger(attacherTriggerTriggerNode, "dynamicMountTriggerCallback", self);

            self.dynamicMountAttacherTrigger = {triggerNode=attacherTriggerTriggerNode, rootNode=attacherTriggerRootNode, jointNode=attacherTriggerJointNode, attacherJointType=attacherJointType, forceAcceleration=forceAcceleration};
        end
        self.dynamicMountedObjects = {};
        self.pendingDynamicMountObjects = {};
    end
	
	if self.usesTranslations then
		self.movingTools[2].transMin_bac = self.movingTools[2].transMin;
		self.movingTools[1].transMax_bac = self.movingTools[1].transMax;
	else
		self.movingTools[1].rotMin_bac = self.movingTools[1].rotMin;
		self.movingTools[2].rotMax_bac = self.movingTools[2].rotMax;
	end;	
	
	self.doNotAttachTimer = 0;
	self.doNotDetachTimer = 0;
	
	self.debugMode = false;
	
	self.grabIsClosed = false;
end;

function ExtendedBaleGrab:delete()
    if self.isServer then
        for object,_ in pairs(self.dynamicMountedObjects) do
            object:unmountDynamic();
        end
    end
    if self.dynamicMountAttacherTrigger ~= nil then
        removeTrigger(self.dynamicMountAttacherTrigger.triggerNode);
    end
end;

function ExtendedBaleGrab:readStream(streamId, connection)
	self:setGrabIsClosed(streamReadBool(streamId), true);
end;

function ExtendedBaleGrab:writeStream(streamId, connection)
	streamWriteBool(streamId, self.grabIsClosed); 
end;

function ExtendedBaleGrab:setGrabIsClosed(bool, limit1, limit2, noEventSend)
	self.grabIsClosed = bool;

	if bool then
		if self.usesTranslations then
			self.movingTools[2].transMin = limit1;
			self.movingTools[1].transMax = limit2;
			local _, y, z = getTranslation(self.movingTools[2].node);
			setTranslation(self.movingTools[2].node,limit1,y,z)
			local _, y, z = getTranslation(self.movingTools[1].node);
			setTranslation(self.movingTools[1].node,limit2,y,z)
		else
			self.movingTools[1].rotMin = limit1;
			self.movingTools[2].rotMax = limit2;
			
			local x, _, z = getRotation(self.movingTools[1].node);
			setRotation(self.movingTools[1].node, x, limit1, z);
			local x, _, z = getRotation(self.movingTools[2].node);
			setRotation(self.movingTools[2].node, x, limit2, z);
		end;		
	else
		if self.usesTranslations then
			self.movingTools[2].transMin = self.movingTools[2].transMin_bac;
			self.movingTools[1].transMax = self.movingTools[1].transMax_bac;
		else
			self.movingTools[1].rotMin = self.movingTools[1].rotMin_bac;
			self.movingTools[2].rotMax = self.movingTools[2].rotMax_bac;
		end;	
	end;
	
	GrabIsClosedEvent.sendEvent(self, bool, limit1, limit2, noEventSend);
end;

function ExtendedBaleGrab:setDoNotAttachTimer(value, noEventSend)
	self.doNotAttachTimer = value;
	SetDoNotAttachTimerEvent.sendEvent(self, bool, limit1, limit2, noEventSend);
end;

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

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

function ExtendedBaleGrab:update(dt)
end;

function ExtendedBaleGrab:updateTick(dt)
	for i = 1, 2 do
		if self.movingTools[i].invertAxis then
			if self.movingTools[i].lastRotSpeed+self.movingTools[i].lastTransSpeed < 0 then
				self:setDoNotAttachTimer(0);
			end;
		else
			if self.movingTools[i].lastRotSpeed+self.movingTools[i].lastTransSpeed > 0 then
				self:setDoNotAttachTimer(0);
			end;
		end;
	end;

    if self.isServer and self:getIsActive() then
		for i = 1, 2 do
			if self.movingTools[i].invertAxis then
				if self.movingTools[i].lastRotSpeed+self.movingTools[i].lastTransSpeed < 0 then
					self.doNotAttachTimer = 0;
				end;
			else
				if self.movingTools[i].lastRotSpeed+self.movingTools[i].lastTransSpeed > 0 then
					self.doNotAttachTimer = 0;
				end;
			end;
		end;
	
		self.doNotAttachTimer = math.max(self.doNotAttachTimer - dt, 0);
		self.doNotDetachTimer = math.max(self.doNotDetachTimer - dt, 0);
		
        local hasTouch1 = false;
		local hasTouch2 = false;
		
		local bale1 = nil;
		local bale2 = nil;
		
		for _,node in pairs(self.raycastNodeGroup1) do
			bale1 = ExtendedBaleGrab.raycastBale(self, node.index, node.distance);
			if bale1 ~= nil then
				hasTouch1 = true;
			end;
		end;
		
		for _,node in pairs(self.raycastNodeGroup2) do
			bale2 = ExtendedBaleGrab.raycastBale(self, node.index, node.distance);
			if bale2 ~= nil then
				hasTouch2 = true;
			end;
		end;

		local isClosed = hasTouch1 and hasTouch2 and bale1 == bale2;
		

		
		local x2, _, _ = getTranslation(self.movingTools[2].node);
		local x1, _, _ = getTranslation(self.movingTools[1].node);		
		local _, y1, _ = getRotation(self.movingTools[1].node);
		local _, y2, _ = getRotation(self.movingTools[2].node);
		
		local isOverLimit = false;
		
		if not isClosed then
			if self.usesTranslations then
				if x2 > self.movingTools[2].transMin+0.1 and x1 < self.movingTools[1].transMax-0.1 then
					isOverLimit = true;
				end;
			else
				if y1 > self.movingTools[1].rotMin+math.rad(5) and y2 < self.movingTools[2].rotMax-math.rad(5) then
					isOverLimit = true;
				end;
			end;
		end;
		
		local savetyMode = false;
		
		if self.usesTranslations then
			if x2 > self.movingTools[2].transMax-0.15 or x1 < self.movingTools[1].transMin+0.15 then
				savetyMode = true;
			end;
		else
			if y1 > self.movingTools[1].rotMax-math.rad(10) or y2 < self.movingTools[2].rotMin+math.rad(10) then
				savetyMode = true;
			end;
		end;
		
		local fixedBales = 0;
		for object,_ in pairs(self.dynamicMountedObjects) do
			if object ~= nil then
				fixedBales = fixedBales + 1;
			end;
		end;
		
		if self.debugMode then
			renderText(0.3, 0.1, 0.02, "isClosed: "..tostring(isClosed));
			renderText(0.3, 0.13, 0.02, "hasTouch1: "..tostring(hasTouch1));
			renderText(0.3, 0.16, 0.02, "hasTouch2: "..tostring(hasTouch2));		
			renderText(0.3, 0.19, 0.02, "bale1 == bale2: "..tostring(bale1 == bale2));		
			renderText(0.3, 0.22, 0.02, "doNotAttachTimer: "..self.doNotAttachTimer);	
			renderText(0.3, 0.24, 0.02, "isOverLimit: "..tostring(isOverLimit));	
			renderText(0.3, 0.26, 0.02, "savetyMode: "..tostring(savetyMode));	
			renderText(0.3, 0.28, 0.02, "fixedBales: "..fixedBales);
		end;
		
		
		
        if isClosed and self.doNotAttachTimer == 0 and not self.grabIsClosed and fixedBales == 0 then
            for object,_ in pairs(self.pendingDynamicMountObjects) do
                if self.dynamicMountedObjects[object] == nil then
                    object:unmountDynamic();
                    if object:mountDynamic(self, self.dynamicMountAttacherTrigger.rootNode, self.dynamicMountAttacherTrigger.jointNode, self.dynamicMountAttacherTrigger.attacherJointType, self.dynamicMountAttacherTrigger.forceAcceleration) then
                        self:addDynamicMountedObject(object);
						if self.usesTranslations then
							self:setGrabIsClosed(true, x2, x1);
						else
							self:setGrabIsClosed(true, y1, y2);
						end;
						self.doNotDetachTimer = 3000; --till its broadcasted to players
                    end;
                end
            end
        elseif (((not isClosed and isOverLimit and self.grabIsClosed) or (savetyMode and fixedBales > 0)) and self.doNotDetachTimer == 0) or (fixedBales == 0 and self.grabIsClosed) then
            
			for object,_ in pairs(self.dynamicMountedObjects) do
                self:removeDynamicMountedObject(object, false);
                object:unmountDynamic();
				self.doNotAttachTimer = 20000;
            end;
			
			self:setGrabIsClosed(false);
        end;
    end
end;

function ExtendedBaleGrab:draw()
end;

function ExtendedBaleGrab:addDynamicMountedObject(object)
    self.dynamicMountedObjects[object] = object;
end

function ExtendedBaleGrab:removeDynamicMountedObject(object, isDeleting)
    self.dynamicMountedObjects[object] = nil;
    if isDeleting then
        self.pendingDynamicMountObjects[object] = nil;
    end
end

function ExtendedBaleGrab:dynamicMountTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId)
	if self.isServer then
		if onEnter then
			local object = g_currentMission:getNodeObject(otherActorId);
			if object == nil then
				object = g_currentMission.nodeToVehicle[otherActorId];
			end;
			if object ~= nil and object ~= self and object.getSupportsMountDynamic ~= nil and object:getSupportsMountDynamic() then
				self.pendingDynamicMountObjects[object] = Utils.getNoNil(self.pendingDynamicMountObjects[object], 0) + 1;
			end
		elseif onLeave then
			local object = g_currentMission:getNodeObject(otherActorId);
			if object == nil then
				object = g_currentMission.nodeToVehicle[otherActorId];
			end;
			if object ~= nil then
				if self.pendingDynamicMountObjects[object] ~= nil then
					local count = self.pendingDynamicMountObjects[object]-1;
					if count == 0 then
						self.pendingDynamicMountObjects[object] = nil;

						if self.dynamicMountedObjects[object] ~= nil then
							self:removeDynamicMountedObject(object, false);
							object:unmountDynamic();
							self.doNotAttachTimer = 20000;
							self:setGrabIsClosed(false, true);
						end
					else
						self.pendingDynamicMountObjects[object] = count;
					end
				end
			end
		end
	end;
end;

function ExtendedBaleGrab.raycastBale(self, node, distance)
	local x, y, z = localDirectionToWorld(node,0,0,1);

	local baleRaycastResult = {
		raycastCallback = function (self, transformId, x, y, z, distance)
			if self.trigger ~= transformId and getRigidBodyType(transformId) ~= "Kinematic" then --to be sure we are not raycasting triggers
				self.baleDistance = distance;
				self.scannedBale = transformId;
			end;
		end
	};
	baleRaycastResult.baleDistance = 0;
	baleRaycastResult.trigger = self.dynamicMountAttacherTrigger.triggerNode;
	baleRaycastResult.scannedBale = nil;
	
	local x1, y1, z1 = getWorldTranslation(node);
	raycastAll(x1, y1, z1, x, y, z, "raycastCallback", distance, baleRaycastResult, 16781312); 
	
	if self.debugMode then
		drawDebugArrow(x1, y1, z1, x, y, z, x, y, z, 1, 0, 0);
	end;
	
	if baleRaycastResult.baleDistance ~= 0 then
		return baleRaycastResult.scannedBale;
	end;
	
    return nil;
end

GrabIsClosedEvent = {};
GrabIsClosedEvent_mt = Class(GrabIsClosedEvent, Event);

InitEventClass(GrabIsClosedEvent, "GrabIsClosedEvent");

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

function GrabIsClosedEvent:new(vehicle, isClosed, limit1, limit2)
    local self = GrabIsClosedEvent:emptyNew()
    self.vehicle = vehicle;
	self.isClosed = isClosed;
	self.limit1 = limit1;	
	self.limit2 = limit2;	
    return self;
end;

function GrabIsClosedEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    self.vehicle = networkGetObject(id);

	self.isClosed = streamReadBool(streamId);
	self.limit1 = streamReadFloat32(streamId);	
	self.limit2 = streamReadFloat32(streamId);	
	if self.vehicle ~= nil then
		self.vehicle:setGrabIsClosed(self.isClosed, self.limit1, self.limit2, true);
	end;
	
	if not connection:getIsServer() then
        g_server:broadcastEvent(GrabIsClosedEvent:new(self.vehicle, self.isClosed, self.limit1, self.limit2), nil, connection, self.vehicle);
    end;
end;

function GrabIsClosedEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteBool(streamId, self.isClosed);
	streamWriteFloat32(streamId, self.limit1);
	streamWriteFloat32(streamId, self.limit2);
end;

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


SetDoNotAttachTimerEvent = {};
SetDoNotAttachTimerEvent_mt = Class(SetDoNotAttachTimerEvent, Event);

InitEventClass(SetDoNotAttachTimerEvent, "SetDoNotAttachTimerEvent");

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

function SetDoNotAttachTimerEvent:new(vehicle, attachTimer)
    local self = SetDoNotAttachTimerEvent:emptyNew()
    self.vehicle = vehicle;
	self.attachTimer = attachTimer;	
    return self;
end;

function SetDoNotAttachTimerEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    self.vehicle = networkGetObject(id);

	self.attachTimer = streamReadInt32(streamId);
	if self.vehicle ~= nil then
		self.vehicle:setDoNotAttachTimer(self.attachTimer, true);
	end;
	
	if not connection:getIsServer() then
        g_server:broadcastEvent(SetDoNotAttachTimerEvent:new(self.vehicle, self.attachTimer), nil, connection, self.vehicle);
    end;
end;

function SetDoNotAttachTimerEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteInt32(streamId, self.attachTimer);
end;

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


