--[[************************************************************************************************************************************************************
	
	universalToggleParts v1.2
	
	09/2013 by Saty / http://forum.lsczech.cz
	
****************************************************************************************************************************************************************
	
	(EN) INFO
	
	Free for use on mods. Modifications only with my permission !
	
****************************************************************************************************************************************************************
	
	(CZ) INFO
	
	Pro volne pouziti v modech. ZAKAZ modifikaci bez meho souhlasu !
	
	--------------------------------------------------------------------------------------------------------------------------------------------------------	
	
	Zapis do XML souboru stroje :
	*****************************
	
	<universalToggleParts>
		<part node="1" name="P1" showHelp="true" rotMin="0 0 0" rotMax="0 0 90" moveTime="1" />
		<part node="2" name="P2" showHelp="false" rotMin="0 0 0" rotMax="0 0 90" moveTime="1.5" autoReturn="true" />
		<part node="3" name="P3" showHelp="false" transMin="0 0 0" transMax="0 0.5 0" moveTime="0.5" save="false" />
		<part node="4" name="P4" showHelp="true" transMin="0 0 0" transMax="0 0.5 0" moveTime="0.5" onfoot="only" onfootNode="4|1" onfootDistance="1" />
		<part node="5" name="P5" showHelp="true" scaleMin="0.5 1 1" scaleMax="1 1 1" moveTime="2" alwaysActive="true" />
		<part node="6" name="P6" showHelp="false" scaleMin="0.5 1 1" scaleMax="1 1 1" moveTime="2" soundFile="Sounds/pipe.wav" pitchOffset="0.7" volume="1" />
		<part node="7" name="P7" showHelp="true" visToggle="true" defaultVis="false" />
		<part node="8" name="P7" showHelp="true" useAnimation="true" animSpeed="1"/>
	</universalToggleParts>
	
	Vysvetlivky k funkcim:
	
	part			- jednotlive casti (neomezeny pocet)
	node			- index ovladane casti v GE
	name			- jmeno, ktere musi souhlasit s nazvem klavesy, pripadne napovedy v souboru modDesc.xml
	showHelp		- (true/false) zobrazeni napovedy pres F1 (pri nezadani defaultne hodnota "false")
	rotMin/rotMax		- min/max rotace ve 3 osach
	transMin/transMax	- min/max posun ve 3 osach
	scaleMin/scaleMax	- min/max meritko ve 3 osach  
	moveTime		- cas v sekundach, ktery je potreba na provedeni zmeny min/max
	autoReturn		- automaticke vraceni do min polohy (reaguje na drzeni klavesy)
	useAnimation		- pouzije pro pohyb animaci zadanou v animations pod stejnym nazvem jako je NAME (nutna specializace animatedVehicle)
	animSpeed		- rychlost prehravani animace (pri nezadani defaultne hodnota "1" = 1:1; vyssi cislo = vyssi rychlost)
	visToggle		- (true/false) funkce prepinani viditelnosti casti (pri nezadani defaultne hodnota "false")
	defaultVis		- (true/false) hodnota viditelnosti casti pri koupeni stroje (pri nezadani defaultne hodnota "true")
	save			- (true/false) ukladani pozice/viditelnosti casti pri ulozeni hry (pri nezadani defaultne hodnota "true")
	alwaysActive		- (true/false) pri hodnote "true" funguje i pri prepnuti na zarizeni (pri nezadani defaultne hodnota "false")
	soundFile		- zvuk prehravany pri zmene polohy nebo meritka casti
	pitchOffset		- rychlost prehravani zvuku (pri nezadani defaultne hodnota "1" = 1:1)
	volume			- hlasitost zvuku (pri nezadani defaultne hodnota "1" = 1:1)
	onfoot			- (false/also/only) moznost provadeni akce mimo vozidlo [nelze/z vozu i zvenci/pouze zvenci] (pri nezadani defaultne hodnota "false")
	onfootNode		- index u ktereho je mozno provadet akci zvenci (pri nezadani defaultne hodnota "node")
	onfootDistance		- polomer kruznice od bodu onfootNode, ktera urcuje moznost akce zvenci (pri nezadani defaultne hodnota "2.5")
	
	( Hodnoty rot/trans/scale lze navzajem kombinovat u jedne akce. Funkce "save" neni nikdy aktivni s funkci "autoReturn". )
	( Funkce useAnimation vyradi rot/trans/scale + moveTime. )
	
	--------------------------------------------------------------------------------------------------------------------------------------------------------	
	
	Zapis do souboru modDesc.xml :
	******************************
	
	vsadit do <specializations> :
		<specialization name="universalToggleParts" className="universalToggleParts" filename="universalToggleParts.lua"/> 
	
	vsadit do <vehicleTypes> :
		<specialization name="universalToggleParts"/>
	
	vsadit do <l10n> :
		<text name="P1"> <en>Action 1</en> <cz>Akce 1</cz> />
		( Hodnoty "name" se musi shodovat s udaji v XML stroje! Pokud je v XML stroje u dane casti showHelp="false", napovedu nezadavame. )
	
	vsadit do <inputBindings> :
		<input name="P1" key1="KEY_5" button="" />
		<input name="P2" key1="KEY_6" button="" />
		<input name="P3" key1="KEY_7" button="" />
		( Vsechny InputBindings musi byt definovany a "name" se musi shodovat s udaji v XML stroje! )
	
************************************************************************************************************************************************************]]--

