// Copyright (c) 2017-2023 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qt/trafficgraphdata.h>

const int TrafficGraphData::RangeMinutes[] = {5,10,15,30,60,120,180,360,720,1440};
const int TrafficGraphData::DESIRED_DATA_SAMPLES = TrafficGraphData::RangeMinutes[TrafficGraphData::Range_5m] * 60; // i.e. one data sample per second for Range_5m
const int TrafficGraphData::DesiredQueueSizes[] = {
        TrafficGraphData::DESIRED_DATA_SAMPLES,     //Range_5m
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_10m
        TrafficGraphData::DESIRED_DATA_SAMPLES*2/3, //Range_15m
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_30m
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_1h
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_2h
        TrafficGraphData::DESIRED_DATA_SAMPLES*2/3, //Range_3h
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_6h
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_12h
        TrafficGraphData::DESIRED_DATA_SAMPLES/2,   //Range_24h
    } ;

const int TrafficGraphData::SMALLEST_SAMPLE_PERIOD =
        TrafficGraphData::RangeMinutes[TrafficGraphData::Range_5m] * 60 * 1000 / TrafficGraphData::DESIRED_DATA_SAMPLES;

TrafficGraphData::TrafficGraphData(GraphRange range)
    :currentGraphRange(range)
{
}

void TrafficGraphData::tryAddingSampleToStash(GraphRange range)
{
    SampleQueue& queue = sampleMap[range];
    if (queue.size() > DesiredQueueSizes[range]) {
       sampleStash[range].push_front(queue.at(DesiredQueueSizes[range]));
    }
}

void TrafficGraphData::tryUpdateNextWithLast2Samples(GraphRange range, GraphRange nextRange)
{
    SampleQueue& queue = sampleMap[range];
    if (queue.size() == DesiredQueueSizes[range] + 2) {
        update(nextRange, queue.takeLast() + queue.takeLast());
    }
}

void TrafficGraphData::tryUpdateNextWithLast3Samples(GraphRange range, GraphRange nextRange)
{
    SampleQueue& stashQueue = sampleStash[range];
    if (stashQueue.size() == 3) {
        update(nextRange, stashQueue.takeLast() + stashQueue.takeLast() + stashQueue.takeLast());
    }
}

void TrafficGraphData::setLastBytes(quint64 nLastBytesIn, quint64 nLastBytesOut)
{
    this->nLastBytesIn = nLastBytesIn;
    this->nLastBytesOut = nLastBytesOut;
}

bool TrafficGraphData::update(quint64 totalBytesRecv, quint64 totalBytesSent)
{
    float inRate = totalBytesRecv - nLastBytesIn;
    float outRate = totalBytesSent - nLastBytesOut;
    nLastBytesIn = totalBytesRecv;
    nLastBytesOut = totalBytesSent;
    return update(TrafficSample(inRate, outRate));
}

bool TrafficGraphData::update(const TrafficSample& trafficSample)
{
    update(Range_5m, trafficSample);

    currentSampleCounter++;

    if (RangeMinutes[currentGraphRange] / RangeMinutes[Range_5m] == currentSampleCounter) {
        currentSampleCounter = 0;
        return true;
    }
    return false;
}

void TrafficGraphData::update(GraphRange range, const TrafficSample& trafficSample)
{
    SampleQueue& queue = sampleMap[range];
    queue.push_front(trafficSample);

    switch(range) {
        case Range_5m: {
            tryAddingSampleToStash(Range_5m);
            tryUpdateNextWithLast2Samples(Range_5m, Range_10m);
            tryUpdateNextWithLast3Samples(Range_5m, Range_15m);
            return;
        }
        case Range_15m: {
            tryUpdateNextWithLast2Samples(Range_15m, Range_30m);
            return;
        }
        case Range_30m: {
            tryUpdateNextWithLast2Samples(Range_30m, Range_1h);
            return;
        }
        case Range_1h: {
            tryAddingSampleToStash(Range_1h);
            tryUpdateNextWithLast2Samples(Range_1h, Range_2h);
            tryUpdateNextWithLast3Samples(Range_1h, Range_3h);
            return;
        }
        case Range_3h: {
            tryUpdateNextWithLast2Samples(Range_3h, Range_6h);
            return;
        }
        case Range_6h: {
            tryUpdateNextWithLast2Samples( Range_6h, Range_12h);
            return;
        }
        case Range_12h: {
            tryUpdateNextWithLast2Samples(Range_12h, Range_24h);
            return;
        }
        default: {
            if (queue.size() > DesiredQueueSizes[range]) {
                queue.removeLast();
            }
            return;
        }
    }
}

