///
//
// icey
// Copyright (c) 2005, icey <https://0state.com>
//
// SPDX-License-Identifier: LGPL-2.1+
//
/// @addtogroup av
/// @{


#pragma once


#include "icy/av/packet.h"
#include "icy/packetstream.h"

#include <time.h>


namespace icy {
/// Audio/video codecs, capture devices, packet types, and media helpers.
namespace av {


/// FPS counter based on the simple moving average (SMA) algorithm
class AV_API FPSCounter
{
private:
    static const int MAX = 100;

    int tickIndex;
    clock_t prevTick;
    clock_t tickSum;
    clock_t tickList[MAX];

    double updateAvg(clock_t newTick)
    {
        tickSum -= tickList[tickIndex]; // subtract value falling off
        tickSum += newTick;             // add new value
        tickList[tickIndex] = newTick;  // save new value so it can be subtracted later
        tickIndex = (tickIndex + 1) % MAX;

        return (static_cast<double>(tickSum) / MAX); // return average
    }

public:
    double fps;
    int64_t frames;

    /// Initialise the counter with all zeroes.
    FPSCounter() { reset(); }

    /// Reset all counters and the SMA window to zero.
    void reset()
    {
        fps = 0;
        frames = 0;
        tickIndex = 0;
        tickSum = 0;
        prevTick = 0;
        for (int i = 0; i < MAX; i++)
            tickList[i] = 0;
    }

    /// Record a new frame and update the FPS estimate.
    /// Must be called once per frame. Updates `fps` and increments `frames`.
    void tick()
    {
        frames++;
        clock_t newTick = clock();
        double avgTick = updateAvg(newTick - prevTick);
        prevTick = newTick;
        if (avgTick == 0.)
            fps = 0.0; //-1.;
        else
            fps = CLOCKS_PER_SEC / avgTick;
    }
};


/// Legacy timing helpers kept for compatibility with older media pipelines.
namespace legacy {

/// Legacy frames-per-second counter
struct FPSCounter
{
    clock_t start;
    clock_t end;
    int64_t frames;
    double total;
    double fps;

    FPSCounter()
    {
        reset();
    }

    /// Begin a new timing cycle (calls endFrame() first if already started).
    void tick()
    {
        if (started())
            endFrame();
        startFrame();
    }

    /// Reset all counters to zero.
    void reset()
    {
        start = 0;
        end = 0;
        total = 0;
        fps = 0;
        frames = 0;
    }

    /// @return True if startFrame() has been called and the timer is running.
    bool started()
    {
        return start != 0;
    }

    /// Record the frame start time.
    void startFrame()
    {
        start = clock();
    }

    /// Record the frame end time and update the cumulative FPS average.
    /// @return The updated FPS value.
    double endFrame()
    {
        end = clock();
        total += static_cast<double>(end - start) / CLOCKS_PER_SEC;
        frames++;
        fps = (1.0 * frames) / total;
        return fps;
    }
};

} // namespace legacy


/// PacketStream processor that caps packet throughput to a maximum FPS.
///
/// If the observed frame rate exceeds the configured FPS budget, packets
/// are dropped until the rate falls back under the limit.
///
/// Note that previous processors must not fragment packets,
/// otherwise this class will not be accurate, and the packet
/// drop rate will be too high.
class AV_API FPSLimiter : public PacketProcessor
{
public:
    /// Construct the limiter.
    /// @param max        The maximum allowed frame rate in frames per second.
    /// @param videoOnly  If true, non-VideoPacket packets are always forwarded regardless of rate.
    FPSLimiter(int max, bool videoOnly = false)
        : PacketProcessor(this->emitter)
        , _max(max)
        , _videoOnly(videoOnly)
    {
    }

    /// Process a packet: forward it if within the rate limit, drop it otherwise.
    /// @param packet  The incoming packet to evaluate.
    virtual void process(IPacket& packet)
    {
        // traceL("FPLLimiter", this)("Processing")

        // Proxy non video packets if videoOnly is set
        if (_videoOnly && !dynamic_cast<av::VideoPacket*>(&packet))
            emit(packet);

        if (_counter.started())
            _counter.endFrame();
        if (static_cast<int>(_counter.fps) > _max) {
            STrace << "Dropping packet: "
                   << _counter.fps << " > " << _max;
            return;
        }
        _counter.startFrame();
        emit(packet);
    }

    /// Reset the FPS counter when the stream state changes.
    virtual void onStreamStateChange(const PacketStreamState&)
    {
        _counter.reset();
    }

    PacketSignal emitter;

protected:
    int _max;
    bool _videoOnly;
    legacy::FPSCounter _counter;
};


} // namespace av
} // namespace icy


/// @}
