--
-- PressureWasher
-- Specialization for PressureWasher
--
-- @author  	Manuel Leithner (SFM-Modding)
-- @version 	v3.0
-- @date  		06/11/12
-- @history:	v1.0 - Initial version
--				v2.0 - converted to 2011 and some bugfixes
--				v3.0 - LS13
--
-- free for noncommerical-usage
--

PressureWasher = {};

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

function PressureWasher:load(xmlFile)

	self.washVehicles = SpecializationUtil.callSpecializationsFunction("washVehicles");
	self.getPlayerInRange = PressureWasher.getPlayerInRange;
	self.getWashableInRange = SpecializationUtil.callSpecializationsFunction("getWashableInRange");
	self.setActiveState = SpecializationUtil.callSpecializationsFunction("setActiveState");
	self.setLanceActive = SpecializationUtil.callSpecializationsFunction("setLanceActive");
	
	self.kaercher = {};
	self.kaercher.actionDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.kaercher#actionDistance"), 10);
	self.kaercher.washDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.kaercher#washDistance"), 8);
	self.kaercher.pipe = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.kaercher#pipe"));
	self.kaercher.lance = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.kaercher#lance"));
	local x,y,z = getTranslation(self.kaercher.lance);	
	self.kaercher.lanceTrans = {x,y,z};
	x,y,z = getRotation(self.kaercher.lance);
	self.kaercher.lanceRot = {x,y,z};
	self.kaercher.pressureWasher = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.kaercher#pressureWasher"));	
	
    self.kaercher.particleSystems = {};
    local psName = "vehicle.waterParticelSystem";
    Utils.loadParticleSystem(xmlFile, self.kaercher.particleSystems, psName, self.components, false, nil, self.baseDirectory);
	Utils.setEmittingState(self.kaercher.particleSystems, false);
	
	local compressorSound = getXMLString(xmlFile, "vehicle.sounds#compressor");	
	if compressorSound ~= nil and compressorSound ~= "" then
		compressorSound = Utils.getFilename(compressorSound, self.baseDirectory); 
		self.compressorSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.sounds#volume"), 1.0);
        self.compressorSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.sounds#radius"), 50);
        self.compressorSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.sounds#innerRadius"), 10);
        self.compressorSound = createAudioSource("compressorSound", compressorSound, self.compressorSoundRadius, self.compressorSoundInnerRadius, self.compressorSoundVolume, 0);
        link(self.components[1].node, self.compressorSound);
        setVisibility(self.compressorSound, false);
	end;
	
	local washerSound = getXMLString(xmlFile, "vehicle.sounds#washer");
    if washerSound ~= nil and washerSound ~= "" then
        washerSound = Utils.getFilename(washerSound, self.baseDirectory); 
        self.washerSound = createSample("washerSound");
        self.washerSoundEnabled = false;
        loadSample(self.washerSound, washerSound, false);
        self.washerSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.sounds#washerPitchOffset"), 1);
        self.washerSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.sounds#washerVolume"), 1);
    end;
	
	self.oldToBeWashed = {};
	self.toBeWashed = {};
	self.isPlayerInRange = false;
	self.washablesInRange = {};
	self.controllingPlayer = nil;
	self.isLanceActive = false;
	self.lastInRangePosition = {0,0,0};
	self.playerIdBackup = nil;
	self.hasPlayerQuit = false;
end;

function PressureWasher:delete()
	self:setActiveState(nil, true);	
	if self.kaercher.particleSystems ~= nil then
		Utils.deleteParticleSystem(self.kaercher.particleSystems);
	end;
    if self.washerSound ~= nil then
        delete(self.washerSound);
    end;
end;

function PressureWasher:readStream(streamId, connection)
	local id = streamReadInt32(streamId);
	if id ~= -1 then
		self.playerIdBackup = id;
	end;	
end;

function PressureWasher:writeStream(streamId, connection)
	local idToWrite = -1;
	if self.controllingPlayer ~= nil then
		idToWrite = networkGetObjectId(self.controllingPlayer);
	end;
	streamWriteInt32(streamId, idToWrite);	
end;

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

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

