-- ***************************************************
-- ***Based on FoerderBandLoad.lua written by Marhu***
-- ***************************************************
--
-- Specialization for grain augers
--
-- Put together by The Man With No Name
-- July 7, 2016
-- version 1.0.3
-- history: v1.0 initial version
-- 			v1.0.1 auger can be turned on/off while on foot, physical augers/rotation parts have more control features
--			v1.0.2 requested feature, ability to set visibility to a given node
--			v1.0.3 add multiplayer support, add save/load attributes and nodes
--
-- Any bugs or imperfections in this script is merely evidence of its hand crafted nature :)
--
g_grainAugerDir = g_currentModDirectory;

grainAuger = {};

function grainAuger.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Fillable, specializations) and SpecializationUtil.hasSpecialization(Shovel, specializations);
end;

function grainAuger:load(xmlFile)
	self.setFillLevel = Utils.overwrittenFunction(self.setFillLevel, grainAuger.setFillLevel);
	self.fillShovelFromTrigger = Utils.overwrittenFunction(self.fillShovelFromTrigger, grainAuger.fillShovelFromTrigger);
	self.shovelTipTrigger.getTipInfoForTrailer = Utils.overwrittenFunction(self.shovelTipTrigger.getTipInfoForTrailer, grainAuger.getTipInfoForTrailer);
	self.shovelTipTrigger.updateTrailerTipping = Utils.overwrittenFunction(self.shovelTipTrigger.updateTrailerTipping, grainAuger.updateTrailerTipping);
	self.updateInitialize = grainAuger.updateInitialize;
	self.findTrailer = grainAuger.findTrailer;
	self.TrailerRaycast = grainAuger.TrailerRaycast;
	self.updateTriggerBackup = grainAuger.updateTriggerBackup;
	self.updateAugers = grainAuger.updateAugers;
	self.setOn = grainAuger.setOn;
	self.defaultSwitch = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.augerRotate#isDefaultOn"),false);
	if self.defaultSwitch then
		self.IsOn = true;
	end;
	self.setVisible = grainAuger.setVisible;
	self.visibilityNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.visibilityNode#index"));
	if self.visibilityNode ~= nil then
		self.defaultVisible = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.visibilityNode#isDefaultVisible"),true);
	end;
	if self.defaultVisible ~= nil then
		if self.defaultVisible then
			self.isVisible = on;
			setVisibility(self.visibilityNode, true);
		else
			self.isVisible = off;
			setVisibility(self.visibilityNode, false);
		end;
	end;
	self.grainAugerDir = g_grainAugerDir
	self.TrailerFindNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.TrailerFindNode#index"));
	self.TrailerFindLight = getChildAt(self.TrailerFindNode,0);
	self.literPerSecond = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.literPerSecond#value"), 1500);
	self.augerEffect = EffectManager:loadEffect(xmlFile , "vehicle.augerEffect" , self.components , self );
	for i=1,table.getn(Fillable.fillTypeIndexToDesc) do
		self.fillTypes[Fillable.fillTypeIndexToDesc[i].index] = true
	end;
	local SoundRefNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.workSound#node"));
	local workSound  = getXMLString(xmlFile, "vehicle.workSound#file");
	if workSound  ~= nil and workSound  ~= "" then
		workSound  = Utils.getFilename(workSound, self.baseDirectory);
		self.workSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.workSound#radius"), 50);
		self.workSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.workSound#innerRadius"), 10);
		self.workSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.workSound#volume"), .8);
		self.workSound = createAudioSource("workSound", workSound, self.workSoundRadius, self.workSoundInnerRadius, self.workSoundVolume, 0);
		link(SoundRefNode, self.workSound);
		setVisibility(self.workSound, false);
	end;
	self.FillLvlBuffer = 0.5;
	self.minFillLvlToUnload = self.capacity;
	self.bodyTypes = true;
	self.GrabBale = g_currentMission.time - 1;
	self.IdentNum = tostring(self.rootNode);
	self.CheckBddy = {};
	self.loaded = true;
	self.Initialize = true;
	self.unitOnOff = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.shovelTipTrigger#index"));
	self.augerCount = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.augerRotate#count"));
	if self.augerCount ~= nil then
		self.table_auger_index = {};
		self.table_auger_axis = {};
		self.table_auger_spin = {};
		self.table_auger_speed = {};
		local string_rot_parts;
		local auger_index;
		local auger_axis;
		local auger_spin;
		for i = 1, self.augerCount do
			string_rot_parts = string.format("vehicle.augerRotate.auger%d", i);
			auger_index = Utils.indexToObject(self.components, getXMLString(xmlFile, string_rot_parts .. "#index"));
			auger_axis = Utils.getNoNil(getXMLFloat(xmlFile, string_rot_parts .. "#augerAxis"));
			auger_spin = Utils.getNoNil(getXMLFloat(xmlFile, string_rot_parts .. "#augerSpin"));
			auger_speed = Utils.getNoNil(getXMLFloat(xmlFile, string_rot_parts .. "#augerSpeed"));
			table.insert(self.table_auger_index, auger_index);
			table.insert(self.table_auger_axis, auger_axis);
			table.insert(self.table_auger_spin, auger_spin);
			table.insert(self.table_auger_speed, auger_speed);
			i = i + 1;
		end;
		self.augerSpinOnStart = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.augerRotate#spinning"),false);
	end;
