/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 "chre/util/non_copyable.h"
#include "chre/util/optional.h"

using chre::Optional;

class DestructorTestingObject {
 public:
  ~DestructorTestingObject() {
    if (valueToFlipWhenDestruct != nullptr) {
      *valueToFlipWhenDestruct = !*valueToFlipWhenDestruct;
    }
  }

  void setValueToFlipWhenDestruct(bool *value) {
    valueToFlipWhenDestruct = value;
  }

 private:
  bool *valueToFlipWhenDestruct = nullptr;
};

TEST(Optional, ShouldDestructContainedObject) {
  bool destructed = false;
  {
    Optional<DestructorTestingObject> object(DestructorTestingObject{});
    object.value().setValueToFlipWhenDestruct(&destructed);
  }
  EXPECT_TRUE(destructed);
}

TEST(Optional, NoValueByDefault) {
  Optional<int> myInt;
  EXPECT_FALSE(myInt.has_value());
}

TEST(Optional, NonDefaultValueByDefault) {
  Optional<int> myInt(0x1337);
  EXPECT_TRUE(myInt.has_value());
  EXPECT_EQ(*myInt, 0x1337);
}

TEST(Optional, NonDefaultMovedValueByDefault) {
  Optional<int> myInt(std::move(0x1337));
  EXPECT_TRUE(myInt.has_value());
  EXPECT_EQ(*myInt, 0x1337);
}

TEST(Optional, CopyConstruct) {
  Optional<int> myInt(0x1337);
  Optional<int> myNewInt(myInt);
  EXPECT_TRUE(myNewInt.has_value());
  EXPECT_EQ(*myNewInt, 0x1337);
}

TEST(Optional, CopyConstructConst) {
  const Optional<int> myInt(0x1337);
  Optional<int> myNewInt(myInt);
  EXPECT_TRUE(myNewInt.has_value());
  EXPECT_EQ(*myNewInt, 0x1337);
}

TEST(Optional, CopyAssignAndRead) {
  Optional<int> myInt;
  EXPECT_FALSE(myInt.has_value());
  myInt = 0x1337;
  EXPECT_EQ(*myInt, 0x1337);
  EXPECT_TRUE(myInt.has_value());
  myInt.reset();
  EXPECT_FALSE(myInt.has_value());
}

TEST(Optional, MoveAssignAndRead) {
  Optional<int> myInt;
  EXPECT_FALSE(myInt.has_value());
  myInt = std::move(0xcafe);
  EXPECT_TRUE(myInt.has_value());
  EXPECT_EQ(*myInt, 0xcafe);
}

TEST(Optional, OptionalMoveAssignAndRead) {
  Optional<int> myInt(0x1337);
  Optional<int> myMovedInt;
  EXPECT_FALSE(myMovedInt.has_value());
  myMovedInt = std::move(myInt);
  EXPECT_TRUE(myInt.has_value());
  EXPECT_TRUE(myMovedInt.has_value());
  EXPECT_EQ(*myMovedInt, 0x1337);
}

TEST(Optional, OptionalCopyAssignAndRead) {
  Optional<int> myInt(0x1337);
  Optional<int> myCopiedInt;
  EXPECT_FALSE(myCopiedInt.has_value());
  myCopiedInt = myInt;
  EXPECT_TRUE(myInt.has_value());
  EXPECT_TRUE(myCopiedInt.has_value());
  EXPECT_EQ(*myInt, 0x1337);
  EXPECT_EQ(*myCopiedInt, 0x1337);
}

static constexpr int kInvalidValue = -1;

class MovableButNonCopyable : public chre::NonCopyable {
 public:
  MovableButNonCopyable() = default;
  MovableButNonCopyable(int value) : mValue(value) {}
  MovableButNonCopyable(MovableButNonCopyable&& other) {
    mValue = other.mValue;
    other.mValue = kInvalidValue;
  }

  MovableButNonCopyable& operator=(MovableButNonCopyable&& other) {
    assert(mMagic == kConstructedMagic);
    mValue = other.mValue;
    other.mValue = kInvalidValue;
    return *this;
  }

  ~MovableButNonCopyable() {
    mMagic = kUninitializedMagic;
    mValue = kUninitializedMagic;
  }

  int getValue() const {
    return mValue;
  }

 private:
  static constexpr int kConstructedMagic = 0xfeedc0fe;
  static constexpr int kUninitializedMagic = 0xdeadbeef;

  int mMagic = kConstructedMagic;
  int mValue = kInvalidValue;
};

TEST(Optional, UninitializedAssignment) {
  constexpr int kValue1 = 0xd00d;
  constexpr int kValue2 = 0xcafe;
  MovableButNonCopyable transferee1(kValue1);
  MovableButNonCopyable transferee2(kValue2);

  Optional<MovableButNonCopyable> container;
  EXPECT_FALSE(container.has_value());

  container = std::move(transferee1);
  EXPECT_TRUE(container.has_value());
  EXPECT_EQ(container->getValue(), kValue1);
  EXPECT_EQ(transferee1.getValue(), kInvalidValue);

  container.reset();
  EXPECT_FALSE(container.has_value());

  container = std::move(transferee2);
  EXPECT_TRUE(container.has_value());
  EXPECT_EQ(container->getValue(), kValue2);
  EXPECT_EQ(transferee2.getValue(), kInvalidValue);
}

// TODO: should add some tests to cover the possible assignment outcomes between
// two Optional instances (e.g. assign one w/o value to one w/value, etc)