--
-- changeBodyTypes
--
-- @author:    	Xentro (www.fs-uk.com)(Marcus@Xentro.se)
-- @version:    v1.01
-- @date:       2013-03-30
-- 
--
--[[
<changeBodyTypes inputBinding="opt_string_input" fillLevelChange="bool">
	<body name="string_name" capacity="opt_int" rotationDegrees="bool" fillTypes="string_filltypes">
		<fillPlane type="string_filltype">
			<node index="">
				<key time="float" translation="string_xyz" rotation="string_xyz" scale="string_xyz" />
			</node>
		</fillPlane>
		
		<visibility>
			<node index="" />
		</visibility>
	</body>
	
	<visibility bodyId="string_ids" >
		<node index="" />
	</visibility>
</changeBodyTypes>
]]--
changeBodyTypes = {};

function changeBodyTypes.prerequisitesPresent(specializations)
	if not SpecializationUtil.hasSpecialization(Trailer, specializations) then print("Warning: Specialization changeBodyTypes needs the specialization Trailer."); end;
	if not SpecializationUtil.hasSpecialization(Fillable, specializations) then print("Warning: Specialization changeBodyTypes needs the specialization Fillable."); end;
	
	return SpecializationUtil.hasSpecialization(Fillable, specializations) and SpecializationUtil.hasSpecialization(Trailer, specializations);
end;