universalToggleParts = {};

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

function universalToggleParts:load(xmlFile)
	self.toggleVisibility = SpecializationUtil.callSpecializationsFunction("toggleVisibility");
	self.togglePart = SpecializationUtil.callSpecializationsFunction("togglePart");
	
	self.parts = {};
	local i = 0;
	while true do
		local baseString = string.format("vehicle.universalToggleParts.part(%d)", i);
		local node = getXMLString(xmlFile, baseString.."#node");
		if not hasXMLProperty(xmlFile, baseString) then
			break;
		end;
		local part = {};
		
		part.node = Utils.indexToObject(self.components, node);
		part.name = getXMLString(xmlFile, baseString.."#name");
		
		local onfoot = getXMLString(xmlFile, baseString.."#onfoot");
		if onfoot == "only" or onfoot == "also" then
			part.onfoot = onfoot;
			part.onfootNode = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, baseString.."#onfootNode")), part.node);
			part.onfootDistance = Utils.getNoNil(getXMLFloat(xmlFile, baseString.."#onfootDistance"), 2.5);
			part.onfootInRange = false;
		else
			part.onfoot = false;
		end;
		
		local rotMin = getXMLString(xmlFile, baseString.."#rotMin");
		local rotMax = getXMLString(xmlFile, baseString.."#rotMax");
		if rotMin ~= nil and rotMax ~= nil then
			local x,y,z = Utils.getVectorFromString(rotMin);
			part.rotMin = {math.rad(Utils.getNoNil(x,0)),math.rad(Utils.getNoNil(y,0)),math.rad(Utils.getNoNil(z,0))};
			local x,y,z = Utils.getVectorFromString(rotMax);
			part.rotMax = {math.rad(Utils.getNoNil(x,0)),math.rad(Utils.getNoNil(y,0)),math.rad(Utils.getNoNil(z,0))};
		end;
		
		local transMin = getXMLString(xmlFile, baseString.."#transMin");
		local transMax = getXMLString(xmlFile, baseString.."#transMax");
		if transMin ~= nil and transMax ~= nil then
			local x,y,z = Utils.getVectorFromString(transMin);
			part.transMin = {Utils.getNoNil(x,0),Utils.getNoNil(y,0),Utils.getNoNil(z,0)};
			local x,y,z = Utils.getVectorFromString(transMax);
			part.transMax = {Utils.getNoNil(x,0),Utils.getNoNil(y,0),Utils.getNoNil(z,0)};
		end;
		
		local scaleMin = getXMLString(xmlFile, baseString.."#scaleMin");
		local scaleMax = getXMLString(xmlFile, baseString.."#scaleMax");
		if scaleMin ~= nil and scaleMax ~= nil then
			local x,y,z = Utils.getVectorFromString(scaleMin);
			part.scaleMin = {Utils.getNoNil(x,1),Utils.getNoNil(y,1),Utils.getNoNil(z,1)};
			local x,y,z = Utils.getVectorFromString(scaleMax);
			part.scaleMax = {Utils.getNoNil(x,1),Utils.getNoNil(y,1),Utils.getNoNil(z,1)};
		end;
		
		local sound = getXMLString(xmlFile, baseString.."#soundFile");
		if sound ~= nil and sound ~= "" then
			sound = Utils.getFilename(sound, self.baseDirectory);
			part.sound = createSample("#partSound");
			loadSample(part.sound, sound, false);
			local soundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, baseString.."#pitchOffset"), 1);
			part.soundVolume = Utils.getNoNil(getXMLFloat(xmlFile, baseString.."#volume"), 1);
			setSamplePitch(part.sound, soundPitchOffset);
			part.soundPlay = false;
		end;
		
		part.help = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#showHelp"), false);
		part.alwaysActive = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#alwaysActive"), false);
		part.visToggle = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#visToggle"), false);
		part.vis = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#defaultVis"), true);
		part.save = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#save"), true);
		part.moveTime = Utils.getNoNil(getXMLFloat(xmlFile, baseString.."#moveTime"), 1) * 1000;
		part.autoReturn = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#autoReturn"), false);
		
		local animation = Utils.getNoNil(getXMLBool(xmlFile, baseString.."#useAnimation"), false);
		local animSpeed = Utils.getNoNil(getXMLFloat(xmlFile, baseString.."#animSpeed"), 1);
		if animation and self.playAnimation ~= nil and self.animations ~= nil then
			if self.animations[part.name] ~= nil then
				part.animation = part.name;
				part.animSpeed = animSpeed;
				part.playAnim = false;
				part.rotMin = nil;
				part.rotMax = nil;
				part.transMin = nil;
				part.transMax = nil;
				part.scaleMin = nil;
				part.scaleMax = nil;
			end;
		end;
		
		if part.autoReturn then
			part.save = false;
		end;
		
		part.toMax = false;
		part.move = false;
		
		table.insert(self.parts, part);
		i = i + 1;
	end;
