/*
 * Copyright 2016 Facebook, Inc.
 *
 * 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 <gtest/gtest.h>

#include <folly/futures/Future.h>

using namespace folly;

TEST(Reduce, basic) {
  auto makeFutures = [](int count) {
    std::vector<Future<int>> fs;
    for (int i = 1; i <= count; ++i) {
      fs.emplace_back(makeFuture(i));
    }
    return fs;
  };

  // Empty (Try)
  {
    auto fs = makeFutures(0);

    Future<double> f1 = reduce(fs, 1.2,
      [](double a, Try<int>&& b){
        return a + *b + 0.1;
      });
    EXPECT_EQ(1.2, f1.get());
  }

  // One (Try)
  {
    auto fs = makeFutures(1);

    Future<double> f1 = reduce(fs, 0.0,
      [](double a, Try<int>&& b){
        return a + *b + 0.1;
      });
    EXPECT_EQ(1.1, f1.get());
  }

  // Returning values (Try)
  {
    auto fs = makeFutures(3);

    Future<double> f1 = reduce(fs, 0.0,
      [](double a, Try<int>&& b){
        return a + *b + 0.1;
      });
    EXPECT_EQ(6.3, f1.get());
  }

  // Returning values
  {
    auto fs = makeFutures(3);

    Future<double> f1 = reduce(fs, 0.0,
      [](double a, int&& b){
        return a + b + 0.1;
      });
    EXPECT_EQ(6.3, f1.get());
  }

  // Returning futures (Try)
  {
    auto fs = makeFutures(3);

    Future<double> f2 = reduce(fs, 0.0,
      [](double a, Try<int>&& b){
        return makeFuture<double>(a + *b + 0.1);
      });
    EXPECT_EQ(6.3, f2.get());
  }

  // Returning futures
  {
    auto fs = makeFutures(3);

    Future<double> f2 = reduce(fs, 0.0,
      [](double a, int&& b){
        return makeFuture<double>(a + b + 0.1);
      });
    EXPECT_EQ(6.3, f2.get());
  }
}

TEST(Reduce, chain) {
  auto makeFutures = [](int count) {
    std::vector<Future<int>> fs;
    for (int i = 1; i <= count; ++i) {
      fs.emplace_back(makeFuture(i));
    }
    return fs;
  };

  {
    auto f = collectAll(makeFutures(3)).reduce(0, [](int a, Try<int>&& b){
      return a + *b;
    });
    EXPECT_EQ(6, f.get());
  }
  {
    auto f = collect(makeFutures(3)).reduce(0, [](int a, int&& b){
      return a + b;
    });
    EXPECT_EQ(6, f.get());
  }
}

TEST(Reduce, unorderedReduce) {
  {
    std::vector<Future<int>> fs;
    fs.push_back(makeFuture(1));
    fs.push_back(makeFuture(2));
    fs.push_back(makeFuture(3));

    Future<double> f =
        unorderedReduce(fs.begin(),
                        fs.end(),
                        0.0,
                        [](double /* a */, int&& b) { return double(b); });
    EXPECT_EQ(3.0, f.get());
  }
  {
    Promise<int> p1;
    Promise<int> p2;
    Promise<int> p3;

    std::vector<Future<int>> fs;
    fs.push_back(p1.getFuture());
    fs.push_back(p2.getFuture());
    fs.push_back(p3.getFuture());

    Future<double> f =
        unorderedReduce(fs.begin(),
                        fs.end(),
                        0.0,
                        [](double /* a */, int&& b) { return double(b); });
    p3.setValue(3);
    p2.setValue(2);
    p1.setValue(1);
    EXPECT_EQ(1.0, f.get());
  }
}

TEST(Reduce, unorderedReduceException) {
  Promise<int> p1;
  Promise<int> p2;
  Promise<int> p3;

  std::vector<Future<int>> fs;
  fs.push_back(p1.getFuture());
  fs.push_back(p2.getFuture());
  fs.push_back(p3.getFuture());

  Future<double> f =
      unorderedReduce(fs.begin(),
                      fs.end(),
                      0.0,
                      [](double /* a */, int&& b) { return b + 0.0; });
  p3.setValue(3);
  p2.setException(exception_wrapper(std::runtime_error("blah")));
  p1.setValue(1);
  EXPECT_THROW(f.get(), std::runtime_error);
}