function changeBodyTypes:load(xmlFile)
	self.setBodyTypeId = SpecializationUtil.callSpecializationsFunction("setBodyTypeId");
	self.setBodyType = SpecializationUtil.callSpecializationsFunction("setBodyType");
	self.updateBodyParts = SpecializationUtil.callSpecializationsFunction("updateBodyParts");
	
	local input = getXMLString(xmlFile, "vehicle.changeBodyTypes#inputBinding");
	if InputBinding[input] ~= nil then
		self.cby_input = InputBinding[input];
	else
		self.cby_input = InputBinding.IMPLEMENT_EXTRA4;
	end;
	
	self.bodyTypes = {};
	local i = 0;
	while true do
		local key = string.format("vehicle.changeBodyTypes.body(%d)", i);
		if not hasXMLProperty(xmlFile, key) then break; end;
		
		local entry = {};
		entry.name = getXMLString(xmlFile, key .. "#name");
		entry.capacity = Utils.getNoNil(getXMLInt(xmlFile, key .. "#capacity"), self.capacity);
		local fillPlanesRotDeg = Utils.getNoNil(getXMLBool(xmlFile, key .. "#rotationDegrees"), false);
		
		local fillTypes = getXMLString(xmlFile, key .. "#fillTypes");
		if fillTypes ~= nil then
			entry.fillTypes = {};
			entry.fillTypes[Fillable.FILLTYPE_UNKNOWN] = true;
			
			local types = Utils.splitString(" ", fillTypes);
			for k, v in pairs(types) do
				local fillType = Fillable.fillTypeNameToInt[v];
				if fillType ~= nil then
					entry.fillTypes[fillType] = true;
				else
					print("Warning: '" .. self.configFileName .. "' has invalid fillType '" .. v .. "'.");
				end;
			end;
		end;
		
		if self.isClient then
			entry.fillPlanes = {};
			
			local plainI = 0;
			while true do
				local plainKey = key .. string.format(".fillPlane(%d)", plainI);
				if not hasXMLProperty(xmlFile, plainKey) then break; end;
				
				local fillType = getXMLString(xmlFile, plainKey .. "#type");
				local fillPlane = {};
				fillPlane.capacity = capacity;
				
				if fillType ~= nil then
					fillPlane.nodes = {};
					
					local nodeI = 0;
					while true do
						local nodeKey = plainKey .. string.format(".node(%d)", nodeI);
						if not hasXMLProperty(xmlFile, nodeKey) then break; end;
						
						local node = Utils.indexToObject(self.components, getXMLString(xmlFile, nodeKey .. "#index"));
						if node ~= nil then
							local defaultX, defaultY, defaultZ = getTranslation(node);
							local defaultRX, defaultRY, defaultRZ = getRotation(node);
							setVisibility(node, false);
 
							local animCurve = AnimCurve:new(linearInterpolatorTransRotScale);
							local keyI = 0;
							while true do
								local animKey = nodeKey .. string.format(".key(%d)", keyI);
								if not hasXMLProperty(xmlFile, animKey) then break; end;
								
								local keyTime = getXMLFloat(xmlFile, animKey .. "#time");
								if keyTime ~= nil then
									local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, animKey .. "#translation"));
									if y == nil then
										y = getXMLFloat(xmlFile, animKey .. "#y");
									end;
									
									local rx, ry, rz = Utils.getVectorFromString(getXMLString(xmlFile, animKey .. "#rotation"));
									local sx, sy, sz = Utils.getVectorFromString(getXMLString(xmlFile, animKey .. "#scale"));
									local x = Utils.getNoNil(x, defaultX);
									local y = Utils.getNoNil(y, defaultY);
									local z = Utils.getNoNil(z, defaultZ);
									
									if fillPlanesRotDeg then
										rx = Utils.getNoNilRad(rx, defaultRX);
										ry = Utils.getNoNilRad(ry, defaultRY);
										rz = Utils.getNoNilRad(rz, defaultRZ);
									else
										rx = Utils.getNoNil(rx, defaultRX);
										ry = Utils.getNoNil(ry, defaultRY);
										rz = Utils.getNoNil(rz, defaultRZ);
									end
									
									local sx = Utils.getNoNil(sx, 1);
									local sy = Utils.getNoNil(sy, 1);
									local sz = Utils.getNoNil(sz, 1);
									
									animCurve:addKeyframe({x = x, y = y, z = z, rx = rx, ry = ry, rz = rz, sx = sx, sy = sy, sz = sz, time = keyTime});
									keyI = keyI +1;
								else
									print("Error: key time is nil in '" .. self.configFileName .. "'");
									break;
								end;
							end;
							
							if keyI == 0 then
								local minY, maxY = Utils.getVectorFromString(getXMLString(xmlFile, nodeKey .. "#minMaxY"));
								local minY = Utils.getNoNil(minY, defaultY);
								local maxY = Utils.getNoNil(maxY, defaultY);
								
								animCurve:addKeyframe({x = defaultX, y = minY, z = defaultZ, rx = defaultRX, ry = defaultRY, rz = defaultRZ, sx = 1, sy = 1, sz = 1, time = 0});
								animCurve:addKeyframe({x = defaultX, y = maxY, z = defaultZ, rx = defaultRX, ry = defaultRY, rz = defaultRZ, sx = 1, sy = 1, sz = 1, time = 1});
							end;
							local alwaysVisible = Utils.getNoNil(getXMLBool(xmlFile, nodeKey .. "#alwaysVisible"), false);
							
							table.insert(fillPlane.nodes, {node = node, animCurve = animCurve, alwaysVisible = alwaysVisible});
							nodeI = nodeI + 1;
						else
							print("Error: node index is nil in '" .. self.configFileName .. "'");
							break;
						end;
					end;
					if table.getn(fillPlane.nodes) > 0 then
						if self.defaultFillPlane == nil then
							self.defaultFillPlane = fillPlane;
						end;
						entry.fillPlanes[fillType] = fillPlane;
					end;
					plainI = plainI + 1;
				end;
			end;
		end;
		
		entry.visNodes = {};
		local visI = 0;
		while true do
			local visKey = key .. string.format(".visibility.node(%d)", visI);
			if not hasXMLProperty(xmlFile, visKey) then break; end;
			
			local node = Utils.indexToObject(self.components, getXMLString(xmlFile, visKey .. "#index"));
			if node ~= nil then
				setVisibility(node, false);
				table.insert(entry.visNodes, {node = node});
			end;
			visI = visI + 1;
		end;
		
		local tipI = 0;
		while true do
			local tipKey = key .. string.format(".tipReferencePoint(%d)", tipI);
			if not hasXMLProperty(xmlFile, tipKey) then break; end
	 
			local node = Utils.indexToObject(self.components, getXMLString(xmlFile, tipKey .. "#index"));
			if node ~= nil then
				local tipAnimation = getXMLInt(xmlFile, tipKey .. "#tipAnimation");
				if tipAnimation ~= nil and self.tipAnimations[tipAnimation] ~= nil then
					if entry.tipPoints == nil then 
						entry.tipPoints = {};
						entry.tipAnimations = {};
					end;
					
					local width = Utils.getNoNil(getXMLFloat(xmlFile, tipKey .. "#width"), 0);
					local ref = {};
					ref.node = node;
					ref.width = width;
					
					table.insert(entry.tipPoints, ref);
					table.insert(entry.tipAnimations, self.tipAnimations[tipAnimation]);
				else
					print("Warning: '" .. self.configFileName .. "' has invalid tipAnimation '" .. tipAnimation .. "'.");
				end;
			end
			tipI = tipI + 1;
		end
	
		table.insert(self.bodyTypes, entry);
		i = i + 1;
	end;
	
	self.visBodyParts = {};
	local i = 0;
	while true do
		local key = string.format("vehicle.changeBodyTypes.visibility(%d)", i);
		if not hasXMLProperty(xmlFile, key) then break; end;
				
		local id = getXMLString(xmlFile, key .. "#bodyId");
		if id ~= nil then
			local entry = {};
			entry.ids = {};
			
			local ids = Utils.splitString(" ", id);
			for k, v in pairs(ids) do
				local bodyId = self.bodyTypes[tonumber(v)];
				
				if bodyId ~= nil then
					if entry.ids == nil then
						entry.ids = {};
					end;
					
					entry.ids[tonumber(v)] = true;
				else
					print("Warning: '" .. self.configFileName .. "' has invalid body id '" .. v .. "'.");
				end;
			end;
			
			if entry.ids ~= nil then
				entry.nodes = {};
				local visI = 0;
				while true do
					local visKey = key .. string.format(".node(%d)", visI);
					if not hasXMLProperty(xmlFile, visKey) then break; end;
					
					local node = Utils.indexToObject(self.components, getXMLString(xmlFile, visKey .. "#index"));
					if node ~= nil then
						setVisibility(node, false);
						table.insert(entry.nodes, {node = node});
						visI = visI + 1;
					end;
				end;
				
				table.insert(self.visBodyParts, entry);
				i = i + 1;
			else
				print("Error: '" .. self.configFileName .. "' has no valid body id.");
				break;
			end;
		end;
	end;
	
	self.maxBodyTypes = table.getn(self.bodyTypes);
	self.currentBodyType = 1;
	self.lastBodyType = 1;
	
	self.bodyLimits = {}
	self.bodyLimits.allowChange = true;
	self.bodyLimits.pendingChange = false;
	self.bodyLimits.fillLevelChange = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.changeBodyTypes#fillLevelChange"), true);
	
	self.pe = {};
	self.pe.id = 0;
	self.pe.time = 0;
	
	self.savedFillTypes = self.fillTypes;
	self.savedTipReferencePoints = self.tipReferencePoints;
	self.savedTipAnimations = self.tipAnimations;
	
	if table.getn(self.bodyTypes) > 0 then
		self:setBodyType(self.currentBodyType);
	else
		self.bodyLimits.allowChange = false;
	end;