end;

function universalToggleParts:getSaveAttributesAndNodes(nodeIdent)
	local nodes = "";
	local partNum = 0;
	for i=1, table.getn(self.parts) do
		local part = self.parts[i];
		if partNum > 0 then
			nodes = nodes.."\n";
		end;
		nodes = nodes..nodeIdent..'<universalToggleParts  partVisible="' .. tostring(part.vis) .. '" partMax="'..tostring(part.toMax)..'"' .. ' />';
		partNum = partNum + 1;
	end;
	
	return nil,nodes;
end;

function universalToggleParts:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	if not resetVehicles then
		for nr, part in ipairs(self.parts) do
			local partKey = key..string.format(".universalToggleParts(%d)", nr - 1);
			local vis = Utils.getNoNil(getXMLBool(xmlFile, partKey.."#partVisible"), part.vis);
			local toMax = Utils.getNoNil(getXMLBool(xmlFile, partKey.."#partMax"), part.toMax);
			if part.save then
				self:toggleVisibility(nr, vis);
				if not part.autoReturn then
					self:togglePart(nr, toMax); 
				end;
			end;
		end;
	end;
	
	return BaseMission.VEHICLE_LOAD_OK;
end;

function universalToggleParts:update(dt)
	for nr, part in ipairs(self.parts) do
		if self:getIsActiveForInput() or (part.alwaysActive and self.isClient and self.isEntered and not self:hasInputConflictWithSelection()) then
			if part.onfoot ~= "only" then
				if part.visToggle then
					if InputBinding.hasEvent(InputBinding[part.name]) then
						self:toggleVisibility(nr, not part.vis); 
					end;
				else
					if not part.autoReturn then
						if InputBinding.hasEvent(InputBinding[part.name]) then
							self:togglePart(nr, not part.toMax); 
						end;
					else
						if InputBinding.isPressed(InputBinding[part.name]) then
							if not part.toMax then
								self:togglePart(nr, true); 
							end;
						else
							if part.toMax then
								self:togglePart(nr, false); 
							end;
						end;
					end;
				end;
			end;
		end;
		
		if g_currentMission.player ~= nil then
			if part.onfootInRange then
				if part.help then
					g_currentMission:addHelpButtonText(g_i18n:getText(part.name), InputBinding[part.name]);
				end;
				if part.visToggle then
					if InputBinding.hasEvent(InputBinding[part.name]) then
						self:toggleVisibility(nr, not part.vis); 
					end;
				else
					if not part.autoReturn then
						if InputBinding.hasEvent(InputBinding[part.name]) then
							self:togglePart(nr,not part.toMax); 
						end;
					else
						if InputBinding.isPressed(InputBinding[part.name]) then
							if not part.toMax then
								self:togglePart(nr, true); 
							end;
						else
							if part.toMax then
								self:togglePart(nr, false); 
							end;
						end;
					end;
				end;
			end;
		end;
		
		setVisibility(part.node, part.vis);
		
		if part.move then
			part.move = false;
			
			--[[if self.movingParts ~= nil then
				for _, part in pairs(self.movingParts) do
					Cylindered.updateMovingPart(self, part);
				end;
			end;]]
			
			if part.animation ~= nil then
				if part.toMax then
					self:playAnimation(part.animation, part.animSpeed, nil, true);
				end;
				if not part.toMax then
					self:playAnimation(part.animation, - part.animSpeed, nil, true);
				end;
			end;
			
			if part.rotMin ~= nil then
				local curRot = {getRotation(part.node)};
				local newRot = Utils.getMovedLimitedValues(curRot, part.rotMax, part.rotMin, 3, part.moveTime, dt, not part.toMax);
				setRotation(part.node, unpack(newRot));
				for i=1,3 do
					if math.abs(newRot[i]-curRot[i]) > 0.001 then
						part.move = true;
					end;
				end;
			end;
			
			if part.transMin ~= nil then
				local curTrans = {getTranslation(part.node)};
				local newTrans = Utils.getMovedLimitedValues(curTrans, part.transMax, part.transMin, 3, part.moveTime, dt, not part.toMax);
				setTranslation(part.node, unpack(newTrans));
				for i=1,3 do
					if math.abs(newTrans[i]-curTrans[i]) > 0.001 then
						part.move = true;
					end;
				end;
			end;
			
			if part.scaleMin ~= nil then
				local curScale = {getScale(part.node)};
				local newScale = Utils.getMovedLimitedValues(curScale, part.scaleMax, part.scaleMin, 3, part.moveTime, dt, not part.toMax);
				setScale(part.node, unpack(newScale));
				for i=1,3 do
					if math.abs(newScale[i]-curScale[i]) > 0.001 then
						part.move = true;
					end;
				end;
			end;
			
			if part.sound ~= nil then
				if self.isClient then
					if part.move then
						if self:getIsActiveForSound() and not part.soundPlay then
							playSample(part.sound, 0, part.soundVolume, 0);
							part.soundPlay = true;
						end;
					else
						stopSample(part.sound);
						part.soundPlay = false;
					end;
				end;
			end;
		end;
	end;
