--
-- Copyright © 2013 Joacim Sigfridsson <joxxer@msn.com>
-- This work is free. You can redistribute it and/or modify it under the
-- terms of the Do What The Fuck You Want To Public License, Version 2,
-- as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
--
--
-- BJR_AttachablePTO
-- 
-- Specialization for attachable PTO
--
-- @author  JoXXer
-- @date  	23/05/13
--
-- @history	v1.0 - Initial implementation
--			v1.0.5 - Added support for attacherVehicle without PTO-node
--
--
-- @usage
-- XML: 
-- <powerTakeOffShaft shaftIndex="MAIN_SHAFT_INDEX" movingPart="MOVING_SHAFT_INDEX" referenceNode="REFERENCE_INDEX" tractorFixPoint="TRACTOR_FIX_POINT_INDEX" implementYokeIndex="YOKE_INDEX" tractorYokeIndex="YOKE_INDEX" innerYoke1="YOKE_INDEX" innerYoke2="YOKE_INDEX" distanceModifier="MODIFIER_FLOAT"/>
--
-- MODDESC:
-- inputBindings:
-- <input name="BJR_TOGGLE_PTO" category="VEHICLE" key1="KEY_q" key2="" button="" device="0" mouse="" />	
--
-- l10n:
--	<text name="BJR_ATTACH_PTO">
--        <en>Attach PTO</en>
--        <de>Attach PTO</de>
--    </text>
--	<text name="BJR_DETACH_PTO">
--        <en>Detach PTO</en>
--        <de>Detach PTO</de>
--    </text>
--	<text name="BJR_TOGGLE_PTO">
--        <en>Attach/Detach PTO</en>
--        <de>Attach/Detach PTO</de>
--    </text>
--

BJR_AttachablePTO = {};

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

function BJR_AttachablePTO:load(xmlFile)
	self.togglePTOAttach = SpecializationUtil.callSpecializationsFunction("togglePTOAttach");
	
	self.attachablePTO = {};
    self.attachablePTO.implementYoke = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#implementYokeIndex"));
    self.attachablePTO.tractorYoke = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#tractorYokeIndex"));
	
    self.attachablePTO.tractorFixPoint = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#tractorFixPoint"));
	self.attachablePTO.tractorFixPointTrans = {getTranslation(self.attachablePTO.tractorFixPoint)};
	self.attachablePTO.tractorFixPointRot = {getRotation(self.attachablePTO.tractorFixPoint)};
	
    self.attachablePTO.shaft = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#shaftIndex"));
    self.attachablePTO.shaftRot = {getRotation(self.attachablePTO.shaft)};
	
    self.attachablePTO.referenceNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#referenceNode"));
    self.attachablePTO.movingPart = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#movingPart"));
	self.attachablePTO.movingPartTrans = {getTranslation(self.attachablePTO.movingPart)};
	
    self.attachablePTO.innerYoke1 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#innerYoke1"));
    self.attachablePTO.innerYoke2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.powerTakeOffShaft#innerYoke2"));
	self.attachablePTO.yokeRotation = 0;
	
    self.attachablePTO.distanceModifier = getXMLFloat(xmlFile, "vehicle.powerTakeOffShaft#distanceModifier");
	
	self.attachablePTO.tractorPTONode = nil;
	self.attachablePTO.jointDescIndex = nil;
	self.attachablePTO.inRange = false;
	self.attachablePTO.attached = false;
end;

function BJR_AttachablePTO:delete()
end;

function BJR_AttachablePTO:readStream(streamId, connection)
	self:togglePTOAttach(streamReadBool(streamId), true);
end;

function BJR_AttachablePTO:writeStream(streamId, connection)
	streamWriteBool(streamId, self.attachablePTO.attached);
end;

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

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

function BJR_AttachablePTO:update(dt)
	if self.attachablePTO.tractorPTONode then
		-- Manage key events for inrange pto --
		if self.attachablePTO.inRange then
			if InputBinding.hasEvent(InputBinding.BJR_TOGGLE_PTO) then
				if self.attachablePTO.attached then
					self:togglePTOAttach(false);
				else
					self:togglePTOAttach(true);
				end;
			end;
			-- Display key when in range --
			if self.attachablePTO.attached then
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_DETACH_PTO"), self.typeDesc), InputBinding.BJR_TOGGLE_PTO);
			else
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_ATTACH_PTO"), self.typeDesc), InputBinding.BJR_TOGGLE_PTO);
			end;
		end;
		
		if self.attachablePTO.attached then
			if self.isTurnedOn then
				self.attachablePTO.yokeRotation = (self.attachablePTO.yokeRotation + dt*1) % (2*math.pi);
			else
				self.attachablePTO.yokeRotation = 0;
			end;
			
			if self.attachablePTO.innerYoke1 ~= nil then
				setRotation(self.attachablePTO.innerYoke1, 0, 0, self.attachablePTO.yokeRotation);
			end;
			if self.attachablePTO.innerYoke2 ~= nil then
				setRotation(self.attachablePTO.innerYoke2, 0, 0, self.attachablePTO.yokeRotation);
			end;
			if self.attachablePTO.tractorYoke ~= nil then
				setRotation(self.attachablePTO.tractorYoke, 0, 0, self.attachablePTO.yokeRotation);
			end;
			if self.attachablePTO.implementYoke ~= nil then
				setRotation(self.attachablePTO.implementYoke, 0, 0, self.attachablePTO.yokeRotation);
			end;
		
			local ptoAttX, ptoAttY, ptoAttZ = getWorldTranslation(self.attachablePTO.tractorPTONode);
			setTranslation(self.attachablePTO.tractorFixPoint, 0, 0, 0);
			local localXTrans, localYTrans, localZTrans = worldToLocal(self.attachablePTO.tractorFixPoint, ptoAttX, ptoAttY, ptoAttZ);
			setTranslation(self.attachablePTO.tractorFixPoint, localXTrans, localYTrans, localZTrans+self.attachablePTO.distanceModifier);
			
			setDirection(self.attachablePTO.shaft, localXTrans, localYTrans, localZTrans, 0, 1, 0);
			local _,_,dist = worldToLocal(self.attachablePTO.referenceNode, ptoAttX, ptoAttY, ptoAttZ);
			local movPartX, movePartY, movPartZ = unpack(self.attachablePTO.movingPartTrans);
			setTranslation(self.attachablePTO.movingPart, movPartX, movePartY, movPartZ+dist+self.attachablePTO.distanceModifier);
		end;
	end;