end;

function grainAuger:delete()
end;

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

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


function grainAuger:update(dt)
	if self:getIsActiveForInput() then
		if InputBinding.hasEvent(InputBinding.grainAuger_ONOFF) then
			self:setOn(not self.IsOn);
		end;
	end;
	if self.augerCount ~= nil then
		if self.IsOn then
			if self.moveAugers == true then
				local rot_speed;
				local rot_axis;
				for i = 1, self.augerCount do
					rot_axis = self.table_auger_axis[i];
					rot_speed = dt*0.01*self.table_auger_spin[i]*self.table_auger_speed[i];
					if rot_axis == 1 then
						rotate(self.table_auger_index[i], rot_speed, 0, 0);
					elseif rot_axis == 2 then
						rotate(self.table_auger_index[i], 0, rot_speed, 0);
					elseif rot_axis == 3 then
						rotate(self.table_auger_index[i], 0, 0, rot_speed);
					end;
					i = i + 1;
				end;
			end;
		end;
	end;
    if self.walkOnOff then
		if InputBinding.hasEvent(InputBinding.grainAuger_ONOFF) then
			self:setOn(not self.IsOn);
		end;
		if InputBinding.hasEvent(InputBinding.grainAuger_visibility) then
			self:setVisible(not self.isVisible);
		end;
	end;
    if self.walkOnOff then
		if self.IsOn then
			g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_Off"), InputBinding.grainAuger_ONOFF);
		else
			g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_On"), InputBinding.grainAuger_ONOFF);
		end;
		if self.visibilityNode ~= nil then 
			if self.isVisible then
				g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_notVisible"), InputBinding.grainAuger_visibility);
				setVisibility(self.visibilityNode, true);
			else
				g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_visible"), InputBinding.grainAuger_visibility);
				setVisibility(self.visibilityNode, false);
			end;
		end;
    end;
	local nearestDistance = 3; 
	local px, py, pz = getWorldTranslation(self.unitOnOff); 
	local vx, vy, vz = getWorldTranslation(getCamera());
	local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);	
	if distance < nearestDistance then
		self.walkOnOff = true; 
	else
		self.walkOnOff = false; 
	end;	
end;

function grainAuger:updateTick(dt)
	if self.isServer then
		local old_inRange = self.inRange;
		self.inRange = false; 
		if self.IsOn then
			if self.triggerBackup then 
				self:updateTriggerBackup(dt);
			end;
			if self.currentFillType ~= Fillable.FILLTYPE_UNKNOWN and self.fillLevel > self.minFillLvlToUnload then
				self.minFillLvlToUnload = 0;
				if self:findTrailer() then
					if self.trailerFound or self.objectFound then
						local deltaLevel = self.literPerSecond*dt/1000.0;
						deltaLevel = math.min(self.fillLevel, deltaLevel);
						local x,y,z = getWorldTranslation(self.fillVolumeDischargeInfo.node);
						local d1x,d1y,d1z = localDirectionToWorld(self.fillVolumeDischargeInfo.node,self.fillVolumeDischargeInfo.width,0,0);
						local d2x,d2y,d2z = localDirectionToWorld(self.fillVolumeDischargeInfo.node,0,0,self.fillVolumeDischargeInfo.length);
						
						if self.trailerFound then
							deltaLevel = math.min(deltaLevel, self.trailerFound.capacity - self.trailerFound.fillLevel);
							self.trailerFound:setFillLevel(self.trailerFound.fillLevel + deltaLevel, self.currentFillType,false,{x=x,y=y,z=z,d1x=d1x,d1y=d1y,d1z=d1z,d2x=d2x,d2y=d2y,d2z=d2z});
						else
