// Copyright 2023 The Pigweed Authors
//
// 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
//
//     https://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 "pw_result/expected.h"

#include "pw_status/try.h"
#include "pw_string/string.h"
#include "pw_string/string_builder.h"
#include "pw_unit_test/framework.h"

namespace pw {
namespace {

struct Defaults {
  Defaults() = default;
  Defaults(const Defaults&) = default;
  Defaults(Defaults&&) = default;
  Defaults& operator=(const Defaults&) = default;
  Defaults& operator=(Defaults&&) = default;
};

struct NoDefaultConstructor {
  NoDefaultConstructor() = delete;
  NoDefaultConstructor(std::nullptr_t) {}
};

struct NoCopy {
  NoCopy(const NoCopy&) = delete;
  NoCopy(NoCopy&&) = default;
  NoCopy& operator=(const NoCopy&) = delete;
  NoCopy& operator=(NoCopy&&) = default;
};

struct NoCopyNoMove {
  NoCopyNoMove(const NoCopyNoMove&) = delete;
  NoCopyNoMove(NoCopyNoMove&&) = delete;
  NoCopyNoMove& operator=(const NoCopyNoMove&) = delete;
  NoCopyNoMove& operator=(NoCopyNoMove&&) = delete;
};

struct NonTrivialDestructor {
  ~NonTrivialDestructor() {}
};

namespace test_constexpr {
// Expected and unexpected are constexpr types.
constexpr expected<int, int> kExpectedConstexpr1;
constexpr expected<int, int> kExpectedConstexpr2{5};
constexpr expected<int, int> kExpectedConstexpr3 = unexpected<int>(42);
constexpr unexpected<int> kExpectedConstexprUnexpected{50};
static_assert(kExpectedConstexpr1.has_value());
static_assert(kExpectedConstexpr1.value() == 0);
static_assert(kExpectedConstexpr2.has_value());
static_assert(kExpectedConstexpr2.value() == 5);
static_assert(!kExpectedConstexpr3.has_value());
static_assert(kExpectedConstexpr3.error() == 42);
static_assert(kExpectedConstexprUnexpected.error() == 50);
}  // namespace test_constexpr

namespace test_default_construction {
// Default constructible if and only if T is default constructible.
static_assert(
    std::is_default_constructible<expected<Defaults, Defaults>>::value);
static_assert(!std::is_default_constructible<
              expected<NoDefaultConstructor, Defaults>>::value);
static_assert(std::is_default_constructible<
              expected<Defaults, NoDefaultConstructor>>::value);
static_assert(!std::is_default_constructible<
              expected<NoDefaultConstructor, NoDefaultConstructor>>::value);
// Never default constructible.
static_assert(!std::is_default_constructible<unexpected<Defaults>>::value);
static_assert(
    !std::is_default_constructible<unexpected<NoDefaultConstructor>>::value);
}  // namespace test_default_construction

namespace test_copy_construction {
// Copy constructible if and only if both types are copy constructible.
static_assert(std::is_copy_constructible<expected<Defaults, Defaults>>::value);
static_assert(!std::is_copy_constructible<expected<Defaults, NoCopy>>::value);
static_assert(!std::is_copy_constructible<expected<NoCopy, Defaults>>::value);
static_assert(!std::is_copy_constructible<expected<NoCopy, NoCopy>>::value);
// Copy constructible if and only if E is copy constructible.
static_assert(std::is_copy_constructible<unexpected<Defaults>>::value);
static_assert(!std::is_copy_constructible<unexpected<NoCopy>>::value);
}  // namespace test_copy_construction

namespace test_copy_assignment {
// Copy assignable if and only if both types are copy assignable.
static_assert(std::is_copy_assignable<expected<Defaults, Defaults>>::value);
static_assert(!std::is_copy_assignable<expected<Defaults, NoCopy>>::value);
static_assert(!std::is_copy_assignable<expected<NoCopy, Defaults>>::value);
static_assert(!std::is_copy_assignable<expected<NoCopy, NoCopy>>::value);
// Copy assignable if and only if E is copy assignable.
static_assert(std::is_copy_assignable<unexpected<Defaults>>::value);
static_assert(!std::is_copy_assignable<unexpected<NoCopy>>::value);
}  // namespace test_copy_assignment

namespace test_move_construction {
// Move constructible if and only if both types are move constructible.
static_assert(std::is_move_constructible<expected<Defaults, Defaults>>::value);
static_assert(
    !std::is_move_constructible<expected<Defaults, NoCopyNoMove>>::value);
static_assert(
    !std::is_move_constructible<expected<NoCopyNoMove, Defaults>>::value);
static_assert(
    !std::is_move_constructible<expected<NoCopyNoMove, NoCopyNoMove>>::value);
// Move constructible if and only if E is move constructible.
static_assert(std::is_move_constructible<unexpected<Defaults>>::value);
static_assert(!std::is_move_constructible<unexpected<NoCopyNoMove>>::value);
}  // namespace test_move_construction

namespace test_move_assignment {
// Move assignable if and only if both types are move assignable.
static_assert(std::is_move_assignable<expected<Defaults, Defaults>>::value);
static_assert(
    !std::is_move_assignable<expected<Defaults, NoCopyNoMove>>::value);
static_assert(
    !std::is_move_assignable<expected<NoCopyNoMove, Defaults>>::value);
static_assert(
    !std::is_move_assignable<expected<NoCopyNoMove, NoCopyNoMove>>::value);
// Move assignable if and only if E is move assignable.
static_assert(std::is_move_assignable<unexpected<Defaults>>::value);
static_assert(!std::is_move_assignable<unexpected<NoCopyNoMove>>::value);
}  // namespace test_move_assignment

namespace test_trivial_destructor {
// Destructor is trivial if and only if both types are trivially destructible.
static_assert(
    std::is_trivially_destructible<expected<Defaults, Defaults>>::value);
static_assert(!std::is_trivially_destructible<
              expected<NonTrivialDestructor, Defaults>>::value);
static_assert(!std::is_trivially_destructible<
              expected<Defaults, NonTrivialDestructor>>::value);
static_assert(!std::is_trivially_destructible<
              expected<NonTrivialDestructor, NonTrivialDestructor>>::value);
// Destructor is trivial if and only if E is trivially destructible.
static_assert(std::is_trivially_destructible<unexpected<Defaults>>::value);
static_assert(
    !std::is_trivially_destructible<unexpected<NonTrivialDestructor>>::value);
}  // namespace test_trivial_destructor

using SmallString = pw::InlineString<2>;

constexpr char kRecoverySentinel = 0x04;  // Arbitrary non-printable sentinel.

SmallString Itoa(int x) {
  SmallString result;
  if ((StringBuilder(result) << x).status() != OkStatus()) {
    ADD_FAILURE();
  }
  return result;
}

expected<int, std::string_view> FailableFunction1(bool fail, int num) {
  if (fail) {
    return unexpected("FailableFunction1");
  }
  return num;
}

expected<SmallString, std::string_view> FailableFunction2(bool fail, int num) {
  if (fail) {
    return unexpected("FailableFunction2");
  }

  return Itoa(num);
}

expected<int, std::string_view> FailOnOdd(int x) {
  if (x % 2) {
    return unexpected("odd");
  }
  return x;
}

expected<SmallString, std::string_view> ItoaFailOnNegative(int x) {
  if (x < 0) {
    return unexpected("negative");
  }
  return Itoa(x);
}

expected<char, std::string_view> GetSecondChar(std::string_view s) {
  if (s.size() < 2) {
    return unexpected("string too small");
  }
  return s[1];
}

expected<char, std::string_view> RecoverStringTooSmall(std::string_view err) {
  if (err == "string too small") {
    return kRecoverySentinel;
  }
  return unexpected(err);
}

int Decrement(int x) { return x - 1; }

template <class T, class E>
expected<void, E> Consume(const expected<T, E>& e) {
  return e.transform([](auto) {});
}

TEST(ExpectedTest, HoldIntValueSuccess) {
  auto x = FailableFunction1(false, 10);
  ASSERT_TRUE(x.has_value());
  EXPECT_EQ(x.value(), 10);
  EXPECT_EQ(*x, 10);
  EXPECT_EQ(x.value_or(33), 10);
  EXPECT_EQ(x.error_or("no error"), "no error");
}

TEST(ExpectedTest, HoldIntValueFail) {
  auto x = FailableFunction1(true, 10);
  ASSERT_FALSE(x.has_value());
  EXPECT_EQ(x.error(), "FailableFunction1");
  EXPECT_EQ(x.value_or(33), 33);
  EXPECT_EQ(x.error_or("no error"), "FailableFunction1");
}

TEST(ExpectedTest, HoldStringValueSuccess) {
  auto x = FailableFunction2(false, 42);
  ASSERT_TRUE(x.has_value());
  EXPECT_EQ(x.value(), "42");
  EXPECT_EQ(*x, "42");
  EXPECT_EQ(x.value_or("33"), "42");
  EXPECT_EQ(x.error_or("no error"), "no error");
}

TEST(ExpectedTest, HoldStringValueFail) {
  auto x = FailableFunction2(true, 42);
  ASSERT_FALSE(x.has_value());
  EXPECT_EQ(x.error(), "FailableFunction2");
  EXPECT_EQ(x.value_or("33"), "33");
  EXPECT_EQ(x.error_or("no error"), "FailableFunction2");
}

TEST(ExpectedTest, MonadicOperation) {
  auto f = [](expected<int, const char*> value) {
    return value.and_then(FailOnOdd)
        .transform(Decrement)
        .transform(Decrement)
        .and_then(ItoaFailOnNegative)
        .and_then(GetSecondChar);
  };
  EXPECT_EQ(f(26).value_or(0), '4');
  EXPECT_EQ(f(26).error_or("no error"), "no error");
  EXPECT_EQ(f(25).value_or(0), 0);
  EXPECT_EQ(f(25).error_or("no error"), "odd");
  EXPECT_EQ(f(0).value_or(0), 0);
  EXPECT_EQ(f(0).error_or("no error"), "negative");
  EXPECT_EQ(f(4).value_or(0), 0);
  EXPECT_EQ(f(4).error_or("no error"), "string too small");
  EXPECT_TRUE(Consume(f(26)).has_value());
  EXPECT_EQ(Consume(f(25)).error_or("no error"), "odd");
  EXPECT_EQ(Consume(f(0)).error_or("no error"), "negative");
  EXPECT_EQ(Consume(f(4)).error_or("no error"), "string too small");

  EXPECT_EQ(f(26).or_else(RecoverStringTooSmall).value_or(0), '4');
  EXPECT_EQ(f(26).or_else(RecoverStringTooSmall).error_or("no error"),
            "no error");
  EXPECT_EQ(f(25).or_else(RecoverStringTooSmall).value_or(0), 0);
  EXPECT_EQ(f(25).or_else(RecoverStringTooSmall).error_or("no error"), "odd");
  EXPECT_EQ(f(0).or_else(RecoverStringTooSmall).value_or(0), 0);
  EXPECT_EQ(f(0).or_else(RecoverStringTooSmall).error_or("no error"),
            "negative");
  EXPECT_EQ(f(4).or_else(RecoverStringTooSmall).value_or(0), kRecoverySentinel);
  EXPECT_EQ(f(4).or_else(RecoverStringTooSmall).error_or("no error"),
            "no error");
}

class ExpectedVoidTest : public ::testing::Test {
 protected:
  constexpr static expected<void, int> kSuccess{};
  constexpr static int kErrorValue = -5;
  constexpr static int kRecoverableErrorValue = 5;

