require "SoundFX/SoundFX"

RadioWavs = {}

RadioWavs.soundCache = {}

RadioWavs.files = {}
-- Example
-- RadioWavs.files[1] = "ZenitramJrMuricanaNumber"
-- add RadioWavs.files here
--BEGIN RadioWavs
RadioWavs.files[1] = "aece616a-4883-4e09-bd35-39aed89fe655"
RadioWavs.files[2] = "4e3d8faf-8e30-4f80-9eac-a7fcb32b3900"
RadioWavs.files[3] = "0f2c6233-c94b-4cfc-933a-64a20d41c803"
RadioWavs.files[4] = "87fd2783-9fe4-4605-becf-5875a5616f28"
RadioWavs.files[5] = "42301cfa-ead1-4c3f-81db-b7369279aa4c"
RadioWavs.files[6] = "a24b3295-6241-401c-a7d2-0c1bf0bba426"
RadioWavs.files[7] = "c8da05a5-8df9-4969-af46-65f415a0d321"
RadioWavs.files[8] = "20965910-b900-456e-af67-d917b846f112"
RadioWavs.files[9] = "29f94658-1439-4dce-b764-828960ae641a"
RadioWavs.files[10] = "ad968fc8-cdfb-4c50-9341-45d3c6e687e1"
RadioWavs.files[11] = "e7f7c2e2-ec25-4098-99be-f9f423a2e79a"
RadioWavs.files[12] = "30ef07c1-2690-4e51-b421-a70ea3edf322"
RadioWavs.files[13] = "75d511c5-cb3f-41cc-81c9-b47df52957ae"
RadioWavs.files[14] = "d969e014-5392-469d-9981-1e93d12ccfac"
RadioWavs.files[15] = "acfa95f5-420e-42f0-83e2-2e28c3624390"
RadioWavs.files[16] = "48af015e-a0fa-4d24-b0fd-006d107d9069"
RadioWavs.files[17] = "18dce784-0448-4b0e-8cba-18d16d2b6601"
RadioWavs.files[18] = "f6a29037-e70a-4745-8efc-0a58e4b086e9"
RadioWavs.files[19] = "35a2653b-10db-423a-8dfa-fb0e31a01200"
RadioWavs.files[20] = "8d8676b0-4498-47ad-830a-01c584b46eb7"
RadioWavs.files[21] = "f1b5054b-6152-418f-993c-ad477bdbd0ce"
RadioWavs.files[22] = "6c8629da-1997-4087-ad9d-f4908e7a8b39"
RadioWavs.files[23] = "cae4e722-4ef4-4553-b926-542341ad9192"
RadioWavs.files[24] = "3166434a-ce11-4df5-acb6-4a023bb45c67"
RadioWavs.files[25] = "1165aff4-8707-47df-b4b9-80e168c77fb9"
RadioWavs.files[26] = "40dacfdc-3473-40e8-8276-eb5faf42dea3"
RadioWavs.files[27] = "28f2ff4b-fd62-4176-af0e-766fd2cea3ff"
RadioWavs.files[28] = "c20ef080-6050-4a1c-8428-277f61010dcf"
RadioWavs.files[29] = "7a1469c5-0f2f-48e4-b8e6-df2ead207b53"
RadioWavs.files[30] = "3cc3f408-8a6e-49be-a46a-cb360026de7b"
RadioWavs.files[31] = "a843a3ea-0635-4f54-9425-91e22f3c2aa4"
RadioWavs.files[32] = "884f99cd-fd75-4448-9108-b0d13d20d7a8"
RadioWavs.files[33] = "97964ab8-6445-4172-980a-5d5517a6fc26"
RadioWavs.files[34] = "06070bf7-a1d2-48ac-b6cb-c53175e562cc"
RadioWavs.files[35] = "4fd49029-1b17-43ee-b699-1de993206620"
RadioWavs.files[36] = "1953b137-c59b-48c7-91c0-aca3d320046b"
RadioWavs.files[37] = "5cd792f6-327a-4c0d-b595-0a5b6331d5c0"
RadioWavs.files[38] = "bbfd53a8-fc33-4caf-80d5-4476d2ddf0e9"
RadioWavs.files[39] = "1d05c1d5-42a4-4593-a5fa-7be3158e2f7f"
RadioWavs.files[40] = "81bd5859-e72b-4901-8d41-3b615ca74b80"
RadioWavs.files[41] = "06fbc13c-6446-46c8-91f5-cc6c3abf8a68"
RadioWavs.files[42] = "4990dd94-1086-45c8-b426-451e621b32ea"
RadioWavs.files[43] = "e04fcd48-f530-4a57-b921-69a05b7297ca"
RadioWavs.files[44] = "00616d98-0408-42d0-b1a8-cb44e8545ffc"
RadioWavs.files[45] = "5bf377c8-e066-4b72-95d0-6c483194ed3f"
RadioWavs.files[46] = "a4921c6f-987d-4e4c-934e-da88112e27ac"
RadioWavs.files[47] = "e733a163-d649-4556-a47f-0054e16f705e"
RadioWavs.files[48] = "216423cf-fa74-4508-b2f8-40e371ee8d0e"
RadioWavs.files[49] = "4c064253-1c7d-4393-b006-f7d2c9b4710f"
RadioWavs.files[50] = "0d37c65e-1dff-409e-9a49-16623c2d0a90"
RadioWavs.files[51] = "85a27962-73ec-4891-a28d-e92bfa2c3b7b"
RadioWavs.files[52] = "8c903462-c961-4b82-ab80-ed7fc0a121ab"
RadioWavs.files[53] = "e010f645-ddaa-4e9e-9614-d6ff1648ccba"
RadioWavs.files[54] = "942c7659-1ed4-415c-81ab-28ac4a0a9653"
RadioWavs.files[55] = "d3bba5c3-e656-4abf-95bd-5d8afda5b3ee"
RadioWavs.files[56] = "f4f383e5-c0fc-455a-9c61-7886c15dddcf"
RadioWavs.files[57] = "b18304ac-1658-4714-a25c-02ba380f5464"
RadioWavs.files[58] = "69d8e6bd-a8ed-4167-b609-fb25df1ee49f"
RadioWavs.files[59] = "426586ab-3699-4f45-97b9-6088686a4f06"
RadioWavs.files[60] = "08755a2d-6649-40e0-a18d-e920e0c1834b"
RadioWavs.files[61] = "187d9478-8afb-4c1c-a5f5-d26479731333"
RadioWavs.files[62] = "59cc5e7c-ec90-4a7a-8842-3dbe6533b606"
RadioWavs.files[63] = "5b4670fa-6846-4218-a20d-b610a2fed121"
RadioWavs.files[64] = "7b5dfe97-78d3-4f8e-9344-762713069913"
RadioWavs.files[65] = "02c01b40-92c0-439d-8c20-291472d24120"
RadioWavs.files[66] = "778513f9-eb09-4095-87e7-9f74ec385e38"
--END RadioWavs
RadioWavs.cacheSize = 10			-- number of devices to keep in cache