end;

function universalToggleParts:updateTick(dt)
	if g_currentMission.player ~= nil then
		for _, part in ipairs(self.parts) do
			if part.onfoot ~= false then
				if g_currentMission.player.rootNode ~= nil then
					local vx, vy, vz = getWorldTranslation(g_currentMission.player.rootNode);
					local sx, sy, sz = getWorldTranslation(part.onfootNode); 
					local distance = Utils.vector3Length(sx-vx, sy-vy, sz-vz);
					if distance < part.onfootDistance then
						part.onfootInRange = true; 
					else
						part.onfootInRange = false; 
					end;
				end;
			end;
		end;
	end;
end;

function universalToggleParts:draw()
	for _, part in ipairs(self.parts) do
		if self:getIsActiveForInput() or (part.alwaysActive and self.isClient and self.isEntered) then
			if part.help and part.onfoot ~= "only" then
				g_currentMission:addHelpButtonText(g_i18n:getText(part.name), InputBinding[part.name]);
			end;
		end;
	end;
end;

function universalToggleParts:toggleVisibility(name, state, noEventSend)
	toggleVisEvent.sendEvent(self, name, state, noEventSend);
	self.parts[name].vis = state;
end;

function universalToggleParts:togglePart(name, state, noEventSend)
	togglePartEvent.sendEvent(self, name, state, noEventSend);
	self.parts[name].toMax = state;
	self.parts[name].move = true;
