
local pl = {}

local function check_player(player)
    local pi = pl[player]
    if not pi then pi = {}; pl[player] = pi end
    return pi
end

local wind_check_directions = {
    vector.new(0,  0,  0),
    vector.new(0, -6,  0),

    vector.new( 12,  8,  0),
    vector.new(-12,  8,  0),
    vector.new( 22,  30,  22),
    vector.new(-22,  30,  22),
}

local UP = vector.new(0,1,0)

aom_climate.wind.sound_minimum_wind_strength = 0.2

function aom_climate.wind.get_wind_exposure(pos, wind_dir)
    local total_length = 0
    local total = 0
    wind_dir = vector.normalize(wind_dir * (-1))
    local wind_yaw = core.dir_to_yaw(wind_dir)
    for i, dir in ipairs(wind_check_directions) do
        dir = vector.rotate_around_axis(dir, UP, wind_yaw)
        local bias = (i == 1) and 1 or 1
        local len = i<=1 and 20 or 10
        local start = pos + dir
        for l = 1, len do
            local d = i <= 1 and l or (l*l*0.3 + l*0.7)
            local p = vector.multiply(wind_dir, d) + start
            local n = core.get_node(p)
            if (core.get_item_group(n.name, "solid") > 0) or (core.get_natural_light(p, 0.5) or 0) < 1 then
                total_length = total_length + l * bias
                break
            elseif l == len then
                total_length = total_length + l * bias
            end
        end
        total = total + len * bias
    end
    return total_length / total
end

function aom_climate.wind.get_wind_pitch_gain(pos)
    local light = (minetest.get_natural_light(pos, 0.5) or 0) / core.LIGHT_MAX
    if light <= 0 then return 0.7, 0.0000001 end
    local dir = aom_climate.wind.get_wind_dir(pos)
    local wind_strength = math.min(1.0, vector.length(dir))
    if wind_strength < aom_climate.wind.sound_minimum_wind_strength then
        return 0.7, 0.0000001
    end
    local exposure = aom_climate.wind.get_wind_exposure(pos, dir)
    local gain = light * (exposure * 1.0 - 0.0) * wind_strength * 1.3 - 0.3
    local pitch = (light * exposure) * 0.5 + 0.7
    if gain < 0.0000001 then gain = 0.0000001 end
    return pitch, gain
end

local fade_factor = 0.6
local sound_total_time = 5

function aom_climate.wind.play_wind_sound(pitch, start)
    return minetest.sound_play("aom_climate_wind", {
        gain = 0.00001,
        pitch = pitch,
        loop = false,
        start = start,
    })
end

function aom_climate.wind.do_sound_player(player, dtime)
    local pi = check_player(player)
    pi.sound_time = (pi.sound_time or 0) + dtime
    local pos = aom_climate.get_eyepos(player)
    local setting = aom_climate.get_setting(player, "climate_wind_volume", 0)
    local pitch, gain = aom_climate.wind.get_wind_pitch_gain(pos)
    if gain < 0 then gain = 0.00001 end
    if setting <= 0 then
        if pi.id_main then
            minetest.sound_fade(pi.id_main, fade_factor, 0) -- delete
            pi.id_main = nil
        end
        return
    end

    local start = math.random() * 2

    if pi.sound_time * (pi.pitch_main or 1) > sound_total_time then
        if pi.id_main then core.sound_fade(pi.id_main, fade_factor, 0) end -- delete
        pi.id_main = nil
        start = 0
    end

    if not pi.id_main then
        pi.id_main = aom_climate.wind.play_wind_sound(pitch, start)
        pi.sound_time = start
    end

    if pi.id_main and math.abs((pi.pitch_main or pitch) - pitch) > 0.02 then
        minetest.sound_fade(pi.id_main, fade_factor, 0) -- delete
        pi.id_main = aom_climate.wind.play_wind_sound(pitch, start)
        pi.sound_time = start
    end

    local gaintime = ((pi.gain_main or 0) / gain)
    pi.pitch_main = pitch or 1
    pi.gain_main = gain or 0
    minetest.sound_fade(pi.id_main, fade_factor + gaintime, gain * setting)
end

local _t = 0
minetest.register_globalstep(function(dtime)
    if _t > 0 then _t = _t - dtime; return else _t = _t + 0.1 end
    for i, player in ipairs(minetest.get_connected_players()) do
        aom_climate.wind.do_sound_player(player, 0.1)
    end
end)
