--
-- wheelWeight
-- Specialization for wheelWeight functionality
--
-- @author  Milan1702976
-- @date  10/4/13
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

wheelWeight = {};

function wheelWeight.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Motorized, specializations);
end;

function wheelWeight:load(xmlFile)
    self.getRpmLimit = Utils.overwrittenFunction(self.getRpmLimit, wheelWeight.getRpmLimit);
    self.toggleControl = wheelWeight.toggleControl;

	self.isInControll = false
    self.isControlled = false;
	
    wheelWeight.loadSettingsFromXML(self, xmlFile);
	
    self.lastAcceleration = 0;
    self.lastRealAcceleration = 0;

    self.isEntered = false;

    self.steeringEnabled = true;
    self.stopMotorOnLeave = true;
    self.deactivateOnLeave = true;

    self.axisForward = 0;
    self.axisForwardIsAnalog = false;
    self.axisSide = 0;
    self.axisSideIsAnalog = false;
	
    self.isActiveForInput = false;
	
end;

function wheelWeight:loadSettingsFromXML(xmlFile)

    self.speedRotScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.speedRotScale#scale"), 80);
    self.speedRotScaleOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.speedRotScale#offset"), 0.7);
    self.maxRotatedTimeSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.maxRotatedTimeSpeed#value"), 1)*0.001;

    self.maxAccelerationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.accelerationSpeed#maxAcceleration"), 2)*0.001;
    self.decelerationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.accelerationSpeed#deceleration"), 0.5)*0.001;

    self.backwardMaxAccelerationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.accelerationSpeed#backwardMaxAcceleration"), self.maxAccelerationSpeed*1000)*0.001;
    self.backwardDecelerationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.accelerationSpeed#backwardDeceleration"), self.decelerationSpeed*1000)*0.001;
end;

function wheelWeight:readStream(streamId, connection)
    local isControlled = streamReadBool(streamId);
    if isControlled then
        self.controllerName = streamReadString(streamId);
        self:onEnter(false);
    end;
end;

function wheelWeight:writeStream(streamId, connection)
    if streamWriteBool(streamId, self.isControlled) then
        streamWriteString(streamId, self.controllerName);
    end;
end;

function wheelWeight:readUpdateStream(streamId, timestamp, connection)
    if not connection:getIsServer() then
        local hasSteerableUpdate = streamReadBool(streamId);
        if hasSteerableUpdate then
            local axisForwardIsAnalog = streamReadBool(streamId);
            local axisSideIsAnalog = streamReadBool(streamId);
            local axisForward = streamReadFloat32(streamId);
            local axisSide = streamReadFloat32(streamId);
            local dt = streamReadFloat32(streamId);
            if self.steeringEnabled and self.isControlled then
                wheelWeight.updateVehiclePhysics(self, axisForward, axisForwardIsAnalog, axisSide, axisSideIsAnalog, dt);
            end;
        end;
    end;
end;

function wheelWeight:writeUpdateStream(streamId, connection, dirtyMask)
    if connection:getIsServer() then
        if streamWriteBool(streamId, bitAND(dirtyMask, self.steerableGroundFlag) ~= 0) then
            streamWriteBool(streamId, self.axisForwardIsAnalog);
            streamWriteBool(streamId, self.axisSideIsAnalog);
            streamWriteFloat32(streamId, self.axisForward);
            streamWriteFloat32(streamId, self.axisSide);
            streamWriteFloat32(streamId, self.tickDt);
        end;
    end;
end;

function wheelWeight:delete()

end;

function wheelWeight:mouseEvent(posX, posY, isDown, isUp, button)

end;

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

function wheelWeight:update(dt)
	if self.attacherVehicle ~= nil then
		local numTouching = 0;
		local numNotTouching = 0;
		local attacherVehicle = self.attacherVehicle
		local attacherVehicleNumTouching = 0;
		local attacherVehicleNumNotTouching = 0;
		local implement = attacherVehicle:getImplementByObject(self);
		self.isActiveForInput = attacherVehicle:getIsActiveForInput(false)
		self.isControlled = attacherVehicle.isControlled
		self.isEntered = attacherVehicle.isEntered