end;

function universalToggleParts:readStream(streamId, connection)
	for nr, part in ipairs(self.parts) do
		local state1 = streamReadBool(streamId);
		local state2 = streamReadBool(streamId);
		self:toggleVisibility(nr, state1, true);
		self:togglePart(nr, state2, true);
	end;
end;
	
function universalToggleParts:writeStream(streamId, connection)
	for nr, part in ipairs(self.parts) do
		streamWriteBool(streamId, part.vis);
		streamWriteBool(streamId, part.toMax);
	end;
end;

function universalToggleParts:delete()
	for nr, part in ipairs(self.parts) do
		if part.sound ~= nil then
			delete(part.sound);
		end;
	end;
end;

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

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



toggleVisEvent = {};
toggleVisEvent_mt = Class(toggleVisEvent, Event);
InitEventClass(toggleVisEvent, "toggleVisEvent");

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

function toggleVisEvent:new(object, name, state)
	local self = toggleVisEvent:emptyNew()
	self.object = object;
	self.name = name;
	self.state = state;
	return self;
end;

function toggleVisEvent:readStream(streamId, connection)
	self.object = networkGetObject(streamReadInt32(streamId));
	self.name  = streamReadInt8(streamId);
	self.state = streamReadBool(streamId);
	self:run(connection);
end;

function toggleVisEvent:writeStream(streamId, connection)
		streamWriteInt32(streamId, networkGetObjectId(self.object));
		streamWriteInt8(streamId, self.name);
		streamWriteBool(streamId, self.state);
end;

function toggleVisEvent:run(connection)
	self.object:toggleVisibility(self.name, self.state, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(toggleVisEvent:new(self.object, self.name, self.state), nil, connection, self.object);
	end;
end;

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

togglePartEvent = {};
togglePartEvent_mt = Class(togglePartEvent, Event);
InitEventClass(togglePartEvent, "togglePartEvent");

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

function togglePartEvent:new(object, name, state)
	local self = togglePartEvent:emptyNew()
	self.object = object;
	self.name = name;
	self.state = state;
	return self;
end;

function togglePartEvent:readStream(streamId, connection)
	self.object = networkGetObject(streamReadInt32(streamId));
	self.name  = streamReadInt8(streamId);
	self.state = streamReadBool(streamId);
	self:run(connection);
end;

function togglePartEvent:writeStream(streamId, connection)
	streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteInt8(streamId, self.name);
	streamWriteBool(streamId, self.state);
end;

function togglePartEvent:run(connection)
	self.object:togglePart(self.name, self.state, true);
	if not connection:getIsServer() then
		g_server:broadcastEvent(togglePartEvent:new(self.object, self.name, self.state), nil, connection, self.object);
	end;
end;

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