/*
 * Copyright (C) 2014-2026 CZ.NIC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <utility> /* ::std::move */

#include "src/datovka_shared/utility/network_speed.h"

#define DFL_UPDATE_INTERVAL_MSEC 1000

NetworkSpeed::NetworkSpeed(void)
    : m_updateIntervalMSec(DFL_UPDATE_INTERVAL_MSEC),
    m_start(),
    m_penultimate(),
    m_stop(),
    m_lastIncrement(0),
    m_totalSum(0)
{
}

NetworkSpeed::NetworkSpeed(const NetworkSpeed &other)
    : m_updateIntervalMSec(other.m_updateIntervalMSec),
    m_start(other.m_start),
    m_penultimate(other.m_penultimate),
    m_stop(other.m_stop),
    m_lastIncrement(other.m_lastIncrement),
    m_totalSum(other.m_totalSum)
{
}

#ifdef Q_COMPILER_RVALUE_REFS
NetworkSpeed::NetworkSpeed(NetworkSpeed &&other) Q_DECL_NOEXCEPT
    : m_updateIntervalMSec(other.m_updateIntervalMSec),
    m_start(::std::move(other.m_start)),
    m_penultimate(::std::move(other.m_penultimate)),
    m_stop(::std::move(other.m_stop)),
    m_lastIncrement(other.m_lastIncrement),
    m_totalSum(other.m_totalSum)
{
}
#endif /* Q_COMPILER_RVALUE_REFS */

NetworkSpeed &NetworkSpeed::operator=(const NetworkSpeed &other) Q_DECL_NOTHROW
{
	m_updateIntervalMSec = other.m_updateIntervalMSec;
	m_start = other.m_start;
	m_penultimate = other.m_penultimate;
	m_stop = other.m_stop;
	m_lastIncrement = other.m_lastIncrement;
	m_totalSum = other.m_totalSum;

	return *this;
}

#ifdef Q_COMPILER_RVALUE_REFS
NetworkSpeed &NetworkSpeed::operator=(NetworkSpeed &&other) Q_DECL_NOTHROW
{
	m_updateIntervalMSec = other.m_updateIntervalMSec;
	m_start = ::std::move(other.m_start);
	m_penultimate = ::std::move(other.m_penultimate);
	m_stop = ::std::move(other.m_stop);
	m_lastIncrement = other.m_lastIncrement;
	m_totalSum = other.m_totalSum;

	return *this;
}
#endif /* Q_COMPILER_RVALUE_REFS */

QString NetworkSpeed::speedOverview(void) const
{
	if (Q_UNLIKELY((!m_penultimate.isValid()) || (!m_stop.isValid()))) {
		return QString();
	}
	const qint64 diffMsecs =
	    m_stop.toMSecsSinceEpoch() - m_penultimate.toMSecsSinceEpoch();
	if (Q_UNLIKELY(diffMsecs <= 0)) {
		return QString();
	}

	QString totalStr;
	{
		if (m_totalSum > (1024 * 1024)) {
			double mb = m_totalSum / ((double)(1024 * 1024));
			totalStr = QString("%1 MB").arg(mb, 0, 'f', 2);
		} else if (m_totalSum > 1024) {
			double kb = m_totalSum / ((double)(1024));
			totalStr = QString("%1 KB").arg(kb, 0, 'f', 2);
		} else {
			totalStr = QString("%1 B").arg(m_totalSum);
		}
	}

	QString speedStr;
	{
		const qint64 bps = (m_lastIncrement * 1000) / diffMsecs;

		if (bps > (1024 * 1024)) {
			double mbps = bps / ((double)(1024 * 1024));
			speedStr = QString("%1 MB/s").arg(mbps, 0, 'f', 2);
		} else if (bps > 1024) {
			double kbps = bps / ((double)(1024));
			speedStr = QString("%1 KB/s").arg(kbps, 0, 'f', 2);
		} else {
			speedStr = QString("%1 B/s").arg(bps);
		}
	}

	return QString("%1; %2").arg(totalStr).arg(speedStr);
}

QString NetworkSpeed::downloadOverview(void) const
{
	QString overview = speedOverview();

	if (!overview.isEmpty()) {
		/* downwards arrow */
		return QString("↓ ") + overview;
	}

	return QString();
}

QString NetworkSpeed::uploadOverview(void) const
{
	QString overview = speedOverview();

	if (!overview.isEmpty()) {
		/* upwards arrow */
		return QString("↑ ") + overview;
	}

	return QString();
}

NetworkSpeed NetworkSpeed::createCounter(qint64 firstTotal)
{
	NetworkSpeed netSpeed;

	netSpeed.resetCounter(firstTotal);

	return netSpeed;
}

void NetworkSpeed::resetCounter(qint64 firstTotal)
{
	if (Q_UNLIKELY(firstTotal < 0)) {
		firstTotal = 0;
	}

	const QDateTime now = QDateTime::currentDateTimeUtc();

	m_start = now;
	m_penultimate = now;
	m_stop = now;
	m_lastIncrement = firstTotal;
	m_totalSum = firstTotal;
}

void NetworkSpeed::updateCounter(qint64 currentTotal)
{
	if (Q_UNLIKELY(!m_start.isValid())) {
		resetCounter(currentTotal);
		return;
	}

	const QDateTime now = QDateTime::currentDateTimeUtc();

	/* Update in intervals longer than m_updateIntervalMSec. */
	if (((m_stop.toMSecsSinceEpoch() + m_updateIntervalMSec) <= now.toMSecsSinceEpoch())
	        && (currentTotal > m_totalSum)) {
		m_penultimate = m_stop;
		m_stop = now;
		m_lastIncrement = (currentTotal - m_totalSum);
		m_totalSum = currentTotal;
	}
}