  expected<void, int> Func1() {
    ++func1_invocations;
    if (func1_result.has_value()) {
      return unexpected(*func1_result);
    }
    return kSuccess;
  }

  expected<void, int> Func2() {
    ++func2_invocations;
    if (func2_result.has_value()) {
      return unexpected(*func2_result);
    }
    return kSuccess;
  }

  expected<void, int> Func3() {
    ++func3_invocations;
    if (func3_result.has_value()) {
      return unexpected(*func3_result);
    }
    return kSuccess;
  }

  expected<void, int> RecoverIfNotNegative(int err) {
    ++recover_if_not_negative_invocations;
    if (err < 0) {
      return unexpected(err);
    }
    return kSuccess;
  }

  void Func1Returns(std::optional<int> value) { func1_result = value; }
  void Func2Returns(std::optional<int> value) { func2_result = value; }
  void Func3Returns(std::optional<int> value) { func3_result = value; }

  size_t CountFunc1Invocations() { return func1_invocations; }
  size_t CountFunc2Invocations() { return func2_invocations; }
  size_t CountFunc3Invocations() { return func3_invocations; }
  size_t CountRecoverIfNotNegativeInvocations() {
    return recover_if_not_negative_invocations;
  }

  template <typename Return, typename... Args>
  auto Bind(Return (ExpectedVoidTest::*member_func)(Args...)) {
    return [this, member_func](Args... args) {
      return (this->*member_func)(args...);
    };
  }