-- renderText(0.6, 0.58, 0.02, "attacherVehicle.isControlled: " .. tostring(attacherVehicle.isControlled));
-- renderText(0.6, 0.56, 0.02, "attacherVehicle.isEntered: " .. tostring(attacherVehicle.isEntered));
-- renderText(0.6, 0.54, 0.02, "attacherVehicle:getIsActiveForInput(): " .. tostring(attacherVehicle:getIsActiveForInput()));
-- renderText(0.6, 0.52, 0.02, "attacherVehicle:getIsActiveForInput(true): " .. tostring(attacherVehicle:getIsActiveForInput(true)));
-- renderText(0.6, 0.50, 0.02, "attacherVehicle:getIsActiveForInput(false): " .. tostring(attacherVehicle:getIsActiveForInput(false)));
		if self.isServer then
			if implement ~= nil then
				local jointDesc = attacherVehicle.attacherJoints[implement.jointDescIndex];
				if jointDesc ~= nil then
					for k, wheel in pairs(self.wheels) do
						local hasGroundContact = wheel.hasGroundContact;
						if hasGroundContact then
							numTouching = numTouching+1;
						else
							numNotTouching = numNotTouching+1;
						end;
					end;
				
					if numTouching > 0 then
-- renderText(0.6, 0.34, 0.02, "dt: " .. dt);
-- renderText(0.6, 0.32, 0.02, "jointDesc.lowerAlpha: " .. jointDesc.lowerAlpha);
-- renderText(0.6, 0.30, 0.02, "jointDesc.moveAlpha: " .. jointDesc.moveAlpha);
						for k, wheel in pairs(attacherVehicle.wheels) do
							local hasGroundContact = wheel.hasGroundContact;
							local requiredDriveMode = 2 -- 0: wheel, 1: drive wheel, 2: steering wheel
							
							if wheel.driveMode >= requiredDriveMode then
								if hasGroundContact then
									attacherVehicleNumTouching = attacherVehicleNumTouching+1;
								else
									attacherVehicleNumNotTouching = attacherVehicleNumNotTouching+1;
								end;
							end;
						end;
-- renderText(0.6, 0.42, 0.02, "attacherVehicleNumTouching: " .. attacherVehicleNumTouching);
-- renderText(0.6, 0.40, 0.02, "attacherVehicleNumNotTouching: " .. attacherVehicleNumNotTouching);
						if attacherVehicleNumTouching > 0 then
							jointDesc.lowerAlpha = 5
							self:toggleControl(false)
						end
						if attacherVehicleNumNotTouching > 0 then
							if self.isInControll ~= true then
								jointDesc.lowerAlpha = jointDesc.moveAlpha + 0.5
								self:toggleControl(true)
							end
						end
					end
					if numNotTouching > 0 then
						jointDesc.lowerAlpha = 5
						self:toggleControl(false)
					end
				end
			end
		end
	end
end;

function wheelWeight:toggleControl(isInControll, noEventSend)
    if isInControll ~= self.isInControll then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(wheelWeightToggleControlEvent:new(self, isInControll), nil, nil, self);
            else
                g_client:getServerConnection():sendEvent(wheelWeightToggleControlEvent:new(self, isInControll));
            end;
        end;

		self.isInControll = isInControll
	end
end

function wheelWeight:updateTick(dt)
-- renderText(0.1, 0.10, 0.02, "self.isEntered and self.isClient and self.isInControll: " .. tostring(self.isEntered) .. " and " .. tostring(self.isClient) .. " and " .. tostring(self.isInControll));
    if self.isEntered and self.isClient then
-- renderText(0.1, 0.12, 0.02, "self.isEntered and self.isClient and self.isInControll: " .. tostring(self.isEntered) .. " and " .. tostring(self.isClient) .. " and " .. tostring(self.isInControll));
        self.axisForward = InputBinding.getDigitalInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE);
        if InputBinding.isAxisZero(self.axisForward) then
            self.axisForward = InputBinding.getAnalogInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE)
            if not InputBinding.isAxisZero(self.axisForward) then
                self.axisForwardIsAnalog = true;
            end
        else
            self.axisForwardIsAnalog = false;
        end;

        self.axisSide = InputBinding.getDigitalInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE);
        if InputBinding.isAxisZero(self.axisSide) then
            self.axisSide = InputBinding.getAnalogInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE)
            if not InputBinding.isAxisZero(self.axisSide) then
                self.axisSideIsAnalog = true;
            end
        else
            self.axisSideIsAnalog = false;
        end;
-- renderText(0.1, 0.40, 0.02, "self.isActiveForInput: " .. tostring(self.isActiveForInput))

        if not self.isActiveForInput then
            if not self.axisSideIsAnalog then
                self.axisSide = 0;
            end
            if not self.axisForwardIsAnalog then
                self.axisForward = 0;
            end

            if self.steeringEnabled then
                if g_gui.currentGui ~= nil and g_gui.currentGuiName ~= "ChatDialog" then
                    self.motor:setSpeedLevel(0, true)
                end;
            end;
        end

        if g_isServerStreamingVersion then self.axisSide = self.axisSide * 0.5; end;   -- This is the factor to slow down the steering

        if self.isServer then
            if self.steeringEnabled then
