/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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.
 */

#include <folly/Benchmark.h>

#include <algorithm>
#include <iostream>
#include <numeric>
#include <random>
#include <vector>

#include <folly/String.h>
#include <folly/container/Foreach.h>

using namespace folly;
using namespace std;

BENCHMARK_COUNTERS(insertVectorBeginWithCounter, counters, n) {
  vector<int> v;
  for (size_t i = 0; i < n; i++) {
    v.insert(v.begin(), 42);
  }
  BENCHMARK_SUSPEND {
    counters["foo"] = v.size();
    counters["bar"] = v.size() * 2;
  }
}

void fun() {
  static double x = 1;
  ++x;
  doNotOptimizeAway(x);
}
BENCHMARK(bmFun) {
  fun();
}
BENCHMARK(bmRepeatedFun, n) {
  FOR_EACH_RANGE (i, 0, n) { fun(); }
}
BENCHMARK_DRAW_LINE();

BENCHMARK(gun) {
  static double x = 1;
  x *= 2000;
  doNotOptimizeAway(x);
}

BENCHMARK_DRAW_LINE();

BENCHMARK(optimizerCanDiscardTrivial, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    for (long j = 0; j < 10000; ++j) {
      x += j;
    }
  }
}

BENCHMARK(optimizerCanPowerReduceInner1Trivial, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
    }
    doNotOptimizeAway(x);
  }
}

BENCHMARK(optimizerCanPowerReduceInner2Trivial, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    makeUnpredictable(i);
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
    }
  }
  doNotOptimizeAway(x);
}

BENCHMARK(optimizerDisabled1Trivial, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
      doNotOptimizeAway(x);
    }
  }
}

BENCHMARK(optimizerDisabled2Trivial, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    makeUnpredictable(i);
    for (long j = 0; j < 10000; ++j) {
      makeUnpredictable(j);
      x += i + j;
    }
  }
  doNotOptimizeAway(x);
}

BENCHMARK(optimizerCanPowerReduceInner1TrivialPtr, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
    }
    doNotOptimizeAway(&x);
  }
}

BENCHMARK(optimizerCanPowerReduceInner2TrivialPtr, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    makeUnpredictable(i);
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
    }
  }
  doNotOptimizeAway(&x);
}

BENCHMARK(optimizerDisabled1TrivialPtr, n) {
  long x = 0;
  for (long i = 0; i < n; ++i) {
    for (long j = 0; j < 10000; ++j) {
      x += i + j;
      doNotOptimizeAway(&x);
    }
  }
}

namespace {
class NonTrivialLong {
 public:
  explicit NonTrivialLong(long v) : value_(v) {}
  virtual ~NonTrivialLong() {}

  void operator++() { ++value_; }
  void operator+=(long rhs) { value_ += rhs; }
  void operator+=(const NonTrivialLong& rhs) { value_ += rhs.value_; }
  bool operator<(long rhs) { return value_ < rhs; }
  NonTrivialLong operator+(const NonTrivialLong& rhs) {
    return NonTrivialLong(value_ + rhs.value_);
  }

 private:
  long value_;
  long otherStuff_[3];
};
} // namespace

BENCHMARK(optimizerCanDiscardNonTrivial, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += j;
    }
  }
}

BENCHMARK(optimizerCanPowerReduceInner1NonTrivial, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
    }
    doNotOptimizeAway(x);
  }
}

BENCHMARK(optimizerCanPowerReduceInner2NonTrivial, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    makeUnpredictable(i);
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
    }
  }
  doNotOptimizeAway(x);
}

BENCHMARK(optimizerDisabled1NonTrivial, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
      doNotOptimizeAway(x);
    }
  }
}

BENCHMARK(optimizerDisabled2NonTrivial, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    makeUnpredictable(i);
    for (NonTrivialLong j(0); j < 10000; ++j) {
      makeUnpredictable(j);
      x += i + j;
    }
  }
  doNotOptimizeAway(x);
}

