#!/usr/bin/ucode
let fs = require("fs");
let ubus = require("ubus");
let conn = ubus.connect();
let phys = [];
let block_list = {
	"2G": {},
	"5G": {}
};
let channel_masks = {
	"40": [ 5180, 5220, 5260, 5300, 5500, 5540, 5580, 5620, 5660, 5700, 5745, 5785, 5825 ],
	"80": [ 5180, 5260, 5500, 5580, 5660, 5745 ],
	"160": [ 5180, 5500, ],
};

function uptime_get() {
	let info = conn.call("system", "info");

	return info.uptime;
}

function remote_info() {
	let info = conn.call("usteer", "remote_info");

	return info || {};
}

function local_info() {
	let info = conn.call("usteer", "local_info");

	return info || {};
}

function remote_hosts() {
	let hosts = conn.call("usteer", "remote_hosts");

	return hosts || {};
}

let uptime = uptime_get();
let info = local_info();
let remote = remote_info();
let hosts = remote_hosts();

function state_get() {
	let file = fs.open("/tmp/uchannel.json", "r");
	let state = file ? json(file.read("all")) : {};

	if (file)
		file.close();

	return state;
}

function state_set(state) {
	let file = fs.open("/tmp/uchannel.json", "w");

	state.executed = uptime;
	file.write(state);
	file.close();

	conn.call("usteer", "set_node_data", {
		node: "*",
		data: {
			status: state.status,
			uptime: state.executed,
		}
	});
	printf("entering %s state\n", state.status);
}

function freq2chan(freq) {
        if (freq == 2484)
                return 14;
        else if (freq < 2484)
                return (freq - 2407) / 5;
        else if (freq >= 4910 && freq <= 4980)
                return (freq - 4000) / 5;
        else if(freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 6)
                return (freq - 56160) / 2160;
        else
                return (freq - 5000) / 5;
}

function freq2band(freq) {
	if (freq < 2500)
		return "2G";
	return "5G";
}

function chan2freq(band, channel) {
	if (band == '2G' && channel >= 1 && channel <= 13)
		return 2407 + channel * 5;
	else if (band == '2G' && channel == 14)
		return 2484;
	else if (band == '5G' && channel >= 36 && channel <= 177)
		return 5000 + channel * 5;
	else if (band == '5G' && channel >= 183 && channel <= 196)
		return 4000 + channel * 5;
	else if (band == '60G' && channel >= 1 && channel <= 6)
		return 56160 + channel * 2160;

	return null;
}

function center_freq(freq, bandwidth) {
	if (bandwidth == 40)
		return +freq + 10;
	if (bandwidth == 80)
		return +freq + 30;
	if (bandwidth == 160)
		return +freq + 70;
	return +freq;
}

function channel_overlap() {
	let overlap = {};
	let peers = {
		"local": {}
	};

	for (let node, r in remote)
		peers[split(node, "#")] = {};

	for (let id, i in info) {
		peers.local[i.freq] = true;
		block_list[freq2band(freq)][i.freq] = 0;
	}
	for (let node, r in remote) {
		peers[split(node, "#")][r.freq] = true;
		block_list[freq2band(freq)][r.freq] = 0;
	}

	for (let id, peer in peers)
		for (let freq, val in peer)
			block_list[freq2band(freq)][freq]++;

	for (let id, i in info)
		for (let node, r in remote)
			if (i.freq == r.freq) {
				overlap[i.freq] = id;
				break;
			}

	return overlap;
}

function phy_lookup() {
	let status = conn.call("network.wireless", "status");

	for (let id, radio in status) {
		let htmode = match(radio.config.htmode, /^([A-Z]+)(.+)$/);
		let phy = {
			path: radio.config.path,
			htmode: lc(htmode[1]),
			bandwidth: htmode[2],
			iface: [],
			sta: false,
		};

		for (let i, iface in radio.interfaces) {

			push(phy.iface, iface.ifname);
			if (iface.config.mode != 'ap')
				phy.sta = true;
		}
		push(phys, phy);
	}
}

function phy_find(iface) {
	for (let idx, phy in phys)
		if (index(phy.iface, iface) >= 0)
			return phy;
	return {};
}

function channel_mask(band, bandwidth) {
	if (band == "2G")
		return [ 2412, 2437, 2462 ];
	return channel_masks[bandwidth];
}

function channel_scan(band) {
	conn.call("wifi", "scan", { band });
	sleep(5000);

	let survey_data = conn.call("wifi", "survey", { band });
	let scan_data = conn.call("wifi", "scan_dump", { band });

	let channels = {};

	for (let survey in survey_data.survey) {
		channels[survey.channel] = survey;
		channels[survey.channel].bss = 0;
	}

	for (let scan in scan_data.scan)
		channels[scan.channel].bss++;

	return channels;
}

function channel_new(band, channels, mask) {
	let new = [];

	for (let chan, data in channels) {
		if (data.in_use)
			continue;
		let freq = chan2freq(band, chan);
		if (length(mask) && index(mask, freq) < 0)
			continue;
		if (block_list[band][freq])
			continue;
		push(new, {
			freq,
			bss: data.bss,
			airtime: data.busy_ms * 100 / data.active_ms,
		});
	}
	print("available free channels :" + new + "\n");

	let best;
	for (let id, data in new) {
		if (!length(best))
			best = data;
		if (best.bss > data.bss)
			best = data;
		else if (best.bss == data.bss &&
			 best.airtime > data.airtime)
			best = data;
	}
	return best || {};
}

function channel_balance(band, mask) {
	let lowest = {
		freq: mask[0],
		count: 1000,
	};
	let highest = {
		freq: mask[0],
		count: 0,
	};

	for (let freq, count in block_list[band]) {
		if (lowest.count > count)
			lowest = { freq, count };
		if (highest.count < count)
			highest = { freq, count };
	}

	if (highest.count - lowest.cont >= 2)
		return lowest;

	return {};
}

function youngest() {
	for (let ip, host in hosts) {
		if (host.host_info?.status == "overlap" &&
		    host.host_info?.uptime < uptime) {
			print("Found a younger host\n");
			return 1;
		}
	}
	print("We are the youngest host\n");
	return 0;
}

let state = state_get();

if (state.status == "waiting" &&
    (uptime - state.changed < (12 * 60 * 60))) {
	state_set(state);
	return;
}

phy_lookup();
print("discovered devices: " + phys + "\n");
let overlap = channel_overlap();
print("list of blocked channels: " + block_list + "\n");
print("list of overlapping channels: " + overlap + "\n");

if (!length(overlap)) {
	state.status = "happy";
	state_set(state);
	return;
}

if (state.status != "overlap" || youngest()) {
	state.status = "overlap";
	state_set(state);
	return;
}

for (let freq, obj in overlap) {
	let phy = phy_find(split(obj, ".")[1]);
	let band = freq2band(freq);
	let channels = channel_scan(band);
	let mask = channel_mask(band, phy.bandwidth);
	let new;

	if (phy.sta) {
		print("phy has a STA interface cannot change channel\n");
		return;
	}

	new = channel_new(band, channels, mask);

	if (!length(new))
		new = channel_balance(band, mask);

	if (!length(new)) {
		print("no alternative channel found\n");
		continue;
	}
	printf("selected channel: " + new + " for %s\n", obj);
	conn.call(obj, "switch_chan",  {
		freq: +new.freq,
		center_freq1: center_freq(new.freq, phy.bandwidth),
		bcn_count: 10,
		force: true
	});
}

state.status = "waiting";
state.changed = uptime;
state_set(state);
