--
-- controlledAnimation
-- Class for controlled animations
--
-- @author  ?????
-- @date
----
-- @edited  PeterJ - euroDZN
-- @date  11/09/2013
--
-- http://eurodzn.wordpress.com/
--
-- Copyright (C) euroDZN, Confidential, All Rights Reserved.

controlledAnimation = {};

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

function controlledAnimation:load(xmlFile)

	self.setAnimationTime = SpecializationUtil.callSpecializationsFunction("setAnimationTime");
	
	self.animationParts = {};
	local i = 0;
	while true do
		local partName = string.format("vehicle.animationParts.animationPart(%d)", i);
		local animationPart = {};
		local partStr = getXMLString(xmlFile, partName .. "#rootNode");
		if partStr == nil then
            break;
        end;
        animationPart.rootNode = Utils.indexToObject(self.components, partStr);
		local charSet = getAnimCharacterSet(animationPart.rootNode);
		if charSet == nil then
			print("Error: invalid animation rootNode " .. partStr);
			break;
		else
			animationPart.animCharSet = charSet;
			animationPart.clip = getAnimClipIndex(animationPart.animCharSet, getXMLString(xmlFile, partName.."#clipName"));
			assignAnimTrackClip(animationPart.animCharSet, 0, animationPart.clip);
			local posInputButtonStr = getXMLString(xmlFile, partName .. "#posInputButton");
			if posInputButtonStr ~= nil then
				animationPart.posInputButton = InputBinding[posInputButtonStr];
			end;
			animationPart.posInputButton = Utils.getNoNil(animationPart.posInputButton, InputBinding.IMPLEMENT_EXTRA2);
			local negInputButtonStr = getXMLString(xmlFile, partName .. "#negInputButton");
			if negInputButtonStr ~= nil then
				animationPart.negInputButton = InputBinding[negInputButtonStr];
			end;
			animationPart.negInputButton = Utils.getNoNil(animationPart.negInputButton, InputBinding.IMPLEMENT_EXTRA4);
			animationPart.clipSpeed = Utils.getNoNil(getXMLFloat(xmlFile, partName .. "#clipSpeed"), 1);
			setAnimTrackSpeedScale(animationPart.animCharSet, animationPart.clip, animationPart.clipSpeed);
			setAnimTrackLoopState(animationPart.animCharSet, 0, false);
			animationPart.startPosition =  Utils.getNoNil(getXMLInt(xmlFile, partName .. "#startPosition"), 0);
			animationPart.currentPosition = animationPart.startPosition;
			setAnimTrackTime(animationPart.animCharSet, 0, animationPart.currentPosition);
			animationPart.animDuration = getAnimClipDuration(animationPart.animCharSet, animationPart.clip);
			if animationPart.currentPosition >= animationPart.animDuration then
				print("Error: Animation Part"..i.." startPosition larger or exakt then animation clip duration!");
				break;
			end;
			animationPart.acceleration = Utils.getNoNil(getXMLFloat(xmlFile, partName .. "#acceleration"), 0);
			animationPart.deceleration = Utils.getNoNil(getXMLFloat(xmlFile, partName .. "#deceleration"), 0)*-1;
			animationPart.offSet = Utils.getNoNil(getXMLInt(xmlFile, partName .. "#offSet"), 50);
			animationPart.loadSave = Utils.getNoNil(getXMLBool(xmlFile, partName.."#loadSave"), true);
			animationPart.showHUDinfo = Utils.getNoNil(getXMLBool(xmlFile, partName.."#showHUDinfo"), false);
			if animationPart.showHUDinfo then
				animationPart.HUDtext = getXMLString(xmlFile, partName .. "#clipName");
			end;
			local numJoints = Utils.getNoNil(getXMLInt(xmlFile, partName .. "#numJoints"), 0); 
			if numJoints > 0 then
				animationPart.joints = {};
				for j=1, numJoints do
					local jointString = string.format("%s.componentJoint%d", partName, j);
					local index = Utils.getNoNil(getXMLInt(xmlFile, jointString .."#index"), 0);
					if index == 0 then 
						print("Error: Invalid ComponenteJointIndex: "..index.."");
						break;
					end;
					local jointIndex = self.componentJoints[index];
					setJointFrame(jointIndex.jointIndex, 0, jointIndex.jointNode);
					table.insert(animationPart.joints, jointIndex);
				end;
			end;
			animationPart.animationEnabled = false;
			animationPart.inputTime = animationPart.currentPosition;
			animationPart.inputDone = false;
			animationPart.clipEndTime = false;
			animationPart.clipStartTime = false;
			animationPart.isLoading = false;
			table.insert(self.animationParts, animationPart);
        end;
        i = i + 1;
    end;
	
	self.setHydraulicDirection = SpecializationUtil.callSpecializationsFunction("setHydraulicDirection");
	
	local hydraulicsCount = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.hydraulics#count"), 0);
	self.hydraulics = {};	
	for i=1, hydraulicsCount do
		local hydraulicName = string.format("vehicle.hydraulics.hydraulic%d", i);		
		self.hydraulics[i] = {};	
		self.hydraulics[i].node = Utils.indexToObject(self.components, getXMLString(xmlFile, hydraulicName .. "#index"));
		self.hydraulics[i].punch = Utils.indexToObject(self.components, getXMLString(xmlFile, hydraulicName .. "#punch"));
		self.hydraulics[i].fixPoint = Utils.indexToObject(self.components, getXMLString(xmlFile, hydraulicName .. "#fixpoint"));	
		local xUp, yUp, zUp = Utils.getVectorFromString(Utils.getNoNil(getXMLString(xmlFile, hydraulicName .. "#upVectors"),"0 1 0"));
		self.hydraulics[i].upVectors = {xUp, yUp, zUp};
		self.hydraulics[i].invertZ = Utils.getNoNil(getXMLBool(xmlFile, hydraulicName.."#invertZ"), false);
		self.hydraulics[i].scaleZ = Utils.getNoNil(getXMLBool(xmlFile, hydraulicName.."#scaleZ"), false);
		if self.hydraulics[i].node ~= nil then
			local ax, ay, az = getWorldTranslation(self.hydraulics[i].node);
			local bx, by, bz = getWorldTranslation(self.hydraulics[i].fixPoint);		
			self.hydraulics[i].referenceDistance = Utils.vector3Length(ax-bx, ay-by, az-bz);
		end;
	end;
	self.setHydraulicTime = 30;