BENCHMARK(optimizerCanPowerReduceInner1NonTrivialPtr, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
    }
    doNotOptimizeAway(&x);
  }
}

BENCHMARK(optimizerCanPowerReduceInner2NonTrivialPtr, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    makeUnpredictable(i);
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
    }
  }
  doNotOptimizeAway(&x);
}

BENCHMARK(optimizerDisabled1NonTrivialPtr, n) {
  NonTrivialLong x(0);
  for (NonTrivialLong i(0); i < n; ++i) {
    for (NonTrivialLong j(0); j < 10000; ++j) {
      x += i + j;
      doNotOptimizeAway(&x);
    }
  }
}

BENCHMARK_DRAW_LINE();

BENCHMARK(baselinevector) {
  vector<int> v;

  BENCHMARK_SUSPEND { v.resize(1000); }

  FOR_EACH_RANGE (i, 0, 100) { v.push_back(42); }
}

BENCHMARK_RELATIVE(bmVector) {
  vector<int> v;
  FOR_EACH_RANGE (i, 0, 100) { v.resize(v.size() + 1, 42); }
}

BENCHMARK_DRAW_LINE();

BENCHMARK(superslow) {
  sleep(1);
}

BENCHMARK_DRAW_LINE();

BENCHMARK(noMulti) {
  fun();
}

BENCHMARK_MULTI(multiSimple) {
  FOR_EACH_RANGE (i, 0, 10) { fun(); }
  return 10;
}

BENCHMARK_RELATIVE_MULTI(multiSimpleRel) {
  FOR_EACH_RANGE (i, 0, 10) {
    fun();
    fun();
  }
  return 10;
}

BENCHMARK_MULTI(multiIterArgs, iter) {
  FOR_EACH_RANGE (i, 0, 10 * iter) { fun(); }
  return 10 * iter;
}

BENCHMARK_RELATIVE_MULTI(multiIterArgsRel, iter) {
  FOR_EACH_RANGE (i, 0, 10 * iter) {
    fun();
    fun();
  }
  return 10 * iter;
}

unsigned paramMulti(unsigned iter, unsigned num) {
  for (unsigned i = 0; i < iter; ++i) {
    for (unsigned j = 0; j < num; ++j) {
      fun();
    }
  }
  return num * iter;
}

unsigned paramMultiRel(unsigned iter, unsigned num) {
  for (unsigned i = 0; i < iter; ++i) {
    for (unsigned j = 0; j < num; ++j) {
      fun();
      fun();
    }
  }
  return num * iter;
}

BENCHMARK_PARAM_MULTI(paramMulti, 1)
BENCHMARK_RELATIVE_PARAM_MULTI(paramMultiRel, 1)

BENCHMARK_PARAM_MULTI(paramMulti, 5)
BENCHMARK_RELATIVE_PARAM_MULTI(paramMultiRel, 5)

BENCHMARK_DRAW_LINE();

BENCHMARK(BenchmarkSuspender_dismissing_void, iter) {
  BenchmarkSuspender braces;
  mt19937_64 rng;
  while (iter--) {
    vector<size_t> v(1 << 12, 0);
    iota(v.begin(), v.end(), 0);
    shuffle(v.begin(), v.end(), rng);
    braces.dismissing([&] { sort(v.begin(), v.end()); });
  }
}

BENCHMARK(BenchmarkSuspender_dismissing_value, iter) {
  BenchmarkSuspender braces;
  mt19937_64 rng;
  while (iter--) {
    vector<size_t> v(1 << 12, 0);
    iota(v.begin(), v.end(), 0);
    shuffle(v.begin(), v.end(), rng);
    auto s = braces.dismissing([&] {
      sort(v.begin(), v.end());
      return accumulate(
          v.begin(), v.end(), 0, [](size_t a, size_t e) { return a + e; });
    });
    doNotOptimizeAway(s);
  }
}

int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  folly::addBenchmark("-", std::string("string_name"), [] { return 0; });
  runBenchmarks();
  runBenchmarksOnFlag();
}
