--
-- BuyableTwinWheels
-- Specialization for Buyable Twin Wheels
--
-- @author      Manuel Leithner (SFM-Modding)
-- @version     v3.0
-- @date          30/10/10
-- @history:    v1.0 - Initial version
--                v2.0 - added network support, changed update to updateTick
--                v3.0 - Added dynamic collision support, ls13-ready
--              v4.0 - moved dynamic collision to tractor, fs15-ready
--
-- free for noncommerical-usage
--

BuyableTwinWheels = {};

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

function BuyableTwinWheels:load(savegame)

    self.twinWheelTriggerCallback = BuyableTwinWheels.twinWheelTriggerCallback;
    self.wheelDelete = BuyableTwinWheels.wheelDelete;
    self.assembleWheels = SpecializationUtil.callSpecializationsFunction("assembleWheels");
    self.disassembleWheels = SpecializationUtil.callSpecializationsFunction("disassembleWheels");

    self.checkString = Utils.getNoNil(getXMLString(xmlFile, "vehicle.twinWheels#checkString"), "standart");
    self.usingTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.twinWheels#activationTrigger"));

    addTrigger(self.usingTrigger, "twinWheelTriggerCallback", self);

    self.belts = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.twinWheels#belts"));
    
    self.maxLongStiffnessMultiplier = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.twinWheels#maxLongStiffnessMultiplier"), 1.0);
    self.maxLatStiffnessMultiplier = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.twinWheels#maxLatStiffnessMultiplier"), 1.0);
    self.maxLatStiffnessLoadMultiplier = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.twinWheels#maxLatStiffnessLoadMultiplier"), 1.0);
    self.frictionScaleMultiplier = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.twinWheels#frictionScaleMultiplier"), 1.0);
    
    self.buyableWheels = {};
    local i = 0;
    while true do
        local wheelnamei = string.format("vehicle.twinWheels.wheel(%d)", i);
        local wheel = {};
        local wheelIndex = getXMLInt(xmlFile, wheelnamei .. "#wheelIndex");
        if wheelIndex == nil then
            break;
        end;

        wheel.wheelIndex = wheelIndex;
        wheel.node = Utils.indexToObject(self.components, getXMLString(xmlFile, wheelnamei .. "#node"));

        wheel.savePosition = Utils.indexToObject(self.components, getXMLString(xmlFile, wheelnamei .. "#savePosition"));
        wheel.hasTyreTracks = Utils.getNoNil(getXMLString(xmlFile, wheelnamei .. "#savePosition"), false);
        if wheel.hasTyreTracks and g_currentMission.tyreTrackSystem ~= nil then
            wheel.tyreTrackNode = Utils.indexToObject(self.components, getXMLString(xmlFile, wheelnamei.."#tyreTrackNode"));
            wheel.radius = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei.."#radius"), 0.5);
            local wheelWidth = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#width"), 0.5);
            local tyreTrackAtlasIndex = Utils.getNoNil(getXMLInt(xmlFile, wheelnamei .. "#tyreTrackAtlasIndex"), 0);
            wheel.tyreTrackIndex = g_currentMission.tyreTrackSystem:createTrack(wheelWidth, tyreTrackAtlasIndex);
        end
        
        table.insert(self.buyableWheels, wheel);
        i = i + 1;
    end;

    self.loadedCoords = nil;
    self.twinWheelsUser = nil;
end;

function BuyableTwinWheels:delete()
    if self.twinWheelsUser ~= nil then
        self.twinWheelsUser:onDisassembling(true);
        for _,twinWheel in pairs(self.buyableWheels) do
            delete(twinWheel.node);
            if twinWheel.tyreTrackIndex ~= nil then
                g_currentMission.tyreTrackSystem:destroyTrack(twinWheel.tyreTrackIndex);
            end
        end;
        self.buyableWheels = {};
    end;
    removeTrigger(self.usingTrigger);
end;

function BuyableTwinWheels:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    if id ~= -1 then
        self.twinWheelsUserToLoad = networkGetObject(id);
    end;
end;

function BuyableTwinWheels:writeStream(streamId, connection)
    local idToWrite = -1;
    if self.twinWheelsUser ~= nil then
        idToWrite = networkGetObjectId(self.twinWheelsUser);
    end;
    streamWriteInt32(streamId, idToWrite);
end;

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

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