--							deltaLevel = math.min(deltaLevel, self.objectFound.bunkerCapacity - self.objectFound.bunkerFillLevel);
							deltaLevel = (self.objectFound:addShovelFillLevel(self, deltaLevel, self.currentFillType)) or 0;
							self.bgalevel = (self.bgalevel or 0) + deltaLevel;
						end
						self:setFillLevel(self.fillLevel-deltaLevel, self.currentFillType,true,{x=x,y=y,z=z,d1x=d1x,d1y=d1y,d1z=d1z,d2x=d2x,d2y=d2y,d2z=d2z});
						self.inRange = true;
					end;
					
					if self.fillLevel < 0.00001 and self.fillLevel > 0 then
						for i = 1, table.getn(self.fillVolumes) do
							for j = 1,3 do
								self.fillVolumes[i].uvPosition[j] = 0;
							end;
						end;
						self:setFillLevel(0, Fillable.FILLTYPE_UNKNOWN);
					end;
				end;
			elseif self.minFillLvlToUnload == 0 then
				self.minFillLvlToUnload = self.capacity;
			elseif self.fillLevel > 0 then
				self.minFillLvlToUnload = math.max(0,self.minFillLvlToUnload-dt);
			end;
		end;
		if old_inRange ~= self.inRange then
			self.SendEvent = true;
		end
		if self.TrailerFindLight_Time then
			if self.TrailerFindLightVisible ~= true then
				self.TrailerFindLightVisible = true
				self.SendEvent = true;
			end
			self.TrailerFindLight_Time = self.TrailerFindLight_Time - dt;
			if self.TrailerFindLight_Time <= 0 then
				self.TrailerFindLight_Time = nil;
			end
		elseif self.TrailerFindLightVisible == true then
			self.TrailerFindLightVisible = nil;
			self.SendEvent = true;
		end;
		if self.augerSpinOnStart ~= true then
			if self.fillLevelChange then
				if self.moveBelts == nil then
					grainAugerMoveAugersEvent.sendEvent(self,true);
				end;
				self.moveAugers = true;
			elseif self.moveAugers ~= nil then
				if self.moveAugers == false then
					self.moveAugers = nil;
					grainAugerMoveAugersEvent.sendEvent(self,false)
				else
					self.moveAugers = false;
				end;
			end;
		else
			self.moveAugers = true;
			grainAugerMoveAugersEvent.sendEvent(self,true);
		end;
	end;
	if self.SendEvent then
		self.SendEvent = nil
		grainAugerStatusEvent.sendEvent(self)
	end
	if self.isClient then
		if self.moveAugers then 
			self:updateAugers(dt); 
		end;
		if self.inRange and not self.turnOn then
			self.turnOn = true;
			for i = 1,table.getn(self.augerEffect.effectNodes) do
				local mat = Fillable.getFillMaterial(self.currentFillType , self.augerEffect.effectNodes[i].materialType , self.augerEffect.effectNodes[i].materialTypeId);
				setMaterial(self.augerEffect.effectNodes[i].node,mat,0);
			end;
			EffectManager:startEffect(self.augerEffect);				
			playSample(self.workSound, 0, 1, 0);
			setVisibility(self.workSound, true);
		elseif not self.inRange and self.turnOn then 
			self.turnOn = false;
			EffectManager:stopEffect(self.augerEffect);
			setVisibility(self.workSound, false);
		end;
		if self.TrailerFindLight_Time ~= nil and getVisibility(self.TrailerFindLight) ~= true then
			setVisibility(self.TrailerFindLight,true);
		elseif self.TrailerFindLight_Time == nil and getVisibility(self.TrailerFindLight) == true then
			setVisibility(self.TrailerFindLight,false);
		end;
	end;
end;

function grainAuger:draw()
	if self:getIsActiveForInput() then
		if self.IsOn then
			g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_Off"), InputBinding.grainAuger_ONOFF);
		else
			g_currentMission:addHelpButtonText(g_i18n:getText("grainAuger_On"), InputBinding.grainAuger_ONOFF);
		end;
	end;
end;

function grainAuger:setFillLevel(org_Funktion, fillLevel, fillType, force, ...)
	if not self.IsOn then 
		fillLevel = self.fillLevel; 
	end;
	local old_fillLevel = self.fillLevel;
	local r = {org_Funktion(self, fillLevel, fillType, force, ...)};
	local delta = self:getFillLevel(fillType) - old_fillLevel;
	if math.abs(delta) > 0 then
		self.fillLevelChange = true;
	end;
	return unpack(r);
end

function grainAuger:fillShovelFromTrigger(org_Funktion, shovelTrigger, ...)
	local r = {org_Funktion(self, shovelTrigger, ...)};
	self.triggerBackup = shovelTrigger;
	return unpack(r);
