/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "BenchmarkStorageParent.h"
#include "KeyValueStorage.h"

namespace mozilla {

/* Moving average window size. */
const int32_t AVG_WINDOW = 20;
/* Calculate the moving average for the new value aValue for the current window
 * aWindow and the already existing average aAverage. When the method returns
 * aAverage will contain the new average and aWindow will contain the new
 * window.*/
void BenchmarkStorageParent::MovingAverage(int32_t& aAverage, int32_t& aWindow,
                                           const int32_t aValue) {
  if (aWindow < AVG_WINDOW) {
    aAverage = (aAverage * aWindow + aValue) / (aWindow + 1);
    aWindow++;
    return;
  }
  MOZ_ASSERT(aWindow == AVG_WINDOW);
  aAverage = (aAverage - aAverage / aWindow) + (aValue / aWindow);
}

/* In order to decrease the number of times the database is accessed when the
 * moving average is stored or retrieved we use the same value to store _both_
 * the window and the average. The range of the average is limited since it is
 * a percentage (0-100), and the range of the window is limited
 * (1-20). Thus the number that is stored in the database is in the form
 * (digits): WWAAA. For example, the value stored when an average(A) of 88
 * corresponds to a window(W) 7 is 7088. The average of 100 that corresponds to
 * a window of 20 is 20100. The following methods are helpers to extract or
 * construct the stored value according to the above. */

/* Stored value will be in the form WWAAA(19098). We need to extract the window
 * (19) and the average score (98). The aValue will
 * be parsed, the aWindow will contain the window (of the moving average) and
 * the return value will contain the average itself. */
int32_t BenchmarkStorageParent::ParseStoredValue(int32_t aValue,
                                                 int32_t& aWindow) {
  MOZ_ASSERT(aValue > 999);
  MOZ_ASSERT(aValue < 100000);

  int32_t score = aValue % 1000;
  aWindow = (aValue / 1000) % 100;
  return score;
}

int32_t BenchmarkStorageParent::PrepareStoredValue(int32_t aScore,
                                                   int32_t aWindow) {
  MOZ_ASSERT(aScore >= 0);
  MOZ_ASSERT(aScore <= 100);
  MOZ_ASSERT(aWindow > 0);
  MOZ_ASSERT(aWindow < 21);

  return aWindow * 1000 + aScore;
}

BenchmarkStorageParent::BenchmarkStorageParent()
    : mStorage(new KeyValueStorage) {}

IPCResult BenchmarkStorageParent::RecvPut(const nsCString& aDbName,
                                          const nsCString& aKey,
                                          const int32_t& aValue) {
  // In order to calculate and store the new moving average, we need to get the
  // stored value and window first, to calculate the new score and window, and
  // then to store the new aggregated value.
  mStorage->Get(aDbName, aKey)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [storage = mStorage, aDbName, aKey, aValue](int32_t aResult) {
            int32_t window = 0;
            int32_t average = 0;
            if (aResult >= 0) {
              // The key found.
              average = ParseStoredValue(aResult, window);
            }
            MovingAverage(average, window, aValue);
            int32_t newValue = PrepareStoredValue(average, window);
            // Avoid storing if the values are the same. This is an optimization
            // to minimize the disk usage.
            if (aResult != newValue) {
              storage->Put(aDbName, aKey, newValue);
            }
          },
          [](nsresult rv) { /*do nothing*/ });

  return IPC_OK();
}

IPCResult BenchmarkStorageParent::RecvGet(const nsCString& aDbName,
                                          const nsCString& aKey,
                                          GetResolver&& aResolve) {
  mStorage->Get(aDbName, aKey)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [aResolve](int32_t aResult) {
            int32_t window = 0;  // not used
            aResolve(aResult < 0 ? -1 : ParseStoredValue(aResult, window));
          },
          [aResolve](nsresult rv) { aResolve(-1); });

  return IPC_OK();
}

IPCResult BenchmarkStorageParent::RecvCheckVersion(const nsCString& aDbName,
                                                   int32_t aVersion) {
  mStorage->Get(aDbName, "Version"_ns)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [storage = mStorage, aDbName, aVersion](int32_t aResult) {
            if (aVersion != aResult) {
              storage->Clear(aDbName)->Then(
                  GetCurrentSerialEventTarget(), __func__,
                  [storage, aDbName, aVersion](bool) {
                    storage->Put(aDbName, "Version"_ns, aVersion);
                  },
                  [](nsresult rv) { /*do nothing*/ });
            }
          },
          [](nsresult rv) { /*do nothing*/ });

  return IPC_OK();
}

};  // namespace mozilla
