--
--	passenger
--
--	@author: 	 patar, rafftnix & Wopster
--	@history:	 v1.0 - patar - unknown 
--				 v1.1 - rafftnix - 2013-09-29 
--				 v1.2 - Wopster - 2016-03-27 - Mature refactoring
--

passenger = {
	DEBUG = false
}

function passenger.prerequisitesPresent(specializations)
    return true
end

function passenger:load(xmlFile)
	self.enterAsPassenger = SpecializationUtil.callSpecializationsFunction('enterAsPassenger')
	self.leaveAsPassenger = SpecializationUtil.callSpecializationsFunction('leaveAsPassenger')
	self.removePassenger = SpecializationUtil.callSpecializationsFunction('removePassenger')
	self.getPlayerInRange = passenger.getPlayerInRange
	self.getFreePlace = passenger.getFreePlace
	self.getOwnPlace = passenger.getOwnPlace

	self.places = {}
	local i = 0
	
	while true do
		local key = string.format('vehicle.passenger.place(%d)', i)
		if not hasXMLProperty(xmlFile, key) then
			break
		end

		local camera = VehicleCamera:new(self)
		camera:loadFromXML(xmlFile, string.format('%s.camera', key))

		if camera ~= nil then
			local cameraRot = {getRotation(camera.cameraNode)}
			local entry = {
				camera = camera,
				passengerNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. '#passengerIndex')),
				nickNameRenderNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. '#passengerNickNameRenderNode')),
				exitNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. '#exitPointIndex')),
				isUsed = false,
				cameraOrigX = cameraRot[1],
				cameraOrigY = cameraRot[2],
				cameraOrigZ = cameraRot[3]
			}

			if entry.passengerNode ~= nil then
				setVisibility(entry.passengerNode, false)
			else
				print(string.format('Passengermod - error: invalid passenger index on place %s', i))
			end

			if entry.exitNode == nil then
				entry.exitNode = self.exitPoint

				if entry.exitNode == nil then
					print('Passenger - error: vehicle requires an exit point!')
					break
				end
			end

			table.insert(self.places, entry)
		else
			print(string.format('Passengermod - error: cannot load VehicleCamera on place %s', i))
			break
		end

		i = i + 1
	end
end

function passenger:readStream(streamId, connection)
	for i = 1, table.getn(self.places) do
		local isUsed = streamReadBool(streamId)

		if isUsed then
			self:enterAsPassenger(networkGetObject(streamReadInt32(streamId)), i, true)
		end
	end
end

function passenger:writeStream(streamId, connection)
	for i = 1, table.getn(self.places) do
		streamWriteBool(streamId, self.places[i].isUsed)

		if self.places[i].isUsed then
			streamWriteInt32(streamId, networkGetObjectId(self.places[i].sittingPlayer))
		end
	end
end

function passenger:delete()
	local ownPlaceIndex = self:getOwnPlace()

	if ownPlaceIndex ~= nil then
		self:removePassenger(ownPlaceIndex)
	end
end

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

function passenger:keyEvent(unicode, sym, modifier, isDown)
end

function passenger:update(dt)
	if g_currentMission.controlPlayer and g_currentMission.player ~= nil then
		if g_gui.currentGui == nil and not g_currentMission.isPlayerFrozen then
			if self:getPlayerInRange() and self:getOwnPlace() == nil then
				g_currentMission:addHelpButtonText(g_i18n:getText('ENTER_AS_PASSENGER'), InputBinding.CAMERA_SWITCH)

				if InputBinding.hasEvent(InputBinding.CAMERA_SWITCH) then
					self:enterAsPassenger(g_currentMission.player, self:getFreePlace())
				end
			elseif self:getOwnPlace() ~= nil then
				g_currentMission:addHelpButtonText(g_i18n:getText('EXIT_AS_PASSENGER'), InputBinding.CAMERA_SWITCH)

				if InputBinding.hasEvent(InputBinding.CAMERA_SWITCH) then
					self:leaveAsPassenger(g_currentMission.player, self:getOwnPlace())
				end
			end
		end
	end

	if self:getOwnPlace() ~= nil then
		local ownPlaceIndex = self:getOwnPlace()

		if self.places[ownPlaceIndex] ~= nil then
			self.places[ownPlaceIndex].camera:update(dt)
		end
	end

	if self.places ~= nil then
		for i = 1, table.getn(self.places) do
			local player = self.places[i].sittingPlayer

			if player ~= nil then
				if self.places[i].isUsed and g_gui.currentGui == nil then
					local renderTrans = {getWorldTranslation(self.places[i].nickNameRenderNode)}
					local cameraTrans = {getWorldTranslation(getCamera())}
					local distance = Utils.vector3Length(renderTrans[1] - cameraTrans[1], renderTrans[2] - cameraTrans[2], renderTrans[3] - cameraTrans[3])

					if distance <= 50 then
						local x, y, z = project(unpack(renderTrans))

						if z <= 1 then
							setTextBold(false)
							setTextAlignment(RenderText.ALIGN_CENTER)
							setTextColor(0.5, 1.0, 0.5, 1.0)
							renderText(x, y, 0.02, player.controllerNameBackup)
							setTextAlignment(RenderText.ALIGN_LEFT)
						end
					end
				end
			end
		end
	end