end

function grainAuger:getTipInfoForTrailer(org_Funktion, ...)
	if self.fillable.IsOn ~= true then
		return true, math.huge;
	end;
	return org_Funktion(self, ...);
end

function grainAuger:updateTrailerTipping(org_Funktion, trailer, fillDelta, fillType)
	local soll_fillLevel = self.fillable.fillLevel-fillDelta;
	local r = {org_Funktion(self, trailer, fillDelta, fillType)};
	local diff = soll_fillLevel-self.fillable.fillLevel;
	trailer:setFillLevel(trailer.fillLevel + diff, fillType);
	return unpack(r)
end

function grainAuger:findTrailer()
	local x,y,z = getWorldTranslation(self.TrailerFindNode);
	local ox,oy,oz = worldToLocal(self.TrailerFindNode, x,y-1,z);
    local dx,dy,dz = localDirectionToWorld(self.TrailerFindNode, ox,oy,oz);
	self.TrailerFindLight_Time = 2000;
    self.trailerFound = nil;
	self.objectFound = nil;
    raycastAll(x, y, z, dx,dy,dz, "TrailerRaycast", 7, self);
	if self.trailerFound then
		if self.trailerFoundSupported == false or self.trailerFound.capacity <= self.trailerFound.fillLevel then
			return false;
		end;
	elseif self.objectFound then
		if self.objectFoundSupported == false then  --or not self.objectFound:isa(Bga) or self.objectFound.bunkerCapacity <= self.objectFound.bunkerFillLevel 
			return false;
		end;
	else
		return false;
	end;

    return true;
end;

function grainAuger:TrailerRaycast(transformId, x, y, z, distance)
	local trailer = g_currentMission.objectToTrailer[transformId];
    if trailer ~= nil and trailer ~= self then
        if trailer:allowFillType(self.currentFillType) and trailer.getAllowFillFromAir ~= nil and trailer:getAllowFillFromAir() then
            self.trailerFound = trailer;
            self.trailerFoundSupported = true;
        else
            if self.trailerFound == nil then
                self.trailerFound = trailer;
                self.trailerFoundSupported = false;
            end;
        end;
        return false;
    end;
	local object = g_currentMission:getNodeObject(transformId);
	if object ~= nil and object ~= self and object.addShovelFillLevel ~= nil and object.getAllowShovelFillType ~= nil then
		if object:getAllowShovelFillType(self.currentFillType) then
			self.objectFound = object;
			self.objectFoundSupported = true;
			return false;
		else
			if self.objectFound == nil then
				self.objectFound = object;
				self.objectFoundSupported = false;
			end;
		end;
	end;
    return true;
end;

function grainAuger:updateTriggerBackup(dt)
	if not self.triggerBackup.currentShovel then
		if self.fillLevel < self.capacity then
			local shovelNode = self.components[1];
			if self.shovelNodes then
				for k,v in pairs(self.shovelNodes) do
						shovelNode = k;
					break;
				end;
			end;
			local sx, sy, sz = getWorldTranslation(shovelNode);
			local nearestDistance = 10;
			local nearestPlane = nil;
			
			if self.triggerBackup.bunkerSilo ~= nil then
				for i,movingPlane in pairs(self.triggerBackup.bunkerSilo.movingPlanes) do
					if movingPlane.fillLevel > 0 then
						local px,py,pz = getWorldTranslation(movingPlane.nodeId);
						local distance = Utils.vector3Length(sx-px, sy-py, sz-pz);
						if distance < nearestDistance then
							nearestDistance = distance;
							nearestPlane = i;
						end;
					end;
				end;
				if nearestPlane ~= nil then
					self.triggerBackup.bunkerSilo.movingPlanes[nearestPlane].shovelTrigger:fillShovel(self, dt)
				else
					self.triggerBackup = nil;
				end;
			elseif self.triggerBackup.fillShovel ~= nil then
				if self.triggerBackup.triggerId then
					local px,py,pz = getWorldTranslation(self.triggerBackup.triggerId); 
					local distance = Utils.vector3Length(sx-px, sy-py, sz-pz);
					if distance < nearestDistance then
						local old_fillLevel = self.fillLevel;
						self.triggerBackup:fillShovel(self, dt);
						if old_fillLevel == self.fillLevel then
							self.triggerBackup = nil;
						end;
					else
						self.triggerBackup = nil;
					end;
				end;
			else
				self.triggerBackup = nil;
			end;
		end;
	end;
end;