end;

function BJR_AttachablePTO:updateTick(dt)
	if self.attachablePTO.tractorPTONode then
		if g_currentMission.player ~= nil then
			-- Getting the distance between the player and the pto
			local nearestDistance = 2.5; --max distance allowed
			local px, py, pz = getWorldTranslation(self.attachablePTO.tractorYoke);
			local vx, vy, vz = getWorldTranslation(g_currentMission.player.rootNode);
			local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);
			if distance < nearestDistance then
				self.attachablePTO.inRange = true;
			else
				self.attachablePTO.inRange = false;
			end;
		end;
	else
		self.attachablePTO.inRange = false;
	end;
end;

function BJR_AttachablePTO:onAttach(attacherVehicle, jointDescIndex)
	local attachedJoint = attacherVehicle.attacherJoints[jointDescIndex];
	if attachedJoint.ptoOutput.node ~= nil then
		self.attachablePTO.tractorPTONode = attachedJoint.ptoOutput.node;
		self.attachablePTO.jointDescIndex = jointDescIndex;
	else
		-- If there's no PTO-node, fake attachment (Like in a bad relationship)
		self:togglePTOAttach(true, true);
	end;
end;

function BJR_AttachablePTO:onDetach()
	self.attachablePTO.tractorPTONode = nil;
	self.attachablePTO.jointDescIndex = nil;
	
	if self.attachablePTO.attached then
		self:togglePTOAttach(false);
	end;
end;

function BJR_AttachablePTO:onSetLowered(moveDown)
end;

function BJR_AttachablePTO:draw()
	
end;

function BJR_AttachablePTO:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	local isPTOAttached = Utils.getNoNil(getXMLBool(xmlFile, key .. "#isPTOAttached"), false);
	self:togglePTOAttach(isPTOAttached);

    return BaseMission.VEHICLE_LOAD_OK;
end;

function BJR_AttachablePTO:getSaveAttributesAndNodes(nodeIdent)
    local attributes = 'isPTOAttached="' .. tostring(self.attachablePTO.attached) ..'"';
    return attributes, nil;
end;

function BJR_AttachablePTO:togglePTOAttach(attached, noEventSend)
	BJR_AttachablePTOAttachEvent.sendEvent(self, attached, noEventSend);
	
	self.attachablePTO.attached = attached;
	if self.attachablePTO.tractorPTONode then
		if self.attachablePTO.attached then
			setRotation(self.attachablePTO.tractorFixPoint, 0, 0, 0);
		else
			setTranslation(self.attachablePTO.tractorFixPoint, unpack(self.attachablePTO.tractorFixPointTrans));
			setRotation(self.attachablePTO.tractorFixPoint, unpack(self.attachablePTO.tractorFixPointRot));
			setTranslation(self.attachablePTO.movingPart, unpack(self.attachablePTO.movingPartTrans));
			setRotation(self.attachablePTO.shaft, unpack(self.attachablePTO.shaftRot));
		end;
	end;
end;


BJR_AttachablePTOAttachEvent = {};
BJR_AttachablePTOAttachEvent_mt = Class(BJR_AttachablePTOAttachEvent, Event);

InitEventClass(BJR_AttachablePTOAttachEvent, "BJR_AttachablePTOAttachEvent");

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

function BJR_AttachablePTOAttachEvent:new(vehicle, attached)
    local self = BJR_AttachablePTOAttachEvent:emptyNew()
    self.vehicle = vehicle;
	self.attached = attached;
    return self;
end;

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

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

function BJR_AttachablePTOAttachEvent:run(connection)
	self.vehicle:togglePTOAttach(self.attached, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(BJR_AttachablePTOAttachEvent:new(self.vehicle, self.attached), nil, connection, self.vehicle);
    end;
end;

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