end

function passenger:updateTick(dt)
	if self.places ~= nil then
		for i = 1, table.getn(self.places) do
			if self.places[i].isUsed and self.places[i].sittingPlayer ~= nil then
				if not self.places[i].sittingPlayer.deleteDonePassengerSkript then
					local exitTrans = {getWorldTranslation(self.places[i].exitNode)}
					local player = self.places[i].sittingPlayer

					if player ~= nil then
						player.walkDistance = 0
						player.currentWalkStep = 0
						setTranslation(player.rootNode, exitTrans[1], exitTrans[2] + 1.75, exitTrans[3])
					end
				elseif self.isServer then
					self:removePassenger(i)
				end
			end
		end
	end
end

function passenger:draw()
end

function passenger:onEnter(isControlling)
	local ownPlaceIndex = self:getOwnPlace()

	if isControlling and ownPlaceIndex ~= nil then
		self:removePassenger(ownPlaceIndex)
	end
end

function passenger:onLeave()
end

function passenger:getPlayerInRange()
	if g_currentMission.player ~= nil then
		local placeIndex = self:getFreePlace()

		if placeIndex ~= nil then
			if self.places[placeIndex] ~= nil then
				local placeTrans = {getWorldTranslation(self.places[placeIndex].exitNode)}
				local playerTrans = {getWorldTranslation(g_currentMission.player.rootNode)}
				local distance = Utils.vector3Length(placeTrans[1] - playerTrans[1], placeTrans[2] - playerTrans[2], placeTrans[3] - playerTrans[3])

				if distance < 2 then
					return true
				end
			end
		end
	end
	
	return false
end

function passenger:getFreePlace()
	if self.places ~= nil then
		for i = 1, table.getn(self.places) do
			if not self.places[i].isUsed then
				return i
			end
		end
	end

	return nil
end

function passenger:getOwnPlace()
	if g_currentMission.passengerHoldingVehicle == self then
		return g_currentMission.passengerPlace
	end
end

function passenger:enterAsPassenger(passenger, placeIndex, noEventSend)
	if table.getn(g_currentMission.playersLoading) == 0 then
		if passenger ~= nil and placeIndex ~= nil then
			enterAsPassengerEvent.sendEvent(self, passenger, placeIndex, noEventSend)

			local place = self.places[placeIndex]

			if place ~= nil then
				if passenger.tools ~= nil then
					if table.getn(passenger.tools) > 0 then
						passenger.backupTools = passenger.tools

						if passenger.currentTool ~= nil then
							passenger.setTool(passenger, nil)
						end

						-- Set empty table because default functions aren't checked on nil values...
						passenger.tools = {}
					end
				end

				place.isUsed = true
				place.sittingPlayer = passenger

				passenger.controllerNameBackup = passenger.controllerName
				passenger.controllerName = ''

				if passenger == g_currentMission.player then
					place.camera:onActivate()
					g_currentMission.passengerPlace = placeIndex
					g_currentMission.passengerHoldingVehicle = self

					if not g_currentMission.passengerHoldingVehicle.isEntered then
						if g_currentMission.passengerHoldingVehicle.mirrorAvailable and place.camera.useMirror then -- Show only on high profile setting
							g_currentMission.passengerHoldingVehicle:setMirrorVisible(true)
						end
					end

					g_currentMission.hasSpecialCamera = true
				else
					if place.passengerNode ~= nil then
						setVisibility(place.passengerNode, true)
					end

					setVisibility(passenger.meshThirdPerson, false)
				end
			end
		end
	end
end

