#!/usr/bin/lua

local util = require 'gluon.util'
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}
	util.foreach_radio(uci, function(radio)
		local hwmodes = iwinfo.nl80211.hwmodelist(util.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.
	util.foreach_radio(uci, function(radio)
		local radio_name = radio['.name']
		local hwmodes = iwinfo.nl80211.hwmodelist(util.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, 'hwmode', '11g')
			else
				-- Assign radio to 5GHz band
				radio_band_count["band5"] = radio_band_count["band5"] + 1
				uci:set('wireless', radio_name, 'hwmode', '11a')
			end
		end
	end)
end

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

local function get_channel(radio, config)
	local channel
	if uci:get_first('gluon-core', 'wireless', 'preserve_channels') then
		-- preserved channel always wins
		channel = radio.channel
	elseif  (radio.hwmode == '11a' or radio.hwmode == '11na') and is_outdoor() then
		-- actual channel will be picked and probed from chanlist
		channel = 'auto'
	end

	return channel or config.channel()
end

local function get_htmode(radio)
	if (radio.hwmode == '11a' or radio.hwmode == '11na') 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 = util.find_phy(radio)
	if iwinfo.nl80211.hwmodelist(phy).ac then
		return 'VHT20'
	end

	return 'HT20'
end

local function is_disabled(name)
	if uci:get('wireless', name) then
		return uci:get_bool('wireless', name, 'disabled')
	else
		return false
	end
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
	else
		return first_non_nil(...)
	end
end


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

	uci:delete('network', name)
	uci:delete('network', name .. '_vlan')
	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('network', name)
	uci:delete('network', name .. '_vlan')
	uci:delete('wireless', name)

	if not config then
		return
	end

	local macaddr = util.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 = util.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)
	local radio_name = radio['.name']
	local suffix = radio_name:match('^radio(%d+)$')

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

util.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 channel = get_channel(radio, config)
	local htmode = get_htmode(radio)

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

	uci:set('wireless', radio_name, 'channel', channel)
	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 hwmode = radio.hwmode
	if hwmode == '11g' or hwmode == '11ng' then
		uci:set('wireless', radio_name, 'legacy_rates', false)
		configure_mesh_wireless(radio, index, config)
	elseif (hwmode == '11a' or hwmode == '11na') then
		if is_outdoor() then
			uci:set('wireless', radio_name, 'channels', config.outdoor_chanlist())

			-- enforce outdoor channels by filtering the regdom for outdoor channels
			local hostapd_options = uci:get_list('wireless', radio_name, 'hostapd_options')
			util.add_to_set(hostapd_options, 'country3=0x4f')
			uci:set_list('wireless', radio_name, 'hostapd_options', hostapd_options)

			uci:delete('wireless', 'mesh_' .. radio_name)
		else
			uci:delete('wireless', radio_name, 'channels')

			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)

			configure_mesh_wireless(radio, index, config)
		end
	end

	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')
