--[[************************************************************************************************************************************************************
    
    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(savegame)
    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(self.xmlFile, baseString.."#node");
        if not hasXMLProperty(self.xmlFile, baseString) then
            break;
        end;
        local part = {};
        
        part.node = Utils.indexToObject(self.components, node);
        part.name = getXMLString(self.xmlFile, baseString.."#name");
        
        local onfoot = getXMLString(self.xmlFile, baseString.."#onfoot");
        if onfoot == "only" or onfoot == "also" then
            part.onfoot = onfoot;
            part.onfootNode = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(self.xmlFile, baseString.."#onfootNode")), part.node);
            part.onfootDistance = Utils.getNoNil(getXMLFloat(self.xmlFile, baseString.."#onfootDistance"), 2.5);
            part.onfootInRange = false;
        else
            part.onfoot = false;
        end;
        
        local rotMin = getXMLString(self.xmlFile, baseString.."#rotMin");
        local rotMax = getXMLString(self.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(self.xmlFile, baseString.."#transMin");
        local transMax = getXMLString(self.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(self.xmlFile, baseString.."#scaleMin");
        local scaleMax = getXMLString(self.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(self.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(self.xmlFile, baseString.."#pitchOffset"), 1);
            part.soundVolume = Utils.getNoNil(getXMLFloat(self.xmlFile, baseString.."#volume"), 1);
            setSamplePitch(part.sound, soundPitchOffset);
            part.soundPlay = false;
        end;
        
        part.help = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#showHelp"), false);
        part.alwaysActive = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#alwaysActive"), false);
        part.visToggle = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#visToggle"), false);
        part.vis = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#defaultVis"), true);
        part.save = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#save"), true);
        part.moveTime = Utils.getNoNil(getXMLFloat(self.xmlFile, baseString.."#moveTime"), 1) * 1000;
        part.autoReturn = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#autoReturn"), false);
        
        local animation = Utils.getNoNil(getXMLBool(self.xmlFile, baseString.."#useAnimation"), false);
        local animSpeed = Utils.getNoNil(getXMLFloat(self.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;