function BuyableTwinWheels:update(dt)
    if self.twinWheelsUserToLoad  ~= nil then
        self:assembleWheels(self.twinWheelsUserToLoad, true);
        self.twinWheelsUserToLoad  = nil;
    end;
    if self.twinWheelsUser ~= nil and self.twinWheelsUser:getIsActive() then
        for l,twinWheel in pairs(self.buyableWheels) do
            if twinWheel.tyreTrackIndex ~= nil then
                for k,wheel in pairs(self.twinWheelsUser.wheels) do
                    if k == l and wheel.tyreTrackIndex ~= nil then
                        local color = wheel.lastColor;
                        if wheel.dirtAmount > 0 and color ~= nil then
                            local wx,wy,wz = getWorldTranslation(twinWheel.tyreTrackNode);
                            wy = wy - twinWheel.radius;
                            local ux,uy,uz = localDirectionToWorld(self.twinWheelsUser.rootNode, 0,1,0);
                            g_currentMission.tyreTrackSystem:addTrackPoint(twinWheel.tyreTrackIndex, wx,wy,wz, ux,uy,uz, color[1], color[2], color[3], wheel.dirtAmount, color[4], 1);
                        else
                            g_currentMission.tyreTrackSystem:cutTrack(twinWheel.tyreTrackIndex);
                        end;
                    end
                end
            end
        end
    else
        for _,twinWheel in pairs(self.buyableWheels) do
            if twinWheel.tyreTrackIndex ~= nil then
                g_currentMission.tyreTrackSystem:cutTrack(twinWheel.tyreTrackIndex);
            end;
        end;
    end;
end;

function BuyableTwinWheels:updateTick(dt)
    -- try to load saved twinwheels
    if self.loadedCoords ~= nil then
        for k,steerable in pairs(g_currentMission.steerables) do
            local a,b,c = getWorldTranslation(steerable.rootNode);
            local distance = Utils.vector3Length(self.loadedCoords.x-a, self.loadedCoords.y-b, self.loadedCoords.z-c);
            if distance < 0.15 then
                self:assembleWheels(steerable);
                break;
            end;
        end;
        self.loadedCoords = nil;
    end;
end;

function BuyableTwinWheels:draw()
end;

function BuyableTwinWheels:twinWheelTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    local vehicle = g_currentMission.controlledVehicle;
    if vehicle ~= nil then
        if onEnter then
            if vehicle.rootNode == otherId then
                if vehicle.buyableTwinWheels ~= nil then
                    if vehicle.buyableTwinWheels.checkString == self.checkString then
                        if self.twinWheelsUser ~= nil then
                            if self.twinWheelsUser == vehicle then
                                vehicle.buyableTwinWheels.wheelsInRange = self;
                            end;
                        else
                            vehicle.buyableTwinWheels.wheelsInRange = self;
                        end;
                    end;
                end;
            end;
        elseif onLeave then
            if otherId == vehicle.rootNode then
                if vehicle.buyableTwinWheels ~= nil then
                    vehicle.buyableTwinWheels.wheelsInRange = nil;
                end;
            end;
        end;
    end;
end;

function BuyableTwinWheels:assembleWheels(vehicle, noEventSend)
    BuyableTwinWheelsAttachEvent.sendEvent(self, vehicle, noEventSend);

    if self.belts ~= nil then
        setVisibility(self.belts, false);
    end;
    if self.twinWheelsUser == nil then
        if vehicle.buyableTwinWheels ~= nil then
            self.twinWheelsUser = vehicle;
            for k,wheel in pairs(vehicle.wheels) do
                for l,twinWheel in pairs(self.buyableWheels) do
                    if k == l then
                        link(wheel.driveNode, twinWheel.node);
                        setRotation(twinWheel.node, 0,0,0);
                        setTranslation(twinWheel.node, 0,0,0);

                        if self.isServer then
                            if wheel.twinWheelCollisionNode ~= nil and wheel.twinWheelPosition ~= nil then
                                setTranslation(wheel.twinWheelCollisionNode, wheel.twinWheelPosition[1], wheel.twinWheelPosition[2], wheel.twinWheelPosition[3])
                            end;

                            wheel.maxLongStiffnessBackUp = wheel.maxLongStiffness;
                            wheel.maxLatStiffnessBackUp = wheel.maxLatStiffness;
                            wheel.maxLatStiffnessLoadBackUp = wheel.maxLatStiffnessLoad;
                            wheel.frictionScaleBackUp = wheel.frictionScale;

                            wheel.maxLongStiffness = wheel.maxLongStiffness * self.maxLongStiffnessMultiplier;
                            wheel.maxLatStiffness = wheel.maxLatStiffness * self.maxLatStiffnessMultiplier;
                            wheel.maxLatStiffnessLoad = wheel.maxLatStiffnessLoad * self.maxLatStiffnessLoadMultiplier;
                            wheel.frictionScale = wheel.frictionScale * self.frictionScaleMultiplier;
                            
                            setWheelShapeTireFriction(wheel.node, wheel.wheelShape, wheel.maxLongStiffness, wheel.maxLatStiffness, wheel.maxLatStiffnessLoad, wheel.frictionScale*wheel.tireGroundFrictionCoeff);
                        end;
                        break;
                    end;
                end;
            end;
            self.twinWheelsUser:onAssembling(self);
        end;
    end;

