-- by modelleicher
-- www.schwabenmodding.bplaced.net
-- Date: November 2015
-- 3 main features
-- 1. roll/pitch feature (useWorldXZRotation for indoorCamera)
-- 2. shake feature, camera shakes according to vehicle movement (to simulate the inertia of mass of you and your head inside a vehicle)
-- 3. TL/TR, ability to look 40° either side or 120° back either side with 2 buttons for example on a steering wheel without touching the mouse (mainly for wheel users)

betterIndoorCamera = {};

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

function betterIndoorCamera:load(xmlFile)
	self.getSmoothed2 = betterIndoorCamera.getSmoothed2; -- update the getSmoothed function from VehicleCamera.lua
	self.changeBICState = betterIndoorCamera.changeBICState; -- change the state of roll/pitch or shake on/off
	self.setRotYState = betterIndoorCamera.setRotYState; -- control if and in what direction the camera should rotate
	self.calcSpeedIncr = betterIndoorCamera.calcSpeedIncr; -- math.. hopefully it works.
		
	if self.bic == nil then -- in case preLoad didn't actually run.. for whatever reason.
		self.bic = {};
	end;
		
	-- alpha values to begin with
	self.bic.alphaX = 0.01;
	self.bic.alphaY = 0.01;
	self.bic.alphaZ = 0.01;	
	
	self.bic.isOn = true;
	self.bic.isWorldXZOn = false;
	
	-- increase per km/h not realistic but needed for better playability
	self.bic.alphaKmhBalanceInc = 0.002; -- basically we want to go from 0.01 at 0km/h to 0.16 at 80km/h so 0.002*80
	self.bic.alphaKmhBalanceInc4 = 0.0003; -- lower value is used for Y value 0.0003
			
	-- add the custom alpha values to each inside camera
	for i=1, self.numCameras do
		if self.cameras[i].isInside then -- found inside camera
			self.cameras[i].usebic = true;
			self.cameras[i].bic = {};
			self.cameras[i].bic.alphaX = 1-math.pow(1-self.bic.alphaX, self.cameras[i].positionSmoothingResolution);
			self.cameras[i].bic.alphaY = 1-math.pow(1-self.bic.alphaY, self.cameras[i].positionSmoothingResolution);
			self.cameras[i].bic.alphaZ = 1-math.pow(1-self.bic.alphaZ, self.cameras[i].positionSmoothingResolution);
		end;
	end;
	
	-- camera rotation by button (for steering wheel users) (TL/TR)
	self.bic.oldRotY = 0;
	self.bic.curRotY = 0;
	self.bic.rotSpeed = 0.1; -- 140 speedIncrease, if we want max speed of not more than 0.4 we need to multiply it by roughly 0.002
	self.bic.rotYState = 2; -- 2 = centered; 1 = left; 0 = left back, 3 = right; 4 = right back
	self.bic.timer = 0;
	self.bic.timerTime = 1500;
	self.bic.doMove = false;
	self.bic.lastSpeedIncrValue = 0;
end;
function betterIndoorCamera:preLoad(xmlFile)
	self.bic = {}; -- table we store everything related to betterIndoorCamera in
	local cameraCount = getXMLInt(xmlFile, "vehicle.cameras#count");
	for i=1, cameraCount do
		local isInside = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.cameras.camera"..i.."#isInside"), false);
		if isInside then 
			local check = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.cameras.camera"..i.."#useDefaultPositionSmoothing"), nil);
			if check == nil then
				setXMLBool(xmlFile, "vehicle.cameras.camera"..i.."#useDefaultPositionSmoothing", false);
				setXMLFloat(xmlFile, "vehicle.cameras.camera"..i.."#positionSmoothingParameter", 0.55);
				setXMLFloat(xmlFile, "vehicle.cameras.camera"..i.."#lookAtSmoothingParameter", 0.1);
			end;
			if self.bic.isWorldXZOn ~= nil then
				setXMLBool(xmlFile, "vehicle.cameras.camera"..i.."#useWorldXZRotation", self.bic.isWorldXZOn);
			end;			
		end;
	end;
	
end;
function betterIndoorCamera:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	if not resetVehicles then
		local state1 = Utils.getNoNil(getXMLString(xmlFile, key.."#BetterIndoorBicOnOff"), self.bic.isOn);
		local state2 = Utils.getNoNil(getXMLString(xmlFile, key.."#BetterIndoorWorldXZOnOff"), self.bic.isWorldXZOn);
		self:changeBICState(state1, state2);
	end;
	return BaseMission.VEHICLE_LOAD_OK;
end;

function betterIndoorCamera:getSaveAttributesAndNodes(nodeIdent)
	local str1 = ' BetterIndoorBicOnOff="'..tostring(self.bic.isOn)..'"'
	local str2 = ' BetterIndoorWorldXZOnOff="'..tostring(self.bic.isWorldXZOn)..'"'
	
	local attributes = str1..str2;
	local nodes = nil;
	return attributes,nodes;
