// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "brillo/enum_flags.h"

#include <gtest/gtest.h>

namespace brillo {

class EnumFlagsTest : public testing::Test {};

enum SomeFlagsEnum /* : int */ {
  FLAG_NONE = 0,
  FLAG_ONE = 1,
  FLAG_TWO = 2,
  FLAG_THREE = 4,
};

enum class SomeFlagsEnumClass /* : int */ {
  NONE = 0,
  ONE = 1,
  TWO = 2,
  THREE = 4,
};

enum SomeBigFlagsEnum : int64_t {
  BIG_FLAG_NONE = 0,
  BIG_FLAG_ONE = 1,
  BIG_FLAG_TWO = 2,
  BIG_FLAG_THREE = 4,
  BIG_FLAG_FOUR = 8,
};

DECLARE_FLAGS_ENUM(SomeFlagsEnum);
DECLARE_FLAGS_ENUM(SomeFlagsEnumClass);
DECLARE_FLAGS_ENUM(SomeBigFlagsEnum);

// These first tests show how these operators are meant to be used.

TEST_F(EnumFlagsTest, SampleUsage) {
  SomeFlagsEnum value = FLAG_NONE;

  // Set a flag.
  value |= FLAG_ONE;
  EXPECT_EQ(FLAG_ONE, value);

  // Set another
  value |= FLAG_THREE;
  EXPECT_EQ(FLAG_ONE | FLAG_THREE, value);

  // Clear a flag
  value &= ~FLAG_ONE;
  EXPECT_EQ(FLAG_THREE, value);

  // Toggle a flag
  value ^= FLAG_TWO;
  EXPECT_EQ(FLAG_THREE | FLAG_TWO, value);
}

TEST_F(EnumFlagsTest, SampleUsageOfMasks) {
  SomeFlagsEnum flags = FLAG_ONE | FLAG_THREE;

  EXPECT_TRUE(flags & FLAG_ONE);
  EXPECT_TRUE(flags & FLAG_THREE);
  EXPECT_FALSE(flags & FLAG_TWO);
  EXPECT_TRUE(flags & ~FLAG_TWO);
}

TEST_F(EnumFlagsTest, SampleUsageWithEnumClass) {
  SomeFlagsEnumClass value = SomeFlagsEnumClass::NONE;

  // Set a flag.
  value |= SomeFlagsEnumClass::ONE;
  EXPECT_EQ(SomeFlagsEnumClass::ONE, value);

  // Set another
  value |= SomeFlagsEnumClass::THREE;
  EXPECT_EQ(SomeFlagsEnumClass::ONE | SomeFlagsEnumClass::THREE, value);

  // Clear a flag
  value &= ~SomeFlagsEnumClass::ONE;
  EXPECT_EQ(SomeFlagsEnumClass::THREE, value);

  // Toggle a flag
  value ^= SomeFlagsEnumClass::TWO;
  EXPECT_EQ(SomeFlagsEnumClass::THREE | SomeFlagsEnumClass::TWO, value);
}

TEST_F(EnumFlagsTest, SampleUsageWithBigEnumType) {
  SomeBigFlagsEnum value = BIG_FLAG_NONE;

  // Set a flag.
  value |= BIG_FLAG_ONE;
  EXPECT_EQ(BIG_FLAG_ONE, value);

  // Set another
  value |= BIG_FLAG_THREE;
  EXPECT_EQ(FLAG_ONE | BIG_FLAG_THREE, value);

  // Clear a flag
  value &= ~BIG_FLAG_ONE;
  EXPECT_EQ(BIG_FLAG_THREE, value);

  // Toggle a flag
  value ^= BIG_FLAG_TWO;
  EXPECT_EQ(BIG_FLAG_THREE | BIG_FLAG_TWO, value);
}

// These following tests verify the binary behavior of the operators.  They do
// not demonstrate standard usage.

TEST_F(EnumFlagsTest, BinaryBehaviorOfAssignmentOperators) {
  SomeFlagsEnum value = FLAG_NONE;

  // Set a flag.
  value |= FLAG_ONE;
  EXPECT_EQ(1, value);

  // Set another
  value |= FLAG_THREE;
  EXPECT_EQ(5, value);

  // Clear a flag
  value &= ~FLAG_ONE;
  EXPECT_EQ(4, value);

  // Toggle a flag
  value ^= FLAG_TWO;
  EXPECT_EQ(6, value);
}

TEST_F(EnumFlagsTest, BinaryBehaviorOfAssignmentOperatorsWithEnumClass) {
  SomeFlagsEnumClass value = SomeFlagsEnumClass::NONE;

  // Set a flag.
  value |= SomeFlagsEnumClass::ONE;
  EXPECT_EQ(1, static_cast<int>(value));  //

  // Set another
  value |= SomeFlagsEnumClass::THREE;
  EXPECT_EQ(5, static_cast<int>(value));

  // Clear a flag
  value &= ~SomeFlagsEnumClass::ONE;
  EXPECT_EQ(4, static_cast<int>(value));

  // Toggle a flag
  value ^= SomeFlagsEnumClass::TWO;
  EXPECT_EQ(6, static_cast<int>(value));
}

TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperations) {
  // These values are set directly with a cast for clarity.
  const int all_bits_int = -1;
  const SomeFlagsEnum all_bits = static_cast<SomeFlagsEnum>(all_bits_int);
  const SomeFlagsEnum just_2_bits = static_cast<SomeFlagsEnum>(3);

  // Inverting a flag should result in all bits set in the base type but that
  // one.
  EXPECT_EQ(-2, ~FLAG_ONE);
  EXPECT_EQ(-3, ~FLAG_TWO);

  // OR'ing two flags should result in both being set.
  EXPECT_EQ(3, FLAG_ONE | FLAG_TWO);

  // AND'ing two flags should result in 0.
  EXPECT_EQ(FLAG_NONE, FLAG_ONE & FLAG_TWO);

  // AND'ing a mask with a flag should result in that flag.
  EXPECT_EQ(FLAG_ONE, all_bits & FLAG_ONE);

  // XOR'ing two flags should result in both being set.
  EXPECT_EQ(3, FLAG_ONE ^ FLAG_TWO);

  // XOR'ing a mask with a flag should toggle that flag in the mask.
  EXPECT_EQ(FLAG_ONE, FLAG_NONE ^ FLAG_ONE);
  EXPECT_EQ(FLAG_TWO, just_2_bits ^ FLAG_ONE);
}

TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperationsOnEnumClass) {
  // These values are set directly with a cast for clarity.
  const int all_bits_int = -1;
  const SomeFlagsEnumClass all_bits =
      static_cast<SomeFlagsEnumClass>(all_bits_int);
  const SomeFlagsEnumClass just_2_bits = static_cast<SomeFlagsEnumClass>(3);

  // Inverting a flag should result in all bits set in the base type but that
  // one.
  EXPECT_EQ(-2, static_cast<int>(~SomeFlagsEnumClass::ONE));
  EXPECT_EQ(-3, static_cast<int>(~SomeFlagsEnumClass::TWO));

  // OR'ing two flags should result in both being set.
  EXPECT_EQ(
      3, static_cast<int>(SomeFlagsEnumClass::ONE | SomeFlagsEnumClass::TWO));

  // AND'ing two flags should result in 0.
  EXPECT_EQ(SomeFlagsEnumClass::NONE,
            SomeFlagsEnumClass::ONE & SomeFlagsEnumClass::TWO);

  // AND'ing a mask with a flag should result in that flag.
  EXPECT_EQ(SomeFlagsEnumClass::ONE, all_bits & SomeFlagsEnumClass::ONE);

  // XOR'ing two flags should result in both being set.
  EXPECT_EQ(
      3, static_cast<int>(SomeFlagsEnumClass::ONE ^ SomeFlagsEnumClass::TWO));

  // XOR'ing a mask with a flag should toggle that flag in the mask.
  EXPECT_EQ(SomeFlagsEnumClass::ONE,
            SomeFlagsEnumClass::NONE ^ SomeFlagsEnumClass::ONE);
  EXPECT_EQ(SomeFlagsEnumClass::TWO, just_2_bits ^ SomeFlagsEnumClass::ONE);
}

TEST_F(EnumFlagsTest, BinaryBehaviorOfSimpleOperationsWithBaseType) {
  // These values are set directly with a cast for clarity.
  const int64_t all_bits_int = -1;
  const SomeBigFlagsEnum all_bits = static_cast<SomeBigFlagsEnum>(all_bits_int);
  const SomeBigFlagsEnum just_2_bits = static_cast<SomeBigFlagsEnum>(3);

  // Inverting a flag should result in all bits set in the base type but that
  // one.
  EXPECT_EQ(all_bits ^ BIG_FLAG_ONE, ~BIG_FLAG_ONE);

  // OR'ing two flags should result in both being set.
  EXPECT_EQ(3, BIG_FLAG_ONE | BIG_FLAG_TWO);

  // AND'ing two flags should result in 0.
  EXPECT_EQ(BIG_FLAG_NONE, BIG_FLAG_ONE & BIG_FLAG_TWO);

  // AND'ing a mask with a flag should result in that flag.
  EXPECT_EQ(BIG_FLAG_ONE, all_bits & BIG_FLAG_ONE);

  // XOR'ing two flags should result in both being set.
  EXPECT_EQ(3, BIG_FLAG_ONE ^ BIG_FLAG_TWO);

  // XOR'ing a mask with a flag should toggle that flag in the mask.
  EXPECT_EQ(BIG_FLAG_ONE, BIG_FLAG_NONE ^ BIG_FLAG_ONE);
  EXPECT_EQ(BIG_FLAG_TWO, just_2_bits ^ BIG_FLAG_ONE);
}

}  // namespace brillo