end;

function controlledAnimation:delete()
end;

function controlledAnimation:readStream(streamId, connection)
	for k, animationPart in pairs(self.animationParts) do
		local timeInput = streamReadInt32(streamId);
		self:setAnimationTime(k, timeInput, true);
		animationPart.isLoading = true;
	end;
end;

function controlledAnimation:writeStream(streamId, connection)
	for k, animationPart in pairs(self.animationParts) do
		streamWriteInt32(streamId, animationPart.inputTime);
	end;
end;

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

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

function controlledAnimation:getSaveAttributesAndNodes(nodeIdent)
	local attributes = nil;
	for k, animationPart in pairs(self.animationParts) do
		if animationPart.loadSave then
			local minTime = math.max(animationPart.currentPosition, 0);
			local maxTime = math.min(minTime, animationPart.animDuration);
			local currentTime = string.format("%d", maxTime);
			local saveAttributes = "animation" .. k .. "=\"" .. currentTime .. "\""
			if k > 1 then
				attributes = attributes .. " " .. saveAttributes;
			else
				attributes = saveAttributes;
			end;
		end;
	end;
	return attributes, nil;
end;

function controlledAnimation:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	if not resetVehicles then
		for k, animationPart in pairs(self.animationParts) do
			local keyString = string.format("#animation%d", k);
			local inputTime = Utils.getNoNil(getXMLInt(xmlFile, key .. keyString), animationPart.startPosition);
			self:setAnimationTime(k, inputTime, true);
			animationPart.isLoading = animationPart.loadSave;
		end;
	end;
	return BaseMission.VEHICLE_LOAD_OK;