function PressureWasher:update(dt)

	if g_gui.currentGui ~= nil and self.playerIdBackup ~= nil then	
		self:setActiveState(networkGetObject(self.playerIdBackup), true);
		self.playerIdBackup = nil;		
	end;
	
	if g_currentMission.lastHandltoolId ~= nil and g_currentMission.lastHandltoolId == self.rootNode then
		g_currentMission.lastHandltoolId = nil;
	end;

	if self.controllingPlayer == g_currentMission.player or (self.controllingPlayer == nil and self.isPlayerInRange) then
		if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA) then
			if self.controllingPlayer == g_currentMission.player then
				self:setActiveState(nil);
			else
				if g_currentMission.player.hasHandTool == nil or g_currentMission.player.hasHandTool == false then
					if g_currentMission.lastHandltoolId == nil then
						self:setActiveState(g_currentMission.player);
					end;
				end;
			end;
		end;	
		
		if self.controllingPlayer ~= nil and self.controllingPlayer == g_currentMission.player then
			g_currentMission:addHelpButtonText(string.format(g_i18n:getText("turn_off_OBJECT"), self.typeDesc), InputBinding.IMPLEMENT_EXTRA);
			g_currentMission:addExtraPrintText(g_i18n:getText("pressure_washer_spray"));
			if Input.isMouseButtonPressed(Input.MOUSE_BUTTON_LEFT) and g_gui.currentGui == nil then
				if not self.isLanceActive then
					self:setLanceActive(true);
				end;
			else
				if self.isLanceActive then
					self:setLanceActive(false);
				end;
			end;
		elseif self.controllingPlayer == nil and self.isPlayerInRange then
			g_currentMission:addHelpButtonText(string.format(g_i18n:getText("turn_on_OBJECT"), self.typeDesc), InputBinding.IMPLEMENT_EXTRA);	
		end;
	else
		if self.controllingPlayer ~= nil then	
			if g_currentMission:getIsServer() then
				if g_currentMission.connectionsToPlayer[self.controllingPlayer.creatorConnection] == nil then
					self.hasPlayerQuit = true;
					self:setActiveState(nil);
				end;
			end;
		end;
	end;	
	if self.controllingPlayer ~= nil then
		-- if player joins a vehicle disable washer
		if not self.controllingPlayer.isControlled and not self.controllingPlayer.isEntered then
			self:setActiveState(nil);
		end;
	end;
end;

function PressureWasher:updateTick(dt)

	if self.controllingPlayer == nil then
		if g_currentMission.currentVehicle == nil then
			self.isPlayerInRange = PressureWasher.getPlayerInRange(self, g_currentMission.player);
		end;
		if self.washerSoundEnabled then
			stopSample(self.washerSound);
			self.washerSoundEnabled = false;
		end;
	else
		local isPlayerInRange = PressureWasher.getPlayerInRange(self, self.controllingPlayer);
		if isPlayerInRange then
			self.lastInRangePosition = {getTranslation(self.controllingPlayer.rootNode)};
		else
			local kx, _, kz = getWorldTranslation(self.rootNode);
			local px, _, pz = getWorldTranslation(self.controllingPlayer.rootNode);			
			local len = Utils.vector2Length(px-kx, pz-kz);			
			local x,y,z = unpack(self.lastInRangePosition);
			x = kx + ((px-kx) / len) * self.kaercher.actionDistance;
			z = kz + ((pz-kz) / len) * self.kaercher.actionDistance;
			self.controllingPlayer:moveToAbsoluteInternal(x, y, z);
			self.lastInRangePosition = {x,y,z};
		end;
		if self.isLanceActive then			
			if self.controllingPlayer == g_currentMission.player then
				self.toBeWashed = {};
				if not self.washerSoundEnabled then
					setSamplePitch(self.washerSound, self.washerSoundPitchOffset);
					playSample(self.washerSound, 0, self.washerSoundVolume, 0);
					self.washerSoundEnabled = true;
				end;
				self:getWashableInRange();
				if table.getn(self.washablesInRange) > 0 then
					for _, washable in pairs(self.washablesInRange) do
						for _, component in pairs(washable.components) do
							local x,y,z = getWorldTranslation(component.node);
							x,y,z = project(x,y,z);
							if x >= 0 and x <= 1 then
								if y >= 0 and y <= 1 then
									if z <= 1 then
										table.insert(self.toBeWashed, washable);	
										break;
									end;
								end;
							end;
						end;
					end;
					
				end;
			end;
		else
			if self.controllingPlayer == g_currentMission.player then
				self.toBeWashed = {};
			end;
			if self.washerSoundEnabled then
				stopSample(self.washerSound);
				self.washerSoundEnabled = false;
			end;
		end;
		if table.getn(self.oldToBeWashed) ~= table.getn(self.toBeWashed) then
			self:washVehicles(self.toBeWashed);
		end;
		self.oldToBeWashed = self.toBeWashed;	
	end;	
	for _, washable in pairs(self.toBeWashed) do
		washable:decreaseDirt(dt);
	end;	
end;

function PressureWasher:draw()	
end;

