/*
 *  Copyright (C) 2005-2020 Team Kodi (https://kodi.tv)
 *  Copyright (C) 2012 Marcel Ebmer
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSE.md for more information.
 */

#include "fische_internal.h"

#include <stdlib.h>
#include <string.h>

#ifdef DEBUG
#include <stdio.h>
#endif

enum {_FISCHE__WAITING_, _FISCHE__MAYBEWAITING_, _FISCHE__BEAT_};

int
_fische__compare_int_fast16_t_ (void const* value1, void const* value2)
{
    return (* (int_fast16_t*) value1 - * (int_fast16_t*) value2);
}

double
_fische__guess_frames_per_beat_ (uint_fast16_t* beat_gap_history)
{
    uint_fast16_t gap_history_sorted[30];

    memcpy (gap_history_sorted, beat_gap_history, 30 * sizeof (uint_fast16_t));
    qsort (gap_history_sorted, 30, sizeof (uint_fast16_t), _fische__compare_int_fast16_t_);

    uint_fast16_t guess = gap_history_sorted[14];

    double result = 0;
    int count = 0;

    uint_fast8_t i;
    uint16_t value;
    for (i = 0; i < 30; ++ i) {
        value = gap_history_sorted[i] - guess;
        if (abs(value) <= 2) {
            result += gap_history_sorted[i];
            ++ count;
        }
    }

    return result / count;
}

double
_fische__get_audio_level_ (double* data, uint_fast32_t data_size)
{
    double E = 0;

    uint_fast32_t i;
    for (i = 0; i < data_size; ++ i) {
        E += fabs (* (data + i));
    }

    if (E <= 0) E = 1e-9;
    E /= data_size;

    return log10 (E) * 10;
}

struct fische__analyst*
fische__analyst_new (struct fische* parent) {

    struct fische__analyst* retval = static_cast<fische__analyst*>(malloc(sizeof (struct fische__analyst)));
    retval->priv = static_cast<_fische__analyst_*>(malloc(sizeof(struct _fische__analyst_)));

    struct _fische__analyst_* P = retval->priv;

    P->fische = parent;
    P->bghist_head = 0;
    P->intensity_moving_avg = 0;
    P->intensity_std_dev = 0;
    P->last_beat_frame = 0;
    P->moving_avg_03 = 0;
    P->moving_avg_30 = 0;
    P->state = _FISCHE__WAITING_;
    P->std_dev = 0;

    P->beat_gap_history = static_cast<uint_fast16_t*>(malloc(30 * sizeof(uint_fast16_t)));
    memset (P->beat_gap_history, '\0', 30 * sizeof (uint_fast16_t));

    retval->frames_per_beat = 0;
    retval->relative_energy = 1;

    return retval;
}

void
fische__analyst_free (struct fische__analyst* self)
{
    if (!self)
        return;

    free (self->priv->beat_gap_history);
    free (self->priv);
    free (self);
}

int_fast8_t
fische__analyst_analyse (struct fische__analyst* self,
                         double* data,
                         uint_fast16_t size)
{
    if (!size)
        return -1;

    struct _fische__analyst_* P = self->priv;

    double dezibel = _fische__get_audio_level_ (data, size * 2);

    if (P->moving_avg_30 == 0)
        P->moving_avg_30 = dezibel;
    else
        P->moving_avg_30 = P->moving_avg_30 * 0.9667 + dezibel * 0.0333;

    P->std_dev = P->std_dev * 0.9667 + fabs (dezibel - P->moving_avg_30) * 0.0333;

    uint_fast32_t frameno = P->fische->frame_counter;
    if ( (frameno - P->last_beat_frame) > 90) {
        self->frames_per_beat = 0;
        memset (P->beat_gap_history, '\0', 30 * sizeof (uint_fast16_t));
        P->bghist_head = 0;
    }

    self->relative_energy = P->moving_avg_03 / P->moving_avg_30;

    double relative_intensity = 0;
    double new_frames_per_beat;

    switch (P->state) {
        case _FISCHE__WAITING_:
            // don't bother if intensity too low
            if (dezibel < P->moving_avg_30 + P->std_dev)
                break;

            // initialisation fallbacks
            if (P->std_dev == 0)
                relative_intensity = 1; // avoid div by 0
            else
                relative_intensity = (dezibel - P->moving_avg_30) / P->std_dev;

            if (P->intensity_moving_avg == 0)
                P->intensity_moving_avg = relative_intensity; // initial assignment
            else
                P->intensity_moving_avg = P->intensity_moving_avg * 0.95 + relative_intensity * 0.05;

            // update intensity standard deviation
            P->intensity_std_dev = P->intensity_std_dev * 0.95 + fabs (P->intensity_moving_avg - relative_intensity) * 0.05;

            // we DO have a beat
            P->state = _FISCHE__BEAT_;

            // update beat gap history
            P->beat_gap_history[P->bghist_head++] = frameno - P->last_beat_frame;
            if (P->bghist_head == 30)
                P->bghist_head = 0;

            // remember this as the last beat
            P->last_beat_frame = frameno;

            // reset the short-term moving average
            P->moving_avg_03 = dezibel;

            // try a guess at the tempo
            new_frames_per_beat = _fische__guess_frames_per_beat_ (P->beat_gap_history);
            if ( (self->frames_per_beat) && (self->frames_per_beat / new_frames_per_beat < 1.2) && (new_frames_per_beat / self->frames_per_beat < 1.2))
                self->frames_per_beat = (self->frames_per_beat * 2 + new_frames_per_beat) / 3;
            else
                self->frames_per_beat = new_frames_per_beat;

            // return based on relative beat intensity
            if (relative_intensity > P->intensity_moving_avg + 3 * P->intensity_std_dev)
                return 4;
            if (relative_intensity > P->intensity_moving_avg + 2 * P->intensity_std_dev)
                return 3;
            if (relative_intensity > P->intensity_moving_avg + 1 * P->intensity_std_dev)
                return 2;

            return 1;

        case _FISCHE__BEAT_:
        case _FISCHE__MAYBEWAITING_:
            // update short term moving average
            P->moving_avg_03 = P->moving_avg_03 * 0.6667 + dezibel * 0.3333;

            // needs to be low enough twice to exit BEAT state
            if (P->moving_avg_03 < P->moving_avg_30 + P->std_dev) {
                P->state = P->state == _FISCHE__MAYBEWAITING_ ? _FISCHE__WAITING_ : _FISCHE__MAYBEWAITING_;
                return 0;
            }
    }

    // report level too low
    if (dezibel < -45) return -1;
    return 0;
}