-- renderText(0.1, 0.26, 0.02, "self.axisForward: " .. tostring(self.axisForward));
-- renderText(0.1, 0.24, 0.02, "self.axisForwardIsAnalog: " .. tostring(self.axisForwardIsAnalog));
-- renderText(0.1, 0.22, 0.02, "self.axisSide: " .. tostring(self.axisSide));
-- renderText(0.1, 0.20, 0.02, "self.axisSideIsAnalog: " .. tostring(self.axisSideIsAnalog));
                wheelWeight.updateVehiclePhysics(self, self.axisForward, self.axisForwardIsAnalog, self.axisSide, self.axisSideIsAnalog, dt);
            end;
        else
            if self.steeringEnabled then
                if math.abs(self.axisForward) > 0.8 then
                    self.motor:setSpeedLevel(0, true)
                end;
            end;

            self.motor:computeMotorRpm(self.wheelRpm, self.axisForward);
            self:raiseDirtyFlags(self.steerableGroundFlag);
        end;
    end;
end

function wheelWeight.calculateRealAcceleration(self, acceleration, dt)

    local maxAccelerationSpeed = self.maxAccelerationSpeed;
    local decelerationSpeed = self.decelerationSpeed;
    if self.movingDirection < 0 then
        maxAccelerationSpeed = self.backwardMaxAccelerationSpeed;
        decelerationSpeed = self.backwardDecelerationSpeed;
    end;
    if math.abs(acceleration) > 0.0001 then
        if Utils.sign(acceleration) ~= Utils.sign(self.lastAcceleration) then
            self.lastAcceleration = 0;
        end;
        acceleration = math.min(math.max(acceleration, self.lastAcceleration-dt*maxAccelerationSpeed), self.lastAcceleration+dt*maxAccelerationSpeed);
        self.lastAcceleration = acceleration;
    else
        if self.lastAcceleration > 0 then
            self.lastAcceleration = math.max(self.lastAcceleration-dt*decelerationSpeed, 0);
        elseif self.lastAcceleration < 0 then
            self.lastAcceleration = math.min(self.lastAcceleration+dt*decelerationSpeed, 0);
        end;
    end;
    self.lastRealAcceleration = acceleration;

    return acceleration;
end;

function wheelWeight.updateVehiclePhysics(self, axisForward, axisForwardIsAnalog, axisSide, axisSideIsAnalog, dt)
    local acceleration = 0;
    if self.isMotorStarted and self.motorStartTime <= self.time then
        acceleration = -axisForward;
        if math.abs(acceleration) > 0.8 then
            if self.motor.speedLevel ~= 0 then
                self.lastAcceleration = self.lastAcceleration*0.5;
            end;
            self.motor:setSpeedLevel(0, true)
        end;
        if self.motor.speedLevel ~= 0 then
            acceleration = self.motor.accelerations[self.motor.speedLevel];
            --acceleration = 1.0;
        end;
        --acceleration = 0.5;
    end;
    if self.fuelFillLevel == 0 then
        acceleration = 0;
    end;

    acceleration = wheelWeight.calculateRealAcceleration(self, acceleration, dt);


    --if Input.isKeyPressed(Input.KEY_a) then
    local inputAxisX = axisSide;
    if axisSideIsAnalog then
        local targetRotatedTime = 0;
        if inputAxisX < 0 then
            -- 0 to maxRotTime
            targetRotatedTime = math.min(-self.maxRotTime*inputAxisX, self.maxRotTime);
        else
            -- 0 to minRotTime
            targetRotatedTime = math.max(self.minRotTime*inputAxisX, self.minRotTime);
        end;
        local maxTime = self.maxRotatedTimeSpeed*dt;
        if math.abs(targetRotatedTime-self.rotatedTime) > maxTime then
            if targetRotatedTime > self.rotatedTime then
                self.rotatedTime = self.rotatedTime + maxTime;
            else
                self.rotatedTime = self.rotatedTime - maxTime;
            end;
        else
            self.rotatedTime = targetRotatedTime;
        end;
    else
        local rotScale = math.min(1.0/(self.lastSpeed*self.speedRotScale+self.speedRotScaleOffset), 1);
        --local inputAxisX = InputBinding.getDigitalInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE);
        if inputAxisX < 0 then
            self.rotatedTime = math.min(self.rotatedTime - dt*0.001*inputAxisX*rotScale, self.maxRotTime);
            --if self.rotatedTime > self.maxRotTime then
            --    self.rotatedTime = self.maxRotTime;
            --end;
        --elseif Input.isKeyPressed(Input.KEY_d) then
        elseif inputAxisX > 0 then
            self.rotatedTime = math.max(self.rotatedTime - dt*0.001*inputAxisX*rotScale, self.minRotTime);
            --if self.rotatedTime < self.minRotTime then
            --    self.rotatedTime = self.minRotTime;
            --end;
        else
            if self.autoRotateBackSpeed ~= 0 then
                if self.rotatedTime > 0 then
                    self.rotatedTime = math.max(self.rotatedTime - dt*0.001*self.autoRotateBackSpeed*rotScale, 0);
                else
                    self.rotatedTime = math.min(self.rotatedTime + dt*0.001*self.autoRotateBackSpeed*rotScale, 0);
                end;
            end;
        end;
    end;
	