end;

function controlledAnimation:update(dt)
	if self:getIsActiveForInput() then
		for nr,part in ipairs(self.animationParts) do
			if InputBinding.isPressed(part.posInputButton) then
				self:setAnimationTime(nr, part.currentPosition + (part.offSet*(dt/5)));
			elseif InputBinding.isPressed(part.negInputButton) then
				self:setAnimationTime(nr, part.currentPosition - (part.offSet*(dt/5)));
			end;
		end;
	end
	if self:getIsActive() then			
		self.setHydraulicTime = 30;	
	end;

	if self.setHydraulicTime > 0 then
		for k,v in pairs(self.hydraulics) do 
			self:setHydraulicDirection(k);
		end;
		self.setHydraulicTime = self.setHydraulicTime - 1;
	end;
end;

function controlledAnimation:updateTick(dt)
	for k, animationPart in pairs(self.animationParts) do
		if animationPart.animationEnabled then
			local currentTime = animationPart.currentPosition;
			local timeInput = animationPart.inputTime;
			local clipSpeed = animationPart.clipSpeed;
			local duration = animationPart.animDuration;
			if animationPart.isLoading then
				clipSpeed = animationPart.clipSpeed*animationPart.offSet;
				animationPart.isLoading = false;
			end;
			if animationPart.clip ~= nil then
				if currentTime < timeInput and animationPart.inputDone == false then
					setAnimTrackSpeedScale(animationPart.animCharSet, animationPart.clip, clipSpeed);
					if currentTime+animationPart.offSet >= timeInput then
						animationPart.inputDone = true;
						setAnimTrackTime(animationPart.animCharSet, animationPart.clip, timeInput);
						setAnimTrackSpeedScale(animationPart.animCharSet, animationPart.clip, 0);
					end;
				elseif currentTime > timeInput and animationPart.inputDone == false then
					setAnimTrackSpeedScale(animationPart.animCharSet, animationPart.clip, -clipSpeed);
					if currentTime-animationPart.offSet <= timeInput then
						animationPart.inputDone = true;
						setAnimTrackTime(animationPart.animCharSet, animationPart.clip, timeInput);
						setAnimTrackSpeedScale(animationPart.animCharSet, animationPart.clip, 0);
					end;
				end;
				if currentTime >= duration-animationPart.offSet then
					animationPart.clipEndTime = true;
				elseif currentTime <= animationPart.offSet then
					animationPart.clipStartTime = true;
				elseif currentTime == animationPart.startPosition then
					animationPart.clipStartTime = true;
				else
					animationPart.clipStartTime = false;
					animationPart.clipEndTime = false;
				end;
			end;
			if animationPart.joints ~= nil then
				for k, joint in pairs(animationPart.joints) do
					setJointFrame(joint.jointIndex, 0, joint.jointNode);
				end;
			end;
			animationPart.currentPosition = getAnimTrackTime(animationPart.animCharSet, animationPart.clip);
		else
			enableAnimTrack(animationPart.animCharSet, animationPart.clip);
		end;
		animationPart.animationEnabled = isAnimTrackEnabled(animationPart.animCharSet, animationPart.clip);
	end;
	for i, jointDesc in pairs(self.componentJoints) do
		setJointFrame(self.componentJoints[i].jointIndex, 0, self.componentJoints[i].jointNode);
	end;
end;

