#include "clock.h"

#include "util/log.h"

#define SC_CLOCK_NDEBUG // comment to debug

void
sc_clock_init(struct sc_clock *clock) {
    clock->count = 0;
    clock->head = 0;
    clock->left_sum.system = 0;
    clock->left_sum.stream = 0;
    clock->right_sum.system = 0;
    clock->right_sum.stream = 0;
}

// Estimate the affine function f(stream) = slope * stream + offset
static void
sc_clock_estimate(struct sc_clock *clock,
                  double *out_slope, sc_tick *out_offset) {
    assert(clock->count > 1); // two points are necessary

    struct sc_clock_point left_avg = {
        .system = clock->left_sum.system / (clock->count / 2),
        .stream = clock->left_sum.stream / (clock->count / 2),
    };
    struct sc_clock_point right_avg = {
        .system = clock->right_sum.system / ((clock->count + 1) / 2),
        .stream = clock->right_sum.stream / ((clock->count + 1) / 2),
    };

    double slope = (double) (right_avg.system - left_avg.system)
                 / (right_avg.stream - left_avg.stream);

    if (clock->count < SC_CLOCK_RANGE) {
        /* The first frames are typically received and decoded with more delay
         * than the others, causing a wrong slope estimation on start. To
         * compensate, assume an initial slope of 1, then progressively use the
         * estimated slope. */
        slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count))
              / SC_CLOCK_RANGE;
    }

    struct sc_clock_point global_avg = {
        .system = (clock->left_sum.system + clock->right_sum.system)
                 / clock->count,
        .stream = (clock->left_sum.stream + clock->right_sum.stream)
                 / clock->count,
    };

    sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope);

    *out_slope = slope;
    *out_offset = offset;
}

void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
    struct sc_clock_point *point = &clock->points[clock->head];

    if (clock->count == SC_CLOCK_RANGE || clock->count & 1) {
        // One point passes from the right sum to the left sum

        unsigned mid;
        if (clock->count == SC_CLOCK_RANGE) {
            mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE;
        } else {
            // Only for the first frames
            mid = clock->count / 2;
        }

        struct sc_clock_point *mid_point = &clock->points[mid];
        clock->left_sum.system += mid_point->system;
        clock->left_sum.stream += mid_point->stream;
        clock->right_sum.system -= mid_point->system;
        clock->right_sum.stream -= mid_point->stream;
    }

    if (clock->count == SC_CLOCK_RANGE) {
        // The current point overwrites the previous value in the circular
        // array, update the left sum accordingly
        clock->left_sum.system -= point->system;
        clock->left_sum.stream -= point->stream;
    } else {
        ++clock->count;
    }

    point->system = system;
    point->stream = stream;

    clock->right_sum.system += system;
    clock->right_sum.stream += stream;

    clock->head = (clock->head + 1) % SC_CLOCK_RANGE;

    if (clock->count > 1) {
        // Update estimation
        sc_clock_estimate(clock, &clock->slope, &clock->offset);

#ifndef SC_CLOCK_NDEBUG
        LOGD("Clock estimation: %g * pts + %" PRItick,
             clock->slope, clock->offset);
#endif
    }
}

sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) {
    assert(clock->count > 1); // sc_clock_update() must have been called
    return (sc_tick) (stream * clock->slope) + clock->offset;
}