-- renderText(0.1, 0.34, 0.02, "self.requiredDriveMode: " .. tostring(self.requiredDriveMode));
-- renderText(0.1, 0.32, 0.02, "acceleration: " .. tostring(acceleration));
-- renderText(0.1, 0.30, 0.02, "self.lastSpeed: " .. tostring(self.lastSpeed));

    if self.firstTimeRun then
        self.motor.rpmLimit = self:getRpmLimit();
        WheelsUtil.updateWheelsPhysics(self, dt, self.lastSpeed, acceleration, false, self.requiredDriveMode)
    end;
end;

function wheelWeight:draw()
end;

function wheelWeight:onAttach(attacherVehicle, jointDescIndex)
	-- increse joint movment
	local jointDesc = attacherVehicle.attacherJoints[jointDescIndex];
	jointDesc.maxRot[1] = jointDesc.maxRot[1] * 2
	jointDesc.maxRot2[1] = jointDesc.maxRot2[1] * 1.25
	jointDesc.lowerAlpha = 5

    self:startMotor(true);
end;

function wheelWeight:onDetach(attacherVehicle, jointDescIndex)

    if self.stopMotorOnLeave then
        self.lastAcceleration = 0;
        self:stopMotor(true);
    end;
    if self.deactivateOnLeave then
        self.lastAcceleration = 0;
        if self.isServer then
            for k,wheel in pairs(self.wheels) do
                setWheelShapeProps(wheel.node, wheel.wheelShape, 0, self.motor.brakeForce, 0);
            end;
        end;
    end;
		
	-- decrese joint movment
	local implement = attacherVehicle:getImplementByObject(self);
	if implement ~= nil then
		local jointDesc = attacherVehicle.attacherJoints[implement.jointDescIndex];
		jointDesc.maxRot[1] = jointDesc.maxRot[1] / 2
		jointDesc.maxRot2[1] = jointDesc.maxRot2[1] / 1.25		
		jointDesc.lowerAlpha = 1
	end;
end;

function wheelWeight:setSpeedLevel(speedLevel)
    self.lastAcceleration = self.lastAcceleration*0.5;
    self.motor:setSpeedLevel(speedLevel, false);
end;

function wheelWeight:getRpmLimit(superFunc)
    if superFunc ~= nil then
        return superFunc(self);
    end;
    return math.huge;
end;

function print_table (maintable, subtable, indent)
	print("_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _")
	local indent = indent or '\t'
	local subtable = subtable or false
	for key,value in pairs(maintable) do
		if type(value) == "table" then 
			print(indent,'[',tostring(key),']',':') 
			if subtable == true then
				print_table1(value,true,indent..'\t')
			end
		else 
			print(indent,'[',tostring(key),']','=',tostring(value),'')
		end
	end
end

function print_table1 (maintable, subtable, indent)
	local indent = indent or '\t'
	local subtable = subtable or false
	for key,value in pairs(maintable) do
		if type(value) == "table" then 
			print(indent,'[',tostring(key),']',':') 
			if subtable == true then
				print_table2(value,true,indent..'\t')
			end
		else 
			print(indent,'[',tostring(key),']','=',tostring(value),'')
		end
	end
end

function print_table2 (maintable, subtable, indent)
	local indent = indent or '\t'
	local subtable = subtable or false
	for key,value in pairs(maintable) do
		if type(value) == "table" then 
			print(indent,'[',tostring(key),']',':') 
			if subtable == true then
				print_table3(value,true,indent..'\t')
			end
		else 
			print(indent,'[',tostring(key),']','=',tostring(value),'')
		end
	end
end

function print_table3 (maintable, subtable, indent)
	local indent = indent or '\t'
	local subtable = subtable or false
	for key,value in pairs(maintable) do
		if type(value) == "table" then 
			print(indent,'[',tostring(key),']',':') 
			if subtable == true then
				print_table3(value,false,indent..'\t')
			end
		else 
			print(indent,'[',tostring(key),']','=',tostring(value),'')
		end
	end
end
