package org.ovirt.engine.core.vdsbroker;

import org.ovirt.engine.core.common.businessentities.network.NetworkInterface;
import org.ovirt.engine.core.common.businessentities.network.NetworkStatistics;

public class NetworkStatisticsBuilder {

    private static final int BITS_IN_BYTE = 8;
    private static final int BITS_IN_MEGABIT = 1000000;

    private Integer speed;
    private Double currentTime;
    private Double previousTime;

    public NetworkStatisticsBuilder() {
    }

    /**
     * Updates an existing NetworkInterface entity with recent statistics stored in a new NetworkInterface entity.
     *
     * @param existingIface
     *            the existing NetworkInterface entity, whose Statistics and Speed members are to be modified.
     * @param reportedIface
     *            the NetworkInterface entity storing recently-reported values, which will not be modified.
     */
    public void updateExistingInterfaceStatistics(NetworkInterface<?> existingIface, NetworkInterface<?> reportedIface) {
        NetworkStatistics existingStats = existingIface.getStatistics();
        NetworkStatistics reportedStats = reportedIface.getStatistics();

        speed = reportedIface.getSpeed();
        currentTime = reportedStats.getSampleTime();
        previousTime = existingStats.getSampleTime();

        existingIface.setSpeed(speed);
        existingStats.setReceiveDropRate(reportedStats.getReceiveDropRate());
        existingStats.setTransmitDropRate(reportedStats.getTransmitDropRate());

        EffectiveStats rxResult =
                computeEffectiveStats(reportedStats.getReceivedBytes(),
                        existingStats.getReceivedBytes(),
                        existingStats.getReceivedBytesOffset());
        EffectiveStats txResult =
                computeEffectiveStats(reportedStats.getTransmittedBytes(),
                        existingStats.getTransmittedBytes(),
                        existingStats.getTransmittedBytesOffset());

        existingStats.setReceivedBytes(rxResult.current);
        existingStats.setReceivedBytesOffset(rxResult.offset);
        existingStats.setReceiveRate(rxResult.rate);
        existingStats.setTransmittedBytes(txResult.current);
        existingStats.setTransmittedBytesOffset(txResult.offset);
        existingStats.setTransmitRate(txResult.rate);
        existingStats.setSampleTime(currentTime);
    }

    private EffectiveStats computeEffectiveStats(Long reported, Long previous, Long offset) {
        EffectiveStats stats = new EffectiveStats();

        if (reported == null) {
            // the value wasn't reported - clear it, keep the previous offset, and rate can't be computed
            stats.current = null;
            stats.offset = offset;
            stats.rate = null;
        } else if (offset == null) {
            // statistic reported for the first time - set to zero, set offset accordingly, and rate can't be computed
            stats.current = 0L;
            stats.offset = -reported;
            stats.rate = null;
        } else {
            stats.offset = offset;
            stats.current = offset + reported;
            if (previous == null) {
                // for some reason there's no previous sample - the rate can't be computed
                stats.rate = null;
            } else {
                if (stats.current < previous) {
                    // if value wrapped around, it means counter had reset - compensate in offset, and update value
                    stats.offset = previous;
                    stats.current = previous + reported;
                }

                // current and previous sampled values are up-to-date - try to compute rate
                stats.rate = computeRatePercentage(stats.current - previous);
            }
        }

        return stats;
    }

    private static double truncatePercentage(double value) {
        return Math.min(100, value);
    }

    private Double computeRatePercentage(long byteDiff) {
        if (currentTime == null || previousTime == null || currentTime <= previousTime || speed == null
                || speed.equals(0)) {
            return null;
        }

        double megabitDiff = BITS_IN_BYTE * (double)byteDiff / BITS_IN_MEGABIT;
        double timeDiffInSeconds = currentTime - previousTime;
        double rateInMbps = megabitDiff / timeDiffInSeconds;
        return truncatePercentage(100 * rateInMbps / speed);
    }

    private static class EffectiveStats {
        private Long current;
        private Long offset;
        private Double rate;
    }

}