function PressureWasher:setActiveState(player, noEventSend)	
	PressureWasherEnableEvent.sendEvent(self, player, noEventSend);
	
	if player ~= nil then	
		player.hasHandTool = true;
		link(player.cameraId, self.kaercher.lance);
		setVisibility(self.kaercher.pipe, false);
		setVisibility(self.compressorSound, true);
	else
		g_currentMission.lastHandltoolId = self.rootNode;
		
		if self.controllingPlayer ~= nil then		
			self.controllingPlayer.hasHandTool = false;
		end;
		link(self.kaercher.pressureWasher, self.kaercher.lance);
		setVisibility(self.kaercher.lance, true);
		setVisibility(self.kaercher.pipe, true);
		setVisibility(self.compressorSound, false);
		Utils.setEmittingState(self.kaercher.particleSystems, false);
	end;
	self.controllingPlayer = player;
	self.hasPlayerQuit = false;
end;

function PressureWasher:setLanceActive(isActive, noEventSend)
	PressureWasherLanceEvent.sendEvent(self, isActive, noEventSend);
	Utils.setEmittingState(self.kaercher.particleSystems, isActive);
	self.isLanceActive = isActive;
end;

function PressureWasher:washVehicles(washables, noEventSend)
	PressureWasherEvent.sendEvent(self, washables, noEventSend);
	self.toBeWashed = washables;	
end;

function PressureWasher:getPlayerInRange(player)
	local isPlayerInRange = false; 
	if player ~= nil then
		local px, py, pz = getWorldTranslation(self.rootNode);
		local vx, vy, vz = getWorldTranslation(player.rootNode);
		local distance = Utils.vector2Length(px-vx, pz-vz);
		if distance < self.kaercher.actionDistance then		
			isPlayerInRange = true;
		end; 
	end;
	
	return isPlayerInRange;
end;

function PressureWasher:getWashableInRange()
	self.washablesInRange = {}; 
	local nearestDistance = self.kaercher.washDistance;
	local px, py, pz = getWorldTranslation(g_currentMission.player.rootNode);
	for k,v in pairs(g_currentMission.vehicles) do
		if v.decreaseDirt ~= nil then
			local vx, vy, vz = getWorldTranslation(v.rootNode);
			local distance = Utils.vector2Length(px-vx, pz-vz);
			if distance < nearestDistance then
				table.insert(self.washablesInRange, v);
			end; 
		end;
    end;
end;


PressureWasherEnableEvent = {};
PressureWasherEnableEvent_mt = Class(PressureWasherEnableEvent, Event);

InitEventClass(PressureWasherEnableEvent, "PressureWasherEnableEvent");

function PressureWasherEnableEvent:emptyNew()
    local self = Event:new(PressureWasherEnableEvent_mt);
    return self;
end;

function PressureWasherEnableEvent:new(vehicle, player)
    local self = PressureWasherEnableEvent:emptyNew()
    self.vehicle = vehicle;
	self.player = player;
    return self;
end;

function PressureWasherEnableEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	local playerId = streamReadInt32(streamId);
	self.player = networkGetObject(playerId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function PressureWasherEnableEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteInt32(streamId, networkGetObjectId(self.player));	
end;

function PressureWasherEnableEvent:run(connection)
	self.vehicle:setActiveState(self.player, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(PressureWasherEnableEvent:new(self.vehicle, self.player), nil, connection, self.vehicle);
	end;
end;

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



PressureWasherEvent = {};
PressureWasherEvent_mt = Class(PressureWasherEvent, Event);

InitEventClass(PressureWasherEvent, "PressureWasherEvent");

function PressureWasherEvent:emptyNew()
    local self = Event:new(PressureWasherEvent_mt);
    return self;
end;

function PressureWasherEvent:new(vehicle, washables)
    local self = PressureWasherEvent:emptyNew()
    self.vehicle = vehicle;
	self.washables = washables;
    return self;
end;

function PressureWasherEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	local count = streamReadInt8(streamId);
	self.washables = {};
	for i=1, count do
		local washableId = streamReadInt32(streamId);
		table.insert(self.washables, networkGetObject(washableId));
	end;	
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function PressureWasherEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));	
	streamWriteInt8(streamId, table.getn(self.washables));
	for _,washable in pairs(self.washables) do
		streamWriteInt32(streamId, networkGetObjectId(washable));
	end;	
end;

function PressureWasherEvent:run(connection)
	self.vehicle:washVehicles(self.washables, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(PressureWasherEvent:new(self.vehicle, self.washables), nil, connection, self.object);
	end;
end;

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



PressureWasherLanceEvent = {};
PressureWasherLanceEvent_mt = Class(PressureWasherLanceEvent, Event);

InitEventClass(PressureWasherLanceEvent, "PressureWasherLanceEvent");

function PressureWasherLanceEvent:emptyNew()
    local self = Event:new(PressureWasherLanceEvent_mt);
    return self;
end;

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

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

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

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

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