end;

function changeBodyTypes:delete()
end;

function changeBodyTypes:readStream(streamId, connection)
	self:setBodyTypeId(streamReadInt8(streamId), true);
end;

function changeBodyTypes:writeStream(streamId, connection)
	streamWriteInt8(streamId, self.currentBodyType);
end;

function changeBodyTypes:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	local bodyId = getXMLInt(xmlFile, key .. "#bodyId");
	local fillLevel = getXMLFloat(xmlFile, key .. "#fillLevel");
	if bodyId ~= nil and fillLevel ~= nil then
		self.lastBodyType = bodyId;
		self.currentBodyType = bodyId;
		self:setBodyType(bodyId, fillLevel);
	end;
	
	return BaseMission.VEHICLE_LOAD_OK;
end;
 
function changeBodyTypes:getSaveAttributesAndNodes(nodeIdent)
	local attributes = 'bodyId="' .. self.currentBodyType .. '" ';
	return attributes;
end;

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

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

function changeBodyTypes:update(dt)
	if self:getIsActive() then
		if self:getIsActiveForInput() then
			if self.bodyLimits.allowChange and (self.bodyLimits.fillLevelChange or (not self.bodyLimits.fillLevelChange and self.fillLevel == 0)) then
				local tipWarning = false;
				
				if Input.isKeyPressed(Input.KEY_lshift) and InputBinding.hasEvent(self.cby_input) then
					if self.tipState == nil or (self.tipState ~= nil and self.tipState == Trailer.TIPSTATE_CLOSED) then
						self:setBodyTypeId(self.currentBodyType - 1);
					else
						tipWarning = true;
					end;
				elseif InputBinding.hasEvent(self.cby_input) then
					if self.tipState == nil or (self.tipState ~= nil and self.tipState == Trailer.TIPSTATE_CLOSED) then
						self:setBodyTypeId(self.currentBodyType + 1);
					else
						tipWarning = true;
					end;
				end;
				
				if tipWarning then
					self.pe.id = 1;
					self.pe.time = self.time + 1500;
				end;
			end;
		end;
	end;
	
	if self.currentBodyType ~= self.lastBodyType and not self.bodyLimits.pendingChange then
		self.pe.id = 4;
		self.pe.time = self.time + 1500;
		
		self:setBodyType(self.currentBodyType);
	end;
