// Copyright 2008 Google Inc.
// Authors: Craig Silverstein, Lincoln Smith
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef OPEN_VCDIFF_TESTING_H_
#define OPEN_VCDIFF_TESTING_H_

#include <config.h>
#include <assert.h>
#include <stdint.h>  // int64_t
#include <stdlib.h>  // rand
#include <time.h>  // gettimeofday
#include "gtest/gtest.h"

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>  // struct timeval
#endif  // HAVE_SYS_TIME_H

#ifdef HAVE_WINDOWS_H
#include <windows.h>  // QueryPerformanceCounter
#endif  // HAVE_WINDOWS_H

// CHECK is used for assertions that verify the consistency of the test itself,
// rather than correctness of the code that is being tested.
//
// It is better to use a preprocessor macro for CHECK
// than an inline function, because assert() may report
// the source file and line where the failure occurred.
//
// Putting parentheses around the macro arguments
// (e.g. "assert((X) == (Y))") would be good practice
// but would produce error messages that are inconsistent
// with those expected in the unit tests.

#define CHECK(CONDITION) assert(CONDITION)
#define CHECK_EQ(X, Y) assert(X == Y)
#define CHECK_NE(X, Y) assert(X != Y)
#define CHECK_GE(X, Y) assert(X >= Y)
#define CHECK_GT(X, Y) assert(X > Y)
#define CHECK_LE(X, Y) assert(X <= Y)
#define CHECK_LT(X, Y) assert(X < Y)

namespace open_vcdiff {

// Support for timing tests
#if defined(HAVE_GETTIMEOFDAY)
class CycleTimer {
 public:
  inline CycleTimer() {
    Reset();
  }

  inline void Reset() {
    start_time_.tv_sec = 0;
    start_time_.tv_usec = 0;
    cumulative_time_in_usec_ = 0;
  }

  inline void Start() {
    CHECK(!IsStarted());
    gettimeofday(&start_time_, NULL);
  }

  inline void Restart() {
    Reset();
    Start();
  }

  inline void Stop() {
    struct timeval end_time;
    gettimeofday(&end_time, NULL);
    CHECK(IsStarted());
    cumulative_time_in_usec_ +=
        (1000000 * (end_time.tv_sec - start_time_.tv_sec))
        + end_time.tv_usec - start_time_.tv_usec;
    start_time_.tv_sec = 0;
    start_time_.tv_usec = 0;
  }

  inline int64_t GetInUsec() {
    return cumulative_time_in_usec_;
  }

 private:
  inline bool IsStarted() {
    return (start_time_.tv_usec > 0) || (start_time_.tv_sec > 0);
  }

  struct timeval start_time_;
  int64_t cumulative_time_in_usec_;
};
#elif defined(HAVE_QUERYPERFORMANCECOUNTER)
class CycleTimer {
 public:
  inline CycleTimer() {
    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);  // counts per second
    usecs_per_count_ = 1000000.0 / static_cast<double>(frequency.QuadPart);
    Reset();
  }

  inline void Reset() {
    start_time_.QuadPart = 0;
    cumulative_time_in_usec_ = 0;
  }

  inline void Start() {
    CHECK(!IsStarted());
    QueryPerformanceCounter(&start_time_);
  }

  inline void Restart() {
    Reset();
    Start();
  }

  inline void Stop() {
    LARGE_INTEGER end_time;
    QueryPerformanceCounter(&end_time);
    CHECK(IsStarted());
    double count_diff = static_cast<double>(
        end_time.QuadPart - start_time_.QuadPart);
    cumulative_time_in_usec_ +=
        static_cast<int64_t>(count_diff * usecs_per_count_);
    start_time_.QuadPart = 0;
  }

  inline int64_t GetInUsec() {
    return cumulative_time_in_usec_;
  }

 private:
  inline bool IsStarted() {
    return start_time_.QuadPart > 0;
  }

  LARGE_INTEGER start_time_;
  int64_t cumulative_time_in_usec_;
  double usecs_per_count_;
};
#else
#error CycleTimer needs an implementation that does not use gettimeofday or QueryPerformanceCounter
#endif  // HAVE_GETTIMEOFDAY

// This function returns a pseudo-random value of type IntType between 0 and
// limit.  It uses the standard rand() function to produce the value, and makes
// as many calls to rand() as needed to ensure that the values returned can fall
// within the full range specified.  It is slow, so don't include calls to this
// function when calculating the execution time of tests.
//
template<typename IntType>
inline IntType PortableRandomInRange(IntType limit) {
  uint64_t value = rand();
  double rand_limit = RAND_MAX;  // The maximum possible value
  while (rand_limit < limit) {
    // value is multiplied by (RAND_MAX + 1) each iteration. This factor will be
    // canceled out when we divide by rand_limit to get scaled_value, below.
    value = (value * (static_cast<uint64_t>(RAND_MAX) + 1)) + rand();
    rand_limit = (rand_limit * (RAND_MAX + 1.0)) + RAND_MAX;
  }
  // Translate the random 64-bit integer into a floating-point value between
  // 0.0 (inclusive) and 1.0 (inclusive).
  const double scaled_value = value / rand_limit;
  return static_cast<IntType>(limit * scaled_value);
}

}  // namespace open_vcdiff

#endif  // OPEN_VCDIFF_TESTING_H_