end;

function BuyableTwinWheels:disassembleWheels(noEventSend)
    BuyableTwinWheelsDetachEvent.sendEvent(self, noEventSend);

    if self.twinWheelsUser ~= nil then
        self.twinWheelsUser:onDisassembling();
    end;

    if self.belts ~= nil then
        setVisibility(self.belts, true);
    end;

    for k,twinWheel in pairs(self.buyableWheels) do

        link(twinWheel.savePosition, twinWheel.node);
        setRotation(twinWheel.node, 0,0,0);
        setTranslation(twinWheel.node, 0,0,0);
        
        for l,wheel in pairs(self.twinWheelsUser.wheels) do
            if l == k then
                if self.isServer then
                    if wheel.twinWheelCollisionNode ~= nil and wheel.twinWheelDefaultPosition ~= nil then
                        setTranslation(wheel.twinWheelCollisionNode, wheel.twinWheelDefaultPosition[1], wheel.twinWheelDefaultPosition[2], wheel.twinWheelDefaultPosition[3])
                    end;
                    
                    wheel.maxLongStiffness = wheel.maxLongStiffnessBackUp;
                    wheel.maxLatStiffness = wheel.maxLatStiffnessBackUp;
                    wheel.maxLatStiffnessLoad = wheel.maxLatStiffnessLoadBackUp;
                    wheel.frictionScale = wheel.frictionScaleBackUp;                
                    setWheelShapeTireFriction(wheel.node, wheel.wheelShape, wheel.maxLongStiffness, wheel.maxLatStiffness, wheel.maxLatStiffnessLoad, wheel.frictionScale*wheel.tireGroundFrictionCoeff);
                end;
            end
        end;
    end;
    self.twinWheelsUser = nil;
end;


function BuyableTwinWheels:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)

    if not resetVehicles then
        local valueStr = getXMLString(xmlFile, key.."#attacherCoords");
        if valueStr ~= nil then
            local x,y,z = Utils.getVectorFromString(valueStr);
            self.loadedCoords = {x = x,y = y,z = z};
        end;
    end;

    return BaseMission.VEHICLE_LOAD_OK;
end;

function BuyableTwinWheels:getSaveAttributesAndNodes(nodeIdent)
    local attributes = nil;

    if self.twinWheelsUser ~= nil then
        local x,y,z = getWorldTranslation(self.twinWheelsUser.rootNode);
        attributes = 'attacherCoords="'.. x .. " " .. y .. " " .. z ..'"';
    end;

    return attributes, nil;
end;

BuyableTwinWheelsAttachEvent = {};
BuyableTwinWheelsAttachEvent_mt = Class(BuyableTwinWheelsAttachEvent, Event);

InitEventClass(BuyableTwinWheelsAttachEvent, "BuyableTwinWheelsAttachEvent");

function BuyableTwinWheelsAttachEvent:emptyNew()
    local self = Event:new(BuyableTwinWheelsAttachEvent_mt);
    return self;
end;

function BuyableTwinWheelsAttachEvent:new(vehicle, attacherVehicle)
    local self = BuyableTwinWheelsAttachEvent:emptyNew()
    self.vehicle = vehicle;
    self.attacherVehicle = attacherVehicle;
    return self;
end;

function BuyableTwinWheelsAttachEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    local attacherId = streamReadInt32(streamId);
    self.attacherVehicle = networkGetObject(attacherId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function BuyableTwinWheelsAttachEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
    streamWriteInt32(streamId, networkGetObjectId(self.attacherVehicle));
end;

function BuyableTwinWheelsAttachEvent:run(connection)
    self.vehicle:assembleWheels(self.attacherVehicle, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(BuyableTwinWheelsAttachEvent:new(self.vehicle, self.attacherVehicle), nil, connection, self.object);
    end;
end;


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


BuyableTwinWheelsDetachEvent = {};
BuyableTwinWheelsDetachEvent_mt = Class(BuyableTwinWheelsDetachEvent, Event);

InitEventClass(BuyableTwinWheelsDetachEvent, "BuyableTwinWheelsDetachEvent");

function BuyableTwinWheelsDetachEvent:emptyNew()
    local self = Event:new(BuyableTwinWheelsDetachEvent_mt);
    return self;
end;

function BuyableTwinWheelsDetachEvent:new(vehicle)
    local self = BuyableTwinWheelsDetachEvent:emptyNew()
    self.vehicle = vehicle;
    return self;
end;

function BuyableTwinWheelsDetachEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function BuyableTwinWheelsDetachEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
end;

function BuyableTwinWheelsDetachEvent:run(connection)
    self.vehicle:disassembleWheels(true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(BuyableTwinWheelsDetachEvent:new(self.vehicle), nil, connection, self.object);
    end;
end;


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


