/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

/* Resamples a signal to an arbitrary rate. Used by the AEC to compensate for clock
 * skew by resampling the farend signal.
 */

#include "aec_resampler.h"

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

#include "aec_core.h"

enum { kEstimateLengthFrames = 400 };

typedef struct {
    short buffer[kResamplerBufferSize];
    float position;

    int deviceSampleRateHz;
    int skewData[kEstimateLengthFrames];
    int skewDataIndex;
    float skewEstimate;
} resampler_t;

static int EstimateSkew(const int* rawSkew,
                        int size,
                        int absLimit,
                        float *skewEst);

int WebRtcAec_CreateResampler(void **resampInst)
{
    resampler_t *obj = malloc(sizeof(resampler_t));
    *resampInst = obj;
    if (obj == NULL) {
        return -1;
    }

    return 0;
}

int WebRtcAec_InitResampler(void *resampInst, int deviceSampleRateHz)
{
    resampler_t *obj = (resampler_t*) resampInst;
    memset(obj->buffer, 0, sizeof(obj->buffer));
    obj->position = 0.0;

    obj->deviceSampleRateHz = deviceSampleRateHz;
    memset(obj->skewData, 0, sizeof(obj->skewData));
    obj->skewDataIndex = 0;
    obj->skewEstimate = 0.0;

    return 0;
}

int WebRtcAec_FreeResampler(void *resampInst)
{
    resampler_t *obj = (resampler_t*) resampInst;
    free(obj);

    return 0;
}

void WebRtcAec_ResampleLinear(void *resampInst,
                              const short *inspeech,
                              int size,
                              float skew,
                              short *outspeech,
                              int *size_out)
{
    resampler_t *obj = (resampler_t*) resampInst;

    short *y;
    float be, tnew, interp;
    int tn, mm;

    assert(!(size < 0 || size > 2 * FRAME_LEN));
    assert(resampInst != NULL);
    assert(inspeech != NULL);
    assert(outspeech != NULL);
    assert(size_out != NULL);

    // Add new frame data in lookahead
    memcpy(&obj->buffer[FRAME_LEN + kResamplingDelay],
           inspeech,
           size * sizeof(short));

    // Sample rate ratio
    be = 1 + skew;

    // Loop over input frame
    mm = 0;
    y = &obj->buffer[FRAME_LEN]; // Point at current frame

    tnew = be * mm + obj->position;
    tn = (int) tnew;

    while (tn < size) {

        // Interpolation
        interp = y[tn] + (tnew - tn) * (y[tn+1] - y[tn]);

        if (interp > 32767) {
            interp = 32767;
        }
        else if (interp < -32768) {
            interp = -32768;
        }

        outspeech[mm] = (short) interp;
        mm++;

        tnew = be * mm + obj->position;
        tn = (int) tnew;
    }

    *size_out = mm;
    obj->position += (*size_out) * be - size;

    // Shift buffer
    memmove(obj->buffer,
            &obj->buffer[size],
            (kResamplerBufferSize - size) * sizeof(short));
}

int WebRtcAec_GetSkew(void *resampInst, int rawSkew, float *skewEst)
{
    resampler_t *obj = (resampler_t*)resampInst;
    int err = 0;

    if (obj->skewDataIndex < kEstimateLengthFrames) {
        obj->skewData[obj->skewDataIndex] = rawSkew;
        obj->skewDataIndex++;
    }
    else if (obj->skewDataIndex == kEstimateLengthFrames) {
        err = EstimateSkew(obj->skewData,
                           kEstimateLengthFrames,
                           obj->deviceSampleRateHz,
                           skewEst);
        obj->skewEstimate = *skewEst;
        obj->skewDataIndex++;
    }
    else {
        *skewEst = obj->skewEstimate;
    }

    return err;
}

int EstimateSkew(const int* rawSkew,
                 int size,
                 int deviceSampleRateHz,
                 float *skewEst)
{
    const int absLimitOuter = (int)(0.04f * deviceSampleRateHz);
    const int absLimitInner = (int)(0.0025f * deviceSampleRateHz);
    int i = 0;
    int n = 0;
    float rawAvg = 0;
    float err = 0;
    float rawAbsDev = 0;
    int upperLimit = 0;
    int lowerLimit = 0;
    float cumSum = 0;
    float x = 0;
    float x2 = 0;
    float y = 0;
    float xy = 0;
    float xAvg = 0;
    float denom = 0;
    float skew = 0;

    *skewEst = 0; // Set in case of error below.
    for (i = 0; i < size; i++) {
      if ((rawSkew[i] < absLimitOuter && rawSkew[i] > -absLimitOuter)) {
        n++;
        rawAvg += rawSkew[i];
      }
    }

    if (n == 0) {
      return -1;
    }
    assert(n > 0);
    rawAvg /= n;

    for (i = 0; i < size; i++) {
      if ((rawSkew[i] < absLimitOuter && rawSkew[i] > -absLimitOuter)) {
        err = rawSkew[i] - rawAvg;
        rawAbsDev += err >= 0 ? err : -err;
      }
    }
    assert(n > 0);
    rawAbsDev /= n;
    upperLimit = (int)(rawAvg + 5 * rawAbsDev + 1); // +1 for ceiling.
    lowerLimit = (int)(rawAvg - 5 * rawAbsDev - 1); // -1 for floor.

    n = 0;
    for (i = 0; i < size; i++) {
        if ((rawSkew[i] < absLimitInner && rawSkew[i] > -absLimitInner) ||
            (rawSkew[i] < upperLimit && rawSkew[i] > lowerLimit)) {
            n++;
            cumSum += rawSkew[i];
            x += n;
            x2 += n*n;
            y += cumSum;
            xy += n * cumSum;
        }
    }

    if (n == 0) {
      return -1;
    }
    assert(n > 0);
    xAvg = x / n;
    denom = x2 - xAvg*x;

    if (denom != 0) {
        skew = (xy - xAvg*y) / denom;
    }

    *skewEst = skew;
    return 0;
}
