#!/bin/env python
"""Script that outputs network activity"""

from pathlib import Path
from json import dumps
from sys import argv
from time import sleep
from typing import Literal, NoReturn


def get_interfaces() -> list[Path]:
	return list(Path("/sys/class/net/").iterdir())


class TransferredBytes:
	def __init__(self, interface: Path, statistic: Literal["tx"] | Literal["rx"]) -> None:
		self._statistic: Path = interface / f"statistics/{statistic}_bytes"
		self._current: int = int(self._statistic.read_text())
		self._previous: int = self._current

	def update(self) -> None:
		self._previous = self._current
		self._current = int(self._statistic.read_text())

	def value(self, interval: float) -> float:
		return (self._current - self._previous) / interval


class Status:
	def __init__(self, interface: Path) -> None:
		self._statistic: Path = interface / "operstate"

	def up(self) -> bool:
		return self._statistic.read_text().strip() == "up"


def format_4_significant_digits(num: float) -> str:
	assert num >= 1.0, "Doesn't properly handle numbers below 1"
	if num < 999:
		return f"{num:04.3g}"
	if num < 1000:
		# Above doesn't nicely handle the special case in [999.5 1000)
		return "0999"
	return f"{num:.4g}"


EBI_UNITS = ("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")
SHORT_UNITS = ("B", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q")


def format_bytes(num: float) -> str:
	for prefix in SHORT_UNITS:
		if num < 1024:
			return f"{format_4_significant_digits(num)}{prefix}"
		num /= 1024
	return "what the fuck"


class Interface:
	def __init__(self, location: Path) -> None:
		self._location: Path = location
		self._tx: TransferredBytes = TransferredBytes(location, "tx")
		self._rx: TransferredBytes = TransferredBytes(location, "rx")
		self._status: Status = Status(location)

	@property
	def name(self) -> str:
		return self._location.name

	def update(self) -> None:
		self._tx.update()
		self._rx.update()

	def json(self, interval: float) -> dict[str, str | bool | float]:
		interval_tx = self._tx.value(interval)
		interval_rx = self._rx.value(interval)

		return {
			"tx": format_bytes(interval_tx),
			"rx": format_bytes(interval_rx),
			"combined": format_bytes(interval_tx + interval_rx),
			"combined_raw": interval_tx + interval_rx,
			"up": self._status.up(),
		}


class Statistics:
	def __init__(self) -> None:
		self._interfaces: list[Interface] = [Interface(location) for location in get_interfaces()]

	def update(self) -> None:
		for interface in self._interfaces:
			interface.update()

	def json(self, interval: float) -> str:
		return dumps(
			{
				interface.name: interface.json(interval) for interface in self._interfaces
			},
			separators=(',', ':'),
		)


def main(interval: float) -> NoReturn:
	stats = Statistics()
	sleep(0.5)
	stats.update()
	print(stats.json(interval))

	while True:
		sleep(interval)
		stats.update()
		print(stats.json(interval), flush=True)


if __name__ == "__main__":
	if len(argv) != 2:
		exit("Must provide 1 arg: Polling interval")
	main(float(argv[1]))