------------------------------------------------------------------------------------
-- PLAY NEW SONG
------------------------------------------------------------------------------------


function RadioWavs.PlaySound(number, device)
    if not RadioWavs.files[number] then
        print("RadioWavs mod ERROR: sound number "..number.." not found!")
        return
    end

    local deviceData = device:getDeviceData()
    local sound = nil
    local t = RadioWavs.getData(deviceData)
    if t then
        sound = t.sound
    else
        sound = SoundFX:new()
    end

    if deviceData:isInventoryDevice() then
        sound:set3D(false)
        sound:setVolumeModifier(0.4)
    elseif deviceData:isIsoDevice() then
        sound:setPosAtObject(device)
        sound:setVolumeModifier(0.4)
    elseif deviceData:isVehicleDevice() then
        local vehiclePart = deviceData:getParent()
        if vehiclePart then
            local vehicle = vehiclePart:getVehicle()
            if vehicle then
                sound:setEmitter( vehicle:getEmitter() ) -- use car's emitter, car radios don't have one
                if vehicle == getPlayer():getVehicle() then -- player is in the car
                    sound:set3D(false)
                    sound:setVolumeModifier(0.4)
                else
                    sound:set3D(true)
                    sound:setVolumeModifier(0.1)
                end
            end
        end
    end

    sound:setVolume( deviceData:getDeviceVolume() )
    sound:play( RadioWavs.files[number] )


    t = t or {}
    t.device = device
    t.deviceData = deviceData
    t.channel = deviceData:getChannel()
    t.sound = sound

    tickCounter2 = 200

    table.insert( RadioWavs.soundCache, 1, t )
    if #RadioWavs.soundCache>RadioWavs.cacheSize then
        for i=RadioWavs.cacheSize+1,#RadioWavs.soundCache do
            table.remove(RadioWavs.soundCache,i)
        end
    end

    return t