function passenger:leaveAsPassenger(passenger, placeIndex, noEventSend)
	if passenger ~= nil and placeIndex ~= nil then
		leaveAsPassengerEvent.sendEvent(self, passenger, placeIndex, noEventSend)

		local place = self.places[placeIndex]

		if place ~= nil then
			if passenger.backupTools ~= nil then
				passenger.tools = passenger.backupTools
				passenger.backupTools = nil
			end

			place.isUsed = false
			place.sittingPlayer = nil

			if place.passengerNode ~= nil then
				setVisibility(place.passengerNode, false)
			end

			passenger.controllerName = passenger.controllerNameBackup

			if passenger.lightNode ~= 0 then
				setVisibility(passenger.lightNode, false)
			end

			if passenger == g_currentMission.player then
				g_currentMission.passengerPlace = nil
				place.camera:onDeactivate()
				g_currentMission.hasSpecialCamera = false

				if not g_currentMission.passengerHoldingVehicle.isEntered then
					if g_currentMission.passengerHoldingVehicle.mirrorAvailable and place.camera.useMirror then -- Show only on high profile setting
						g_currentMission.passengerHoldingVehicle:setMirrorVisible(false)
					end
				end

				g_currentMission.passengerHoldingVehicle = nil
			else
				setVisibility(passenger.meshThirdPerson, true)
			end
		end
	end
end

function passenger:removePassenger(placeIndex, noEventSend)
	if placeIndex ~= nil then
		removePassengerEvent.sendEvent(self, placeIndex, noEventSend)

		local place = self.places[placeIndex]

		if place ~= nil then
			local passenger = place.sittingPlayer

			if passenger ~= nil then
				if passenger.backupTools ~= nil then
					passenger.tools = passenger.backupTools
					passenger.backupTools = nil
				end

				if passenger == g_currentMission.player then
					g_currentMission.passengerPlace = nil

					if g_currentMission.passengerHoldingVehicle.isEntered then
						if g_currentMission.passengerHoldingVehicle.mirrorAvailable and not g_currentMission.passengerHoldingVehicle.cameras[g_currentMission.passengerHoldingVehicle.camIndex].useMirror then
							if place.camera.useMirror then -- Show only on high profile setting
								g_currentMission.passengerHoldingVehicle:setMirrorVisible(false)
							end
						end
					end

					g_currentMission.passengerHoldingVehicle = nil
				end

				if passenger ~= nil and not passenger.deleteDonePassengerSkript then
					passenger.setVisibility(passenger, true)
				end

				passenger.controllerName = passenger.controllerNameBackup
			end

			place.isUsed = false
			place.sittingPlayer = nil

			if place.passengerNode ~= nil then
				setVisibility(place.passengerNode, false)
			end
		end
	end
end

local oldPlayerDelete = Player.delete
Player.delete = function(self)
	self.deleteDonePassengerSkript = true
	oldPlayerDelete(self)
end

--
--	enterAsPassengerEvent
--
--	@author: 	 ? & Wopster
--	@history:	 v1.2 - Wopster - 2016-03-27 - Mature refactoring
--

enterAsPassengerEvent = {}
enterAsPassengerEvent_mt = Class(enterAsPassengerEvent, Event)
InitEventClass(enterAsPassengerEvent, 'enterAsPassengerEvent')

function enterAsPassengerEvent:emptyNew()
    local self = Event:new(enterAsPassengerEvent_mt)
    self.className = 'enterAsPassengerEventEvent'

    return self
end

function enterAsPassengerEvent:new(object, passenger, placeIndex)
    local self = enterAsPassengerEvent:emptyNew()
    self.object = object
	self.passenger = passenger
	self.placeIndex = placeIndex

    return self
end

function enterAsPassengerEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object))
	streamWriteInt32(streamId, networkGetObjectId(self.passenger))
	streamWriteInt8(streamId, self.placeIndex)
end

function enterAsPassengerEvent:readStream(streamId, connection)
	self.object = networkGetObject(streamReadInt32(streamId))
	self.passenger = networkGetObject(streamReadInt32(streamId))
	self.placeIndex = streamReadInt8(streamId)
    self:run(connection)
end

function enterAsPassengerEvent:run(connection)
    self.object:enterAsPassenger(self.passenger, self.placeIndex, true)

	if not connection:getIsServer() then
        g_server:broadcastEvent(enterAsPassengerEvent:new(self.object, self.passenger, self.placeIndex), nil, connection, self.object)
    end
end