end;
-- shake/worldXZ
function betterIndoorCamera:changeBICState(bicOnOff, worldXZOnOff)
	if not bicOnOff then
		for i=1, self.numCameras do
			if self.cameras[i].isInside then
				self.cameras[i].bic.alphaX = 1-math.pow(1-1, self.cameras[i].positionSmoothingResolution);
				self.cameras[i].bic.alphaY = 1-math.pow(1-1, self.cameras[i].positionSmoothingResolution);
				self.cameras[i].bic.alphaZ = 1-math.pow(1-1, self.cameras[i].positionSmoothingResolution);
			end;
		end;
	end;
    if worldXZOnOff ~= nil then
		for i=1, self.numCameras do
			if self.cameras[i].isInside then
				self.cameras[i].useWorldXZRotation = worldXZOnOff;
			end;
		end;
	end;	
	self.bic.isOn = Utils.getNoNil(bicOnOff, self.bic.isOn);
	self.bic.isWorldXZOn = Utils.getNoNil(worldXZOnOff, self.bic.isWorldXZOn)	
end;

-- tl/tr
function betterIndoorCamera:setRotYState(newState) 	
	if newState ~= self.bic.rotYState then
		self.bic.rotYState = newState;
		self.bic.doMove = true;
	end;
end;	
-- tl/tr
-- speed = math.min(math.max(currentValue, startValue) - math.min(currentValue, startValue), math.max(currentValue, endValue) - math.min(currentValue, endValue));
function betterIndoorCamera:calcSpeedIncr(curVal, startVal, endVal, maxVal)
	self.bic.lastSpeedIncrValue = maxVal;
	local speedInc = 0;
	startVal = math.rad(startVal);
	endVal = math.rad(endVal);
	maxVal = math.rad(maxVal);
	speedInc = math.min(math.max(curVal, startVal) - math.min(curVal, startVal), math.max(curVal, endVal) - math.min(curVal, endVal));
	speedInc = speedInc / maxVal;
	speedInc = speedInc / 10 + 0.01;
	return speedInc;
end;
function betterIndoorCamera:update(dt)
	
	if self:getIsActiveForInput() then
		-- BIC Shake and worldXZ
		if InputBinding.hasEvent(InputBinding.betterIndoorCamera_ONOFF_B) then
			self:changeBICState(not self.bic.isOn, nil);
		end;
		if InputBinding.hasEvent(InputBinding.betterIndoorCamera_WORLDXZONOFF_B) then
			self:changeBICState(nil, not self.bic.isWorldXZOn);	
		end;
		-- TL/TR 
		if InputBinding.isPressed(InputBinding.betterIndoorCamera_TL1) then
			local state = 1;
			self.bic.timer = self.bic.timer + dt;
			if self.bic.timer > self.bic.timerTime then -- 
				state = 0;
			end;
			self:setRotYState(state);
		elseif InputBinding.isPressed(InputBinding.betterIndoorCamera_TR1) then
			local state = 3;
			self.bic.timer = self.bic.timer + dt;
			if self.bic.timer > self.bic.timerTime then -- 
				state = 4;
			end;	
			self:setRotYState(state);
		else
			self:setRotYState(2);
			self.bic.timer = 0;
		end;
		--
	end;
	

	if self:getIsActive() and self.isMotorStarted then
		-- TL/TR 
		if self.cameras[self.camIndex].isInside then
			if self.bic.rotYState ~= 2 and self.bic.doMove then
				self.bic.oldRotY = self.cameras[self.camIndex].rotY;
				if self.bic.rotYState == 3 then -- turn right
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, 180, 140, 40);
					self.cameras[self.camIndex].rotY = math.max(self.cameras[self.camIndex].rotY - inc, math.rad(140));
					if self.cameras[self.camIndex].rotY == math.rad(140) then
						self.bic.doMove = false;
					end;
				elseif self.bic.rotYState == 4 then -- turn right back
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, 140, 20, 120);
					self.cameras[self.camIndex].rotY = math.max(self.cameras[self.camIndex].rotY - inc, math.rad(20));
					if self.cameras[self.camIndex].rotY == math.rad(20) then
						self.bic.doMove = false;
					end;
				elseif self.bic.rotYState == 1 then -- turn left
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, 180, 220, 40);
					self.cameras[self.camIndex].rotY = math.min(self.cameras[self.camIndex].rotY + inc, math.rad(220));
					if self.cameras[self.camIndex].rotY == math.rad(220) then
						self.bic.doMove = false;
					end;
				elseif self.bic.rotYState == 0 then -- turn left back
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, 220, 340, 120);
					self.cameras[self.camIndex].rotY = math.min(self.cameras[self.camIndex].rotY + inc, math.rad(340));
					if self.cameras[self.camIndex].rotY == math.rad(340) then
						self.bic.doMove = false;
					end;
				end;
			elseif self.bic.rotYState == 2 and self.bic.doMove then
				if self.cameras[self.camIndex].rotY > math.rad(180) then
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, self.bic.lastSpeedIncrValue, 180, math.max(self.bic.lastSpeedIncrValue, 180) - math.min(self.bic.lastSpeedIncrValue, 180));
					self.cameras[self.camIndex].rotY = math.max(self.cameras[self.camIndex].rotY - inc, math.rad(180));
					if self.cameras[self.camIndex].rotY == math.rad(180) then
						self.bic.doMove = false;
					end;
				elseif self.cameras[self.camIndex].rotY < math.rad(180) then
					local inc = self:calcSpeedIncr(self.cameras[self.camIndex].rotY, self.bic.lastSpeedIncrValue, 180, math.max(self.bic.lastSpeedIncrValue, 180) - math.min(self.bic.lastSpeedIncrValue, 180));					
					self.cameras[self.camIndex].rotY = math.min(self.cameras[self.camIndex].rotY + inc, math.rad(180));
					if self.cameras[self.camIndex].rotY == math.rad(180) then
						self.bic.doMove = false;
					end;
				end;
			end;
		end;
		-- bic shake 
		-- account for the current speed (lower "shaking" at higher speed, unrealistic, but needed for better playability)
		if self.bic.isOn then
			local speed = self:getLastSpeed()
			local alphaX = self.bic.alphaX + (speed*self.bic.alphaKmhBalanceInc);
			local alphaZ = self.bic.alphaZ + (speed*self.bic.alphaKmhBalanceInc);
			local alphaY = self.bic.alphaY + (speed*self.bic.alphaKmhBalanceInc4);
			
			for i=1, self.numCameras do
				if self.cameras[i].isInside then -- found inside camera
					self.cameras[i].bic.alphaX = 1-math.pow(1-alphaX, self.cameras[i].positionSmoothingResolution);
					self.cameras[i].bic.alphaY = 1-math.pow(1-alphaY, self.cameras[i].positionSmoothingResolution);
					self.cameras[i].bic.alphaZ = 1-math.pow(1-alphaZ, self.cameras[i].positionSmoothingResolution);
				end;
			end;
		end;
	end;
	