end;

function changeBodyTypes:updateTick(dt)
	if self:getIsActive() then
		if self.pe.time < self.time and self.pe.id ~= nil then
			self.pe.id = nil;
		end;
	end;
end;

function changeBodyTypes:draw()
	if self.pe.time > self.time then
		if self.pe.id ~= nil and self.pe.id ~= 0 then
			if self.pe.id == 1 then
				if g_i18n:hasText("CBY_TIPPING") then
					g_currentMission:addWarning(g_i18n:getText("CBY_TIPPING"), 0.018, 0.033);
				end;
			elseif self.pe.id == 2 then
				if g_i18n:hasText("CBY_FILLLEVEL") then
					g_currentMission:addWarning(g_i18n:getText("CBY_FILLLEVEL"), 0.018, 0.033);
				end;
			elseif self.pe.id == 3 then
				if g_i18n:hasText("CBY_FILLTYPE") then
					g_currentMission:addWarning(g_i18n:getText("CBY_FILLTYPE"), 0.018, 0.033);
				end;
			elseif self.pe.id == 4 then
				if g_i18n:hasText("CBY_CURRENT_BODY") then
					local name = Utils.getNoNil(self.bodyTypes[self.currentBodyType].name, "");
			
					g_currentMission:addExtraPrintText(string.format(g_i18n:getText("CBY_CURRENT_BODY"), self.currentBodyType, self.maxBodyTypes, name));
				end;
			end;
		end;
	end;
	
	if g_currentMission.showHelpText then
		if g_i18n:hasText("CBY_CHANGE_BODY") and self.bodyLimits.allowChange and (self.bodyLimits.fillLevelChange or (not self.bodyLimits.fillLevelChange and self.fillLevel == 0)) then
			if self.tipState == nil or (self.tipState ~= nil and self.tipState == Trailer.TIPSTATE_CLOSED) then
				g_currentMission:addHelpButtonText(g_i18n:getText("CBY_CHANGE_BODY"), self.cby_input);
			end;
		end;
	end;
end;

function changeBodyTypes:setBodyTypeId(id, noEventSend)
	if id ~= self.currentBodyType then
		if id > self.maxBodyTypes then
			id = 1;
		elseif id <= 0 then
			id = self.maxBodyTypes;
		end;
		
		local stop = false;
		local body = self.bodyTypes[id];
		if body ~= nil then
			if self.fillLevel ~= 0 then
				if self.fillLevel > body.capacity then
					stop = true;
					self.pe.id = 2;
					self.pe.time = self.time + 1500;
				elseif (body.fillplanes ~= nil and body.fillplanes[self.currentFillType] == nil) or (body.fillTypes ~= nil and body.fillTypes[self.currentFillType] == nil) then
					stop = true;
					self.pe.id = 3;
					self.pe.time = self.time + 1500;
				end;
			end;
		else
			stop = true;
		end;
		
		if stop then
			id = self.lastBodyType;
		end;
		
		self.currentBodyType = id;
		
		sendBodyTypeIdEvent.sendEvent(self, id, noEventSend);
	end;