function controlledAnimation:draw()	
	if table.getn(self.animationParts) > 0 and self:getIsActiveForInput() then
		for _,part in ipairs(self.animationParts) do
			if part.showHUDinfo then
				g_currentMission:addExtraPrintText(string.format(g_i18n:getText(part.HUDtext), self.typeDesc) .. " " .. InputBinding.getKeyNamesOfDigitalAction(part.posInputButton) .. "/" .. InputBinding.getKeyNamesOfDigitalAction(part.negInputButton));
			end;
		end;
	end;
end;

function controlledAnimation:setAnimationTime(animationPart, timeInput, noEventSend)
	local minTime = math.max(timeInput, 0);
	local maxTime = math.min(minTime, self.animationParts[animationPart].animDuration);
	SetAnimationEvent.sendEvent(self, animationPart, maxTime, noEventSend);
	if maxTime ~= self.animationParts[animationPart].currentPosition then
		self.animationParts[animationPart].inputDone = false;
		self.animationParts[animationPart].inputTime = maxTime;
	end;
end;

function controlledAnimation:validateAttacherJoint(implement, jointDesc, dt)
    return true;
end;

function controlledAnimation:setHydraulicDirection(index)
	local hydraulic = self.hydraulics[index];

	if hydraulic.fixPoint ~= nil then
		local ax, ay, az = getWorldTranslation(hydraulic.node);
		local bx, by, bz = getWorldTranslation(hydraulic.fixPoint);
		local x, y, z = worldDirectionToLocal(getParent(hydraulic.node), bx-ax, by-ay, bz-az);
		if hydraulic.invertZ then
			x = -x;
			y = -y;
			z = -z;
		end;
		local xUp, yUp, zUp = unpack(hydraulic.upVectors);
		setDirection(hydraulic.node, x, y, z, xUp, yUp, zUp);
		if hydraulic.punch ~= nil then
			local dist = Utils.vector3Length(ax-bx, ay-by, az-bz);
			local newZ = dist - hydraulic.referenceDistance;
			if hydraulic.invertZ then
				dist = -dist;
				newZ = dist + hydraulic.referenceDistance;
			end;
			setTranslation(hydraulic.punch, 0, 0, newZ);
		end;
		if hydraulic.scaleZ and hydraulic.referenceDistance ~= nil then
			local len = Utils.vector3Length(x, y, z);
			setScale(hydraulic.node, 1, 1, len/hydraulic.referenceDistance);
		end
	end;
end;

SetAnimationEvent = {};
SetAnimationEvent_mt = Class(SetAnimationEvent, Event);

InitEventClass(SetAnimationEvent, "SetAnimationEvent");

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

function SetAnimationEvent:new(object, animationIndex, inputTime)
    local self = SetAnimationEvent:emptyNew()
    self.object = object;
	self.animationIndex = animationIndex;
	self.inputTime = inputTime;
    return self;
end;

function SetAnimationEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.animationIndex = streamReadInt32(streamId);
	self.inputTime = streamReadInt32(streamId);
    self:run(connection);
end;

function SetAnimationEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteInt32(streamId, self.animationIndex);
	streamWriteInt32(streamId, self.inputTime);
end;

function SetAnimationEvent:run(connection)
	self.object:setAnimationTime(self.animationIndex, self.inputTime, true)
	if not connection:getIsServer() then
		g_server:broadcastEvent(SetAnimationEvent:new(self.object, self.animationIndex, self.inputTime), nil, connection, self.object);
	end;
end;

function SetAnimationEvent.sendEvent(vehicle, animationIndex, inputTime, noEventSend)
	local animationPart = vehicle.animationParts[animationIndex];
	if animationPart ~= nil then
		if inputTime ~= vehicle.animationParts[animationIndex].inputTime then
			if noEventSend == nil or noEventSend == false then
				if g_server ~= nil then
					g_server:broadcastEvent(SetAnimationEvent:new(vehicle, animationIndex, inputTime), nil, nil, vehicle);
				else
					g_client:getServerConnection():sendEvent(SetAnimationEvent:new(vehicle, animationIndex, inputTime));
				end;
			end;
		end;
	end;
end;