function grainAuger:updateAugers(dt)
	self.fillLevelChange = nil;
	for i = 1, table.getn(self.fillVolumes) do
		for j = 1,3 do
			self.fillVolumes[i].uvPosition[j] = self.fillVolumes[i].uvPosition[j] - (self.fillVolumes[i].scrollSpeedDischarge[j]*dt);
		end;
		setShaderParameter(self.fillVolumes[i].volume, "uvOffset", self.fillVolumes[i].uvPosition[1],self.fillVolumes[i].uvPosition[2],self.fillVolumes[i].uvPosition[3],1,false);
	end;
end;

function grainAuger:setOn(on, noEventSend)
	if noEventSend == nil or noEventSend == false then
		self.SendEvent = true;
	end;
	self.IsOn = on;
end;

function grainAuger:setVisible(on, noEventSend)
	if noEventSend == nil or noEventSend == false then
		self.SendEvent = true;
	end;
	self.isVisible = on;
end;

function grainAuger:getSaveAttributesAndNodes(nodeIdent)
	local attributes, nodes = "", "";
	attributes = attributes ..'isVisible="'..tostring(self.isVisible)..'" IsOn="'..tostring(self.IsOn)..'"';
	return attributes, nodes;
end;

function grainAuger:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	if not resetVehicles then
		local valueOn = getXMLBool(xmlFile, key.."#IsOn");
		if valueOn ~= nil then
			self.IsOn = valueOn;
		end;
		local valueVisible = getXMLBool(xmlFile, key.."#isVisible");
		if valueVisible ~= nil then
			self.isVisible = valueVisible;
		end;
	end;
    return BaseMission.VEHICLE_LOAD_OK;
end;

grainAugerStatusEvent = {};
grainAugerStatusEvent_mt = Class(grainAugerStatusEvent, Event);

InitEventClass(grainAugerStatusEvent, "grainAugerStatusEvent");

function grainAugerStatusEvent:emptyNew()
    local self = Event:new(grainAugerStatusEvent_mt);
    return self;
end;
    
function grainAugerStatusEvent:new(object)
	local self = grainAugerStatusEvent:emptyNew()
	self.object = object;
	self.IsOn = object.IsOn;
	self.isVisible = object.isVisible;
	self.TrailerFindLight = object.TrailerFindLight_Time and true or false;
	self.inRange = object.inRange and true or false;
	return self;
end;

function grainAugerStatusEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteBool(streamId, self.IsOn);
	streamWriteBool(streamId, self.isVisible);
	streamWriteBool(streamId, self.TrailerFindLight);
	streamWriteBool(streamId, self.inRange);
end;

function grainAugerStatusEvent:readStream(streamId, connection)
	local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.IsOn = streamReadBool(streamId);
	self.isVisible = streamReadBool(streamId);
	self.TrailerFindLight = streamReadBool(streamId);
	self.inRange = streamReadBool(streamId);
	self:run(connection);
end;

function grainAugerStatusEvent:run(connection)
	if not connection:getIsServer() then
		g_server:broadcastEvent(self, false, connection, self.object);
	end;
	if self.object ~= nil then
		self.object:setOn(self.IsOn, true);
		self.object:setVisible(self.isVisible, true);
		if connection:getIsServer() then
			self.object.TrailerFindLight_Time = self.TrailerFindLight and 2000 or nil;
			self.object.inRange = self.inRange;
		end;
	end;
end;

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

grainAugerMoveAugersEvent = {};
grainAugerMoveAugersEvent_mt = Class(grainAugerMoveAugersEvent, Event);

InitEventClass(grainAugerMoveAugersEvent, "grainAugerMoveAugersEvent");

function grainAugerMoveAugersEvent:emptyNew()
    local self = Event:new(grainAugerMoveAugersEvent_mt);
    return self;
end;
    
function grainAugerMoveAugersEvent:new(object, move)
	local self = grainAugerMoveAugersEvent:emptyNew()
	self.object = object;
	self.move = move;
	return self;
end;

function grainAugerMoveAugersEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteBool(streamId, self.move);
end;

function grainAugerMoveAugersEvent:readStream(streamId, connection)
	local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.move = streamReadBool(streamId);
	self:run(connection);
end;

function grainAugerMoveAugersEvent:run(connection)
	if connection:getIsServer() then
		if self.object ~= nil then
			self.object.moveAugers = self.move;
		end;
	end;
end;

function grainAugerMoveAugersEvent.sendEvent(vehicle, move, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(grainAugerMoveAugersEvent:new(vehicle, move), nil, nil, vehicle);
		end;
	end;
end;