end;

function changeBodyTypes:setBodyType(bodyId, forceFillLevel)
	local body = self.bodyTypes[bodyId];
	forceFillLevel = Utils.getNoNil(forceFillLevel, self.fillLevel);
	
	self.fillPlanes = body.fillPlanes;
	self.capacity = body.capacity;
	
	if body.fillTypes ~= nil then
		self.fillTypes = body.fillTypes;
	else
		if self.fillTypes ~= self.savedFillTypes then
			self.fillTypes = self.savedFillTypes;
		end;
	end;
	
	self:setFillLevel(forceFillLevel, self.currentFillType);
	
	for _, part in ipairs(body.visNodes) do
		setVisibility(part.node, true);
	end;
	
	self:updateBodyParts(bodyId);
	
	if body.tipPoints ~= nil then
		self.tipReferencePoints = body.tipPoints;
		self.tipAnimations = body.tipAnimations;
	else
		if self.tipReferencePoints ~= self.savedTipReferencePoints then
			self.tipReferencePoints = self.savedTipReferencePoints;
		end;
		if self.tipAnimations ~= self.savedTipAnimations then
			self.tipAnimations = self.savedTipAnimations;
		end;
	end;
	
	if self.currentBodyType ~= self.lastBodyType then
		for _, part in ipairs(self.bodyTypes[self.lastBodyType].visNodes) do
			setVisibility(part.node, false);
		end;
		
		self.lastBodyType = self.currentBodyType;
	end;
end;

function changeBodyTypes:updateBodyParts(id)
	for _, v in ipairs(self.visBodyParts) do
		for _, n in ipairs(v.nodes) do
			setVisibility(n.node, v.ids[id] ~= nil);
		end;
	end;
end;

function changeBodyTypes:trailerDelete(superFunc)
	if self.bodyTypes ~= nil then
		if self.isClient then
			for _, tipAnimations in pairs(self.savedTipAnimations) do
				for _, particleSystem in pairs(tipAnimations.dischargeParticleSystems) do
					Utils.deleteParticleSystem(particleSystem);
				end;
			end;
		end;
	 
		if self.hydraulicSound ~= nil then
			delete(self.hydraulicSound);
		end;
		
		if self.fillSound ~= nil then
			delete(self.fillSound);
		end;
	else
		if superFunc ~= nil then
			superFunc(self);
		end;
	end;
end;
Trailer.delete = Utils.overwrittenFunction(Trailer.delete, changeBodyTypes.trailerDelete);

sendBodyTypeIdEvent = {};
sendBodyTypeIdEvent_mt = Class(sendBodyTypeIdEvent, Event);

InitEventClass(sendBodyTypeIdEvent, "sendBodyTypeIdEvent");

function sendBodyTypeIdEvent:emptyNew()
    local self = Event:new(sendBodyTypeIdEvent_mt);
    self.className = "sendBodyTypeIdEvent";
    return self;
end;

function sendBodyTypeIdEvent:new(object, id)
    local self = sendBodyTypeIdEvent:emptyNew()
    self.object = object;
	self.id = id;
    return self;
end;

function sendBodyTypeIdEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.id = streamReadInt8(streamId);
    self.object = networkGetObject(id);
    self:run(connection);
end;

function sendBodyTypeIdEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteInt8(streamId, self.id);
end;

function sendBodyTypeIdEvent:run(connection)
	self.object:setBodyTypeId(self.id, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(sendBodyTypeIdEvent:new(self.object, self.id), nil, connection, self.object);
	end;
end;
function sendBodyTypeIdEvent.sendEvent(object, id, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(sendBodyTypeIdEvent:new(object, id), nil, nil, object);
		else
			g_client:getServerConnection():sendEvent(sendBodyTypeIdEvent:new(object, id));
		end;
	end;
end;