/* Copyright 2016, Ableton AG, Berlin. All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you would like to incorporate Link into a proprietary software application,
 *  please contact <link-devs@ableton.com>.
 */

#pragma once

#include <array>
#include <cfloat>
#include <cmath>


namespace ableton
{
namespace link
{

template <std::size_t n>
struct Kalman
{
  Kalman()
    : mValue(0)
    , mCoVariance(1)
    , mVarianceLength(n)
    , mCounter(mVarianceLength)
  {
  }

  double getValue()
  {
    return mValue;
  }

  double calculateVVariance()
  {
    auto vVar = 0.;
    auto meanOfDiffs = 0.;

    for (size_t k = 0; k < (mVarianceLength); k++)
    {
      meanOfDiffs += (mMeasuredValues[k] - mFilterValues[k]);
    }

    meanOfDiffs /= (mVarianceLength);

    for (size_t i = 0; i < (mVarianceLength); i++)
    {
      vVar += (pow(mMeasuredValues[i] - mFilterValues[i] - meanOfDiffs, 2.0));
    }

    vVar /= (mVarianceLength - 1);

    return vVar;
  }

  double calculateWVariance()
  {
    auto wVar = 0.;
    auto meanOfDiffs = 0.;

    for (size_t k = 0; k < (mVarianceLength); k++)
    {
      meanOfDiffs += (mFilterValues[(mCounter - k - 1) % mVarianceLength]
                      - mFilterValues[(mCounter - k - 2) % mVarianceLength]);
    }

    meanOfDiffs /= (mVarianceLength);

    for (size_t i = 0; i < (mVarianceLength); i++)
    {
      wVar += (pow(mFilterValues[(mCounter - i - 1) % mVarianceLength]
                     - mFilterValues[(mCounter - i - 2) % mVarianceLength] - meanOfDiffs,
        2.0));
    }

    wVar /= (mVarianceLength - 1);

    return wVar;
  }

  void iterate(const double value)
  {
    const std::size_t currentIndex = mCounter % mVarianceLength;
    mMeasuredValues[currentIndex] = value;

    if (mCounter < (mVarianceLength + mVarianceLength))
    {
      if (mCounter == mVarianceLength)
      {
        mValue = value;
      }
      else
      {
        mValue = (mValue + value) / 2;
      }
    }
    else
    {
      // prediction equations
      const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength];
      mFilterValues[currentIndex] = prevFilterValue;
      const auto wVariance = calculateWVariance();
      const double coVarianceEstimation = mCoVariance + wVariance;

      // update equations
      const auto vVariance = calculateVVariance();
      // Gain defines how easily the filter will adjust to a new condition
      // With gain = 1 the output equals the input, with gain = 0 the input
      // is ignored and the output equals the last filtered value
      const auto divisor = coVarianceEstimation + vVariance;
      const auto gain = divisor != 0. ? coVarianceEstimation / divisor : 0.7;
      mValue = prevFilterValue + gain * (value - prevFilterValue);
      mCoVariance = (1 - gain) * coVarianceEstimation;
    }
    mFilterValues[currentIndex] = mValue;

    ++mCounter;
  }

  double mValue;
  double mCoVariance;
  size_t mVarianceLength;
  size_t mCounter;
  std::array<double, n> mFilterValues;
  std::array<double, n> mMeasuredValues;
};

} // namespace link
} // namespace ableton