  expected<void, int> RunSequence() {
    using Self = ExpectedVoidTest;
    return Func1()
        .and_then(Bind(&Self::Func2))
        .and_then(Bind(&Self::Func3))
        .or_else(Bind(&Self::RecoverIfNotNegative));
  }

 private:
  std::optional<int> func1_result = std::nullopt;
  std::optional<int> func2_result = std::nullopt;
  std::optional<int> func3_result = std::nullopt;

  size_t func1_invocations = 0;
  size_t func2_invocations = 0;
  size_t func3_invocations = 0;
  size_t recover_if_not_negative_invocations = 0;
};

TEST_F(ExpectedVoidTest, VoidAllSuccess) {
  EXPECT_TRUE(RunSequence().has_value());

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(1u, CountFunc2Invocations());
  EXPECT_EQ(1u, CountFunc3Invocations());
  EXPECT_EQ(0u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureFirst) {
  Func1Returns(kErrorValue);

  auto result = RunSequence();
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(result.error(), kErrorValue);

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(0u, CountFunc2Invocations());
  EXPECT_EQ(0u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureMiddle) {
  Func2Returns(kErrorValue);

  auto result = RunSequence();
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(result.error(), kErrorValue);

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(1u, CountFunc2Invocations());
  EXPECT_EQ(0u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureLast) {
  Func3Returns(kErrorValue);

  auto result = RunSequence();
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(result.error(), kErrorValue);

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(1u, CountFunc2Invocations());
  EXPECT_EQ(1u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureFirstRecovers) {
  Func1Returns(kRecoverableErrorValue);

  auto result = RunSequence();
  EXPECT_TRUE(result.has_value());

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(0u, CountFunc2Invocations());
  EXPECT_EQ(0u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureMiddleRecovers) {
  Func2Returns(kRecoverableErrorValue);

  auto result = RunSequence();
  EXPECT_TRUE(result.has_value());

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(1u, CountFunc2Invocations());
  EXPECT_EQ(0u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidOneFailureLastRecovers) {
  Func3Returns(kRecoverableErrorValue);

  auto result = RunSequence();
  EXPECT_TRUE(result.has_value());

  EXPECT_EQ(1u, CountFunc1Invocations());
  EXPECT_EQ(1u, CountFunc2Invocations());
  EXPECT_EQ(1u, CountFunc3Invocations());
  EXPECT_EQ(1u, CountRecoverIfNotNegativeInvocations());
}

TEST_F(ExpectedVoidTest, VoidTransform) {
  auto result = expected<void, const char*>().transform([] { return 100; });
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(*result, 100);
  EXPECT_EQ(result, 100);
}

TEST_F(ExpectedVoidTest, VoidTransformError) {
  auto result = expected<void, int>(unexpect, 100).transform_error([](int x) {
    return x * 2;
  });
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(result.error(), 200);
}

}  // namespace
}  // namespace pw
