#!/usr/bin/lua

local util = require 'gluon.util'
local wireless = require 'gluon.wireless'
local site = require 'gluon.site'
local sysconfig = require 'gluon.sysconfig'
local iwinfo = require 'iwinfo'

local uci = require('simple-uci').cursor()

-- Initial
if not sysconfig.gluon_version then
	uci:delete_all('wireless', 'wifi-iface')

	-- First count all radios with a fixed frequency band.
	-- This is needed to distribute devices which have radios
	-- capable of operating in the 2.4 GHz and 5 GHz band need
	-- to be distributed evenly.
	local radio_band_count = {band24=0, band5=0}
	wireless.foreach_radio(uci, function(radio)
		local hwmodes = iwinfo.nl80211.hwmodelist(wireless.find_phy(radio))
		if hwmodes.g and not (hwmodes.a or hwmodes.ac) then
			-- 2.4 GHz
			radio_band_count["band24"] = radio_band_count["band24"] + 1
		elseif (hwmodes.a or hwmodes.ac) and not hwmodes.g then
			-- 5 GHz
			radio_band_count["band5"] = radio_band_count["band5"] + 1
		end
	end)

	-- Use the number of all fixed 2.4G GHz and 5 GHz radios to
	-- distribute dualband radios in this step.
	wireless.foreach_radio(uci, function(radio)
		local radio_name = radio['.name']
		local hwmodes = iwinfo.nl80211.hwmodelist(wireless.find_phy(radio))
		if (hwmodes.a or hwmodes.ac) and hwmodes.g then
			-- Dualband radio
			if radio_band_count["band24"] <= radio_band_count["band5"] then
				-- Assign radio to 2.4GHz band
				radio_band_count["band24"] = radio_band_count["band24"] + 1
				uci:set('wireless', radio_name, 'band', '2g')
			else
				-- Assign radio to 5GHz band
				radio_band_count["band5"] = radio_band_count["band5"] + 1
				uci:set('wireless', radio_name, 'band', '5g')
			end
		end
	end)
end

local function is_outdoor()
	return uci:get_bool('gluon', 'wireless', 'outdoor')
end

local function get_channel(radio, config)
	if radio.band == '5g' and is_outdoor() then
		-- actual channel will be picked and probed from chanlist
		return 'auto'
	end

	return config.channel()
end

local function get_htmode(radio)
	if wireless.preserve_channels(uci) then
		return radio.htmode
	end

	if radio.band == '5g' and is_outdoor() then
		local outdoor_htmode = uci:get('gluon', 'wireless', 'outdoor_' .. radio['.name'] .. '_htmode')
		if outdoor_htmode ~= nil then
			return outdoor_htmode
		end
	end

	local phy = wireless.find_phy(radio)
	if iwinfo.nl80211.hwmodelist(phy).ax then
		return 'HE20'
	end

	if iwinfo.nl80211.hwmodelist(phy).ac then
		return 'VHT20'
	end

	return 'HT20'
end

local function is_disabled(name)
	if not uci:get('wireless', name) then
		return nil
	end

	return uci:get_bool('wireless', name, 'disabled')
end

-- Returns the first argument that is not nil; don't call without any non-nil arguments!
local function first_non_nil(first, ...)
	if first ~= nil then
		return first
	end

	return first_non_nil(...)
end


local function delete_ibss(radio_name)
	local name = 'ibss_' .. radio_name

	uci:delete('wireless', name)
end

local function configure_mesh(config, radio, index, suffix, disabled)
	local radio_name = radio['.name']
	local name = 'mesh_' .. radio_name

	local macfilter = uci:get('wireless', name, 'macfilter')
	local maclist = uci:get('wireless', name, 'maclist')

	uci:delete('wireless', name)

	if not config then
		return
	end

	local macaddr = wireless.get_wlan_mac(uci, radio, index, 2)
	if not macaddr then
		return
	end

	uci:section('network', 'interface', name, {
		proto = 'gluon_mesh',
	})

	uci:section('wireless', 'wifi-iface', name, {
		device = radio_name,
		network = name,
		mode = 'mesh',
		mesh_id = config.id,
		mesh_fwding = false,
		macaddr = macaddr,
		mcast_rate = config.mcast_rate,
		ifname = suffix and 'mesh' .. suffix,
		disabled = disabled,
		macfilter = macfilter,
		maclist = maclist,
	})
end

local function fixup_wan(radio, index)
	local radio_name = radio['.name']
	local name = 'wan_' .. radio_name

	if not uci:get('wireless', name) then
		return
	end

	local macaddr = wireless.get_wlan_mac(uci, radio, index, 4)
	if not macaddr then
		return
	end

	uci:set('wireless', name, 'macaddr', macaddr)
end

local function configure_mesh_wireless(radio, index, config, disabled)
	local radio_name = radio['.name']
	local suffix = radio_name:match('^radio(%d+)$')

	configure_mesh(config.mesh(), radio, index, suffix,
		first_non_nil(
			disabled,
			is_disabled('mesh_' .. radio_name),
			config.mesh.disabled(false)
		)
	)
end

local function set_channels(radio, radio_name, config)
	if wireless.preserve_channels(uci) then
		return
	end
	local channel = get_channel(radio, config)
	uci:set('wireless', radio_name, 'channel', channel)

	local chanlist
	if radio.band == '5g' and is_outdoor() then
		chanlist = config.outdoor_chanlist()
	end
	uci:set('wireless', radio_name, 'channels', chanlist)
end

wireless.foreach_radio(uci, function(radio, index, config)
	local radio_name = radio['.name']

	delete_ibss(radio_name)

	if not config() then
		uci:set('wireless', radio_name, 'disabled', true)
		return
	end

	local suffix = radio_name:match('^radio(%d+)$')
	if not suffix then
		return
	end

	local htmode = get_htmode(radio)
	local beacon_interval = config.beacon_interval()

	uci:delete('wireless', radio_name, 'disabled')

	set_channels(radio, radio_name, config)

	uci:set('wireless', radio_name, 'htmode', htmode)
	uci:set('wireless', radio_name, 'country', site.regdom())

	uci:delete('wireless', radio_name, 'supported_rates')
	uci:delete('wireless', radio_name, 'basic_rate')

	local band = radio.band
	if band == '2g' then
		uci:set('wireless', radio_name, 'legacy_rates', false)
		configure_mesh_wireless(radio, index, config)
	elseif (band == '5g') then
		-- ToDo: Remove in v2024.x
		local hostapd_options = uci:get_list('wireless', radio_name, 'hostapd_options')
		util.remove_from_set(hostapd_options, 'country3=0x4f')
		uci:set_list('wireless', radio_name, 'hostapd_options', hostapd_options)

		if is_outdoor() then
			-- enforce outdoor channels by filtering the regdom for outdoor channels
			uci:set('wireless', radio_name, 'country3', '0x4f')
			configure_mesh_wireless(radio, index, config, true)
		else
			uci:delete('wireless', radio_name, 'country3')
			configure_mesh_wireless(radio, index, config)
		end
	end

	uci:set('wireless', radio_name, 'beacon_int', beacon_interval)

	fixup_wan(radio, index)
end)


if uci:get('system', 'rssid_wlan0') then
	uci:set('system', 'rssid_wlan0', 'dev', 'mesh0')
	uci:save('system')
end

uci:save('wireless')
uci:save('network')