end


function RadioWavs.split(str,sep)
    local sep, fields = sep or ":", {}
    local pattern = string.format("([^%s]+)", sep)
    str:gsub(pattern, function(c) fields[#fields+1] = c end)
    return fields
end

--rekkie -- original code -- s
--[[
function RadioWavs.OnDeviceText(_interactCodes, _x, _y, _z, _line, _source)
	local codes = RadioWavs.split(_interactCodes, ",")
    local rData = _source:getDeviceData()
    for _,_v in ipairs(codes) do
        if _v:len() > 4 then
            local code = string.sub(_v, 1, 3)
            local op = string.sub(_v, 4, 4)
            local amount = tonumber(string.sub(_v, 5, _v:len()))
            if op=="-" then
            	amount = -amount
        	end
            if amount ~= nil and amount > 0 and code=="DRU" then
                RadioWavs.PlaySound(amount, _source)
            end
        end
    end
end
Events.OnDeviceText.Add( RadioWavs.OnDeviceText )
--]]
--rekkie -- original code -- e

--rekkie -- new code code -- s
function RadioWavs.OnDeviceText( _guid, _interactCodes, _x, _y, _z, _line )
    local radio = nil;

    -- HOTFIX: Check game version
    --      > If using a version prior to 41.69, then use the old function parameters
    --      > This prevents errors when using stable versus beta branch
    local gameVersion = tonumber(tostring(getCore():getGameVersion()))
    local minVersion = 41.69
    if gameVersion <= minVersion then
        local swap1 = _guid
        local swap2 = _interactCodes
        local swap3 = _x
        local swap4 = _y
        local swap5 = _z
        local swap6 = _line
        _interactCodes = swap1
        _x = swap2
        _y = swap3
        _z = swap4
        _line = swap5
        radio = swap6
    end

    local codes = RadioWavs.split(_interactCodes, ",")
    
    -- Radio Device: Walkie Talkie
    if radio == nil then
        if (_x==-1 and _y==-1 and _z==-1) then
            local player = getSpecificPlayer(0)
            if player and player:isDead() then player = nil end
            if player ~=nil then
                local items = player:getInventory():getItems();
                for i = 0, items:size()-1 do
                    local item = items:get(i);
                    if instanceof(item, "Radio") and item:getDeviceData() ~= nil then
                        radio = item
                    end
                end
            end
        end
    end

    -- Radio Device: Portable/HAM radio, television, or vehicle radio
    if radio == nil then
        local sq = getCell():getGridSquare(_x, _y, _z);
        for i=0,sq:getObjects():size() - 1 do
            local item = sq:getObjects():get(i);
            -- Portable/HAM radio or television
            if instanceof(item, "IsoRadio") or instanceof(item, "IsoTelevision") and item:getDeviceData() ~= nil then
                radio = item
                break
            end

            -- Vehicle radio
            if instanceof(item, "IsoObject") then
                local vehicle = sq:getVehicleContainer()
                if vehicle then
                    local part = vehicle:getPartById("Radio");
                    if part and part:getDeviceData() then
                        radio = part
                        break
                    end
                end
            end
        end
    end

    -- If device not found (hopefully this never happens) exit out with error notice
    if radio == nil then
        print("RadioWavs mod ERROR: OnDeviceText() could not find radio device")
        return
    end
    --Where magic happens and audio is played based on codes, a tag that i have to add to the RadioData.xml lines
    for _,_v in ipairs(codes) do
        if _v:len() > 4 then
            local code = string.sub(_v, 1, 3)
            local op = string.sub(_v, 4, 4)
            local amount = tonumber(string.sub(_v, 5, _v:len()))
            if op=="-" then
                amount = -amount
            end
            if amount ~= nil and amount > 0 and code=="DRU" then
                RadioWavs.PlaySound(amount, radio)
            end
        end
    end
end
Events.OnDeviceText.Add( RadioWavs.OnDeviceText )
--rekkie -- new code code -- e

------------------------------------------------------------------------------------
-- INTERACTION OVERRIDES
------------------------------------------------------------------------------------


function RadioWavs.AddOverrides()

    local ISRadioAction_performToggleOnOff = ISRadioAction.performToggleOnOff
    function ISRadioAction:performToggleOnOff()
        ISRadioAction_performToggleOnOff(self)
        local t = RadioWavs.getData(self.deviceData)
        if t then
            if t.deviceData:getIsTurnedOn() and t.deviceData:getChannel() == t.channel then
                t.muted = false
            else
                t.muted = true -- mute sound instead of stopping it, so we can turn it back on
            end
            RadioWavs.updateVolume(t)
        end
    end
    
    local ISRadioAction_performSetChannel = ISRadioAction.performSetChannel
    function ISRadioAction:performSetChannel()
        local t = RadioWavs.getData(self.deviceData)
        if t then
            if t.channel == self.secondaryItem then -- switching back to saved channel
                t.muted = false
            else
                t.muted = true -- mute sound instead of stopping it, so we can switch back to the channel
            end
            RadioWavs.updateVolume(t)
        end
        ISRadioAction_performSetChannel(self)
    end
    
    local ISRadioAction_performSetVolume = ISRadioAction.performSetVolume
    function ISRadioAction:performSetVolume()
        if self:isValidSetVolume() then
            ISRadioAction_performSetVolume(self)
            local t = RadioWavs.getData(self.deviceData)
            if t then
                RadioWavs.updateVolume(t)
            end
        end
    end


    local ISEnterVehicle_perform = ISEnterVehicle.perform
    function ISEnterVehicle:perform()
        ISEnterVehicle_perform(self)
        local t = RadioWavs.getEmitter( self.character:getVehicle():getEmitter() )
        if t then
            t.sound:setVolumeModifier(0.4)
            t.sound:set3D(false) -- no 3d sound while in car, it sounds glitchy
            RadioWavs.updateVolume(t)
        end
    end
    
    local ISExitVehicle_perform = ISExitVehicle.perform
    function ISExitVehicle:perform()
        local t = RadioWavs.getEmitter( self.character:getVehicle():getEmitter() )
        if t then
            t.sound:setVolumeModifier(0.1)
            t.sound:set3D(true)
            RadioWavs.updateVolume(t)
        end
        ISExitVehicle_perform(self)
    end
end
Events.OnGameStart.Add(RadioWavs.AddOverrides)



------------------------------------------------------------------------------------
-- ADJUST SOUNDS BASED ON DISTANCE/STATE
------------------------------------------------------------------------------------


local minRange = 5
local maxRange = 50
local p = nil
local X = 0
local Y = 0
local vehicleEmitter = nil
local dropoffRange = 0
local volumeModifier = 0
local distanceToRadio = 0
local finalVolume = 0
local tickCounter1 = 0
local tickCounter2 = 0

function RadioWavs.adjustSounds()
    -- TODO: tickrates depend on framerate. find something time-based instead
    if tickCounter2 < 1000 then 
        tickCounter2=tickCounter2+1
    else
        p = getPlayer()
        X = p:getX()
        Y = p:getY()
        --attract zombies
        for _,t in ipairs(RadioWavs.soundCache) do
            if RadioWavs.isPlaying(t) and t.deviceData:getHeadphoneType() == -1 and t.device == t.deviceData:getParent() then
                local range = t.deviceData:getDeviceVolume() * t.sound.volumeModifier*2.5 * maxRange
                if t.deviceData:isInventoryDevice() or t.deviceData:isVehicleDevice() then
                    addSound(p, X, Y, p:getZ(), range/4, range/2)
                else
                    addSound(t.device, t.device:getX()+0.5, t.device:getY()+0.5, t.device:getZ(), range/4, range/2) 
                end
            end
        end
        tickCounter2 = 0
    end
    if tickCounter1 < 5 then tickCounter1=tickCounter1+1 return end
    tickCounter1 = 0


    p = getPlayer()
    X = p:getX()
    Y = p:getY()
    highestVolume = 0
    for _,t in ipairs(RadioWavs.soundCache) do        
        -- sync states
        if t.sound and t.sound:isPlaying() then
            if not t.deviceData:isVehicleDevice() and t.device ~= t.deviceData:getParent() then 
                -- device object changed, this happens when the player picks up or places objects. no idea what's up with car radios
                t.device = t.deviceData:getParent() -- update our device reference
                if t.deviceData:isInventoryDevice() then
                    t.sound:set3D(false)
                else
                    t.sound:setPosAtObject(t.device)
                end
            end
            if t.deviceData:isInventoryDevice() and t.deviceData:getIsTurnedOn() and t.device:getType() ~= "CDplayer" then -- make an exception for CDplayer
                if t.device ~= p:getPrimaryHandItem() and t.device ~= p:getSecondaryHandItem() then -- devices only work if equipped & should be switched off otherwise
                    t.deviceData:setIsTurnedOn(false) -- turn device off in case zomboid didn't do so
                elseif t.deviceData:getIsTelevision() then
                    t.deviceData:setIsTurnedOn(false) -- always turn off tele because it's unplugged
                end
            end
            if not t.deviceData:getIsTurnedOn() and not t.muted then -- device was switched off without player action
                t.muted = true -- sync sound accordingly
                RadioWavs.updateVolume(t)
            end
        end
        --adjust volume based on distance
        if RadioWavs.isPlaying(t) then
            if t.deviceData:isInventoryDevice() then
                highestVolume = 1
            else
                distanceToRadio = IsoUtils.DistanceManhatten(t.device:getX(), t.device:getY(), X, Y)
                if distanceToRadio < maxRange then
                    dropoffRange = (maxRange-minRange)*0.2 + t.deviceData:getDeviceVolume() * t.sound.volumeModifier*2.5 * (maxRange-minRange)*0.8
                    volumeModifier = (minRange + dropoffRange - distanceToRadio) / dropoffRange
                    if volumeModifier < 0 then volumeModifier = 0 end
                    t.sound:setVolume(t.deviceData:getDeviceVolume() * volumeModifier)
                    finalVolume = t.deviceData:getDeviceVolume() * t.sound.volumeModifier * volumeModifier
                    if finalVolume > highestVolume then highestVolume = finalVolume end
                end
            end
        end
    end
    --adjust Zomboid music volume
    local optionsVolume = getCore():getOptionMusicVolume()/10
    local optionsVolumeModified = optionsVolume - optionsVolume*highestVolume*10
    if optionsVolumeModified < 0 then optionsVolumeModified = 0 end
    getSoundManager():setMusicVolume(optionsVolumeModified)
end
Events.OnTick.Add( RadioWavs.adjustSounds )


function RadioWavs.OnMainMenuEnter()
    --reset Zomboid music volume again
    getSoundManager():setMusicVolume( getCore():getOptionMusicVolume()/10 )
end
Events.OnMainMenuEnter.Add( RadioWavs.OnMainMenuEnter )

------------------------------------------------------------------------------------
-- VARIOUS
------------------------------------------------------------------------------------


function RadioWavs.updateVolume(t)
    if not t.muted then
        t.sound:setVolume( t.deviceData:getDeviceVolume() )
    else
        t.sound:setVolume(0)
    end
end

function RadioWavs.isPlaying(t)
    if not t.deviceData:getIsTurnedOn() then return false end
    if t.muted then return false end
    if t.sound and t.sound:isPlaying() then return true end

    return false
end

function RadioWavs.getData(deviceData)
    for _,t in ipairs(RadioWavs.soundCache) do
        if t.deviceData == deviceData then
            return t
        end
    end
end
function RadioWavs.getEmitter(emitter)
    for _,t in ipairs(RadioWavs.soundCache) do
        if t.sound.emitter == emitter then
            return t
        end
    end
end