end;

-- // updated version for getSmoothed function (vehicleCamera.getSmoothed) to accommodate for different x, y and z alpha values
function betterIndoorCamera:getSmoothed2(alpha, resolution, curX,curY,curZ,startX,startY,startZ,endX,endY,endZ, dt)
	dt = math.modf(dt, resolution);
	local scale = resolution/dt;
	local dx,dy,dz = (endX-startX)*scale, (endY-startY)*scale, (endZ-startZ)*scale;
	local x,y,z = startX, startY, startZ;
	if self.usebic then
		for i=resolution,dt,resolution do
			x,y,z = x + dx, y + dy, z + dz;
			curX = curX + (x-curX)*self.bic.alphaX;
			curY = curY + (y-curY)*self.bic.alphaY;
			curZ = curZ + (z-curZ)*self.bic.alphaZ;
		end	
	else
		for i=resolution,dt,resolution do
			x,y,z = x + dx, y + dy, z + dz;
			curX = curX + (x-curX)*alpha;
			curY = curY + (y-curY)*alpha;
			curZ = curZ + (z-curZ)*alpha;
		end
	end;
	
	return curX,curY,curZ;
end;
VehicleCamera.getSmoothed = betterIndoorCamera.getSmoothed2;

function betterIndoorCamera:delete()
end;
function betterIndoorCamera:onLeave()
end;
function betterIndoorCamera:mouseEvent(posX, posY, isDown, isUp, button)
end;
function betterIndoorCamera:keyEvent(unicode, sym, modifier, isDown)
end;
function betterIndoorCamera:draw()
	if self:getIsActiveForInput() then
		if self.bic.isOn then
			g_currentMission:addHelpButtonText(g_i18n:getText("betterIndoorCamera_ONOFF_2"),  InputBinding.betterIndoorCamera_ONOFF_B);		
		else
			g_currentMission:addHelpButtonText(g_i18n:getText("betterIndoorCamera_ONOFF_B"),  InputBinding.betterIndoorCamera_ONOFF_B);	
		end;
		if self.bic.isWorldXZOn then
			g_currentMission:addHelpButtonText(g_i18n:getText("betterIndoorCamera_WORLDXZONOFF_2"),  InputBinding.betterIndoorCamera_WORLDXZONOFF_B);	
		else
			g_currentMission:addHelpButtonText(g_i18n:getText("betterIndoorCamera_WORLDXZONOFF_B"),  InputBinding.betterIndoorCamera_WORLDXZONOFF_B);	
		end;	
	end;
end;