function enterAsPassengerEvent.sendEvent(vehicle, passenger, placeIndex, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(enterAsPassengerEvent:new(vehicle, passenger, placeIndex), nil, nil, vehicle)
		else
			g_client:getServerConnection():sendEvent(enterAsPassengerEvent:new(vehicle, passenger, placeIndex))
		end
	end
end

--
--	leaveAsPassengerEvent
--
--	@author: 	 ? & Wopster
--	@history:	 v1.2 - Wopster - 2016-03-27 - Mature refactoring
--

leaveAsPassengerEvent = {}
leaveAsPassengerEvent_mt = Class(leaveAsPassengerEvent, Event)
InitEventClass(leaveAsPassengerEvent, 'leaveAsPassengerEvent')

function leaveAsPassengerEvent:emptyNew()
    local self = Event:new(leaveAsPassengerEvent_mt)
    self.className = 'leaveAsPassengerEvent'

	return self
end

function leaveAsPassengerEvent:new(object, passenger, placeIndex)
    local self = leaveAsPassengerEvent:emptyNew()
    self.object = object
	self.passenger = passenger
	self.placeIndex = placeIndex

    return self
end

function leaveAsPassengerEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object))
	streamWriteInt32(streamId, networkGetObjectId(self.passenger))
	streamWriteInt8(streamId, self.placeIndex)
end

function leaveAsPassengerEvent:readStream(streamId, connection)
	self.object = networkGetObject(streamReadInt32(streamId))
	self.passenger = networkGetObject(streamReadInt32(streamId))
	self.placeIndex = streamReadInt8(streamId)
    self:run(connection)
end

function leaveAsPassengerEvent:run(connection)
    self.object:leaveAsPassenger(self.passenger, self.placeIndex, true)

	if not connection:getIsServer() then
        g_server:broadcastEvent(leaveAsPassengerEvent:new(self.object, self.passenger, self.placeIndex), nil, connection, self.object)
    end
end

function leaveAsPassengerEvent.sendEvent(vehicle, passenger, placeIndex, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(leaveAsPassengerEvent:new(vehicle, passenger, placeIndex), nil, nil, vehicle)
		else
			g_client:getServerConnection():sendEvent(leaveAsPassengerEvent:new(vehicle, passenger, placeIndex))
		end
	end
end

--
--	removePassengerEvent
--
--	@author: 	 ? & Wopster
--	@history:	 v1.2 - Wopster - 2016-03-27 - Mature refactoring
--

removePassengerEvent = {}
removePassengerEvent_mt = Class(removePassengerEvent, Event)
InitEventClass(removePassengerEvent, 'removePassengerEvent')

function removePassengerEvent:emptyNew()
    local self = Event:new(removePassengerEvent_mt)
    self.className = 'removePassengerEvent'

    return self
end

function removePassengerEvent:new(object, placeIndex)
    local self = removePassengerEvent:emptyNew()
    self.object = object
	self.placeIndex = placeIndex

    return self
end

function removePassengerEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object))
	streamWriteInt8(streamId, self.placeIndex)
end

function removePassengerEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId))
	self.placeIndex = streamReadInt8(streamId)
	self:run(connection)
end

function removePassengerEvent:run(connection)
    self.object:removePassenger(self.placeIndex, true)

	if not connection:getIsServer() then
        g_server:broadcastEvent(removePassengerEvent:new(self.object, self.placeIndex), nil, connection, self.object)
    end
end

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

--
--	passengerEventListener
--
--	@author: 	 ? & Wopster
--	@history:	 v1.2 - Wopster - 2016-03-27 - Mature refactoring
--

passengerEventListener = {}

function passengerEventListener:loadMap(name)
end

function passengerEventListener:deleteMap()
end

function passengerEventListener:keyEvent(unicode, sym, modifier, isDown)
end

function passengerEventListener:mouseEvent(posX, posY, isDown, isUp, button)
	local vehicle = g_currentMission.passengerHoldingVehicle

	if vehicle ~= nil and vehicle.getOwnPlace ~= nil then
		local ownPlaceIndex = vehicle:getOwnPlace()

		if ownPlaceIndex ~= nil then
			if vehicle.places[ownPlaceIndex] ~= nil then
				vehicle.places[ownPlaceIndex].camera:mouseEvent(posX, posY, isDown, isUp, button)
			end
		end
	end
end

function passengerEventListener:update(dt)
end

function passengerEventListener:draw()
end

addModEventListener(passengerEventListener)