void TrafficGraphData::switchRange(GraphRange newRange)
{
    currentGraphRange = newRange;
    currentSampleCounter = 0;
}

TrafficGraphData::SampleQueue TrafficGraphData::sumEach2Samples(const SampleQueue& rangeQueue)
{
    SampleQueue result;
    int i = rangeQueue.size() - 1;

    while (i - 1 >= 0) {
        result.push_front(rangeQueue.at(i) + rangeQueue.at(i - 1));
        i -= 2;
    }
    return result;
}

TrafficGraphData::SampleQueue TrafficGraphData::sumEach3Samples(const SampleQueue& rangeQueue, GraphRange range)
{
    SampleQueue result;
    int lastUnusedSample = std::min(rangeQueue.size() - 1, DESIRED_DATA_SAMPLES - 1);

    // use stash first
    SampleQueue& stashQueue = sampleStash[range];
    TrafficSample sum(0, 0);
    for (int i = 0; i < stashQueue.size(); ++i) {
       sum += stashQueue.at(i);
    }
    int toFullSample = 3 - stashQueue.size();
    if (toFullSample > rangeQueue.size())
        return result;

    // append to stash data to create whole sample
    for (int i = 0; i < toFullSample; ++i) {
        sum += rangeQueue.at(lastUnusedSample);
        --lastUnusedSample;
    }
    result.push_front(sum);

    while (lastUnusedSample - 2 >= 0) {
        result.push_front(rangeQueue.at(lastUnusedSample) + rangeQueue.at(lastUnusedSample - 1) + rangeQueue.at(lastUnusedSample - 2));
        lastUnusedSample -= 3;
    }
    return result;
}

TrafficGraphData::SampleQueue TrafficGraphData::getRangeQueue(GraphRange range)
{
    switch(range) {
        case Range_5m: {
            return sampleMap[Range_5m];
        }
        case Range_10m: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_5m));
            queue.append(sampleMap[Range_10m]);
            return queue;
        }
        case Range_15m: {
            SampleQueue queue = sumEach3Samples(getRangeQueue(Range_5m), Range_5m);
            queue.append(sampleMap[Range_15m]);
            return queue;
        }
        case Range_30m: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_15m));
            queue.append(sampleMap[Range_30m]);
            return queue;
        }
        case Range_1h: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_30m));
            queue.append(sampleMap[Range_1h]);
            return queue;
        }
        case Range_2h: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_1h));
            queue.append(sampleMap[Range_2h]);
            return queue;
        }
        case Range_3h: {
            SampleQueue queue = sumEach3Samples(getRangeQueue(Range_1h),Range_1h);
            queue.append(sampleMap[Range_3h]);
            return queue;
        }
        case Range_6h: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_3h));
            queue.append(sampleMap[Range_6h]);
            return queue;
        }
        case Range_12h: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_6h));
            queue.append(sampleMap[Range_12h]);
            return queue;
        }
        case Range_24h: {
            SampleQueue queue = sumEach2Samples(getRangeQueue(Range_12h));
            queue.append(sampleMap[Range_24h]);
            return queue;
        }
        default: {
            return SampleQueue();
        }
    }
}

TrafficGraphData::SampleQueue TrafficGraphData::getCurrentRangeQueue()
{
    SampleQueue newQueue;
    getRangeQueue(currentGraphRange).mid(0, DESIRED_DATA_SAMPLES).swap(newQueue);
    return newQueue;
}

float TrafficGraphData::convertSampleToBandwidth(float dataAmount)
{
    // to base range
    float result  = dataAmount / RangeMinutes[currentGraphRange] * RangeMinutes[TrafficGraphData::Range_5m];
    // to B/s
    result = result * 1000 / SMALLEST_SAMPLE_PERIOD;
    // to KB/s
    result = result / 1024;

    return result;

}

TrafficGraphData::SampleQueue TrafficGraphData::getCurrentRangeQueueWithAverageBandwidth()
{
    SampleQueue newQueue;
    getRangeQueue(currentGraphRange).mid(0, DESIRED_DATA_SAMPLES).swap(newQueue);
    for (auto& sample : newQueue){
        sample.in = convertSampleToBandwidth(sample.in);
        sample.out = convertSampleToBandwidth(sample.out);
    }
    return newQueue;
}

void TrafficGraphData::clear()
{
    sampleMap.clear();
    sampleMap.clear();
    currentSampleCounter = 0;
    nLastBytesIn = 0;
    nLastBytesOut = 0;
}
