/*
 * Copyright (c) Meta Platforms, Inc. and 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/PackedSyncPtr.h>

#include <cinttypes>
#include <thread>
#include <unordered_map>
#include <utility>

#include <folly/portability/GTest.h>

using folly::PackedSyncPtr;

namespace {

// Compile time check for packability.  This requires that
// PackedSyncPtr is a POD struct on gcc.
FOLLY_PACK_PUSH
struct ignore {
  FOLLY_PACK_ATTR PackedSyncPtr<int> foo;
  char c;
} FOLLY_PACK_ATTR;
FOLLY_PACK_POP
static_assert(sizeof(ignore) == 9, "PackedSyncPtr wasn't packable");

} // namespace

TEST(PackedSyncPtr, Basic) {
  PackedSyncPtr<std::pair<int, int>> sp;
  sp.init(new std::pair<int, int>[2]);
  EXPECT_EQ(sizeof(sp), 8);
  sp->first = 5;
  EXPECT_EQ(sp[0].first, 5);
  sp[1].second = 7;
  EXPECT_EQ(sp[1].second, 7);
  sp.lock();
  EXPECT_EQ(sp[1].second, 7);
  sp[0].first = 9;
  EXPECT_EQ(sp->first, 9);
  sp.unlock();
  EXPECT_EQ((sp.get() + 1)->second, 7);

  sp.lock();
  EXPECT_EQ(sp.extra(), 0);
  sp.setExtra(0x13);
  EXPECT_EQ(sp.extra(), 0x13);
  EXPECT_EQ((sp.get() + 1)->second, 7);
  delete[] sp.get();
  auto newP = new std::pair<int, int>();
  sp.set(newP);
  EXPECT_EQ(sp.extra(), 0x13);
  EXPECT_EQ(sp.get(), newP);
  sp.unlock();
  delete sp.get();
}

// Here we use the PackedSyncPtr to lock the whole SyncVec (base, *base, and sz)
template <typename T>
struct SyncVec {
  PackedSyncPtr<T> base;
  SyncVec() { base.init(); }
  ~SyncVec() { free(base.get()); }
  void push_back(const T& t) {
    base.set((T*)realloc(base.get(), (base.extra() + 1) * sizeof(T)));
    base[base.extra()] = t;
    base.setExtra(base.extra() + 1);
  }
  void lock() { base.lock(); }
  void unlock() { base.unlock(); }

  T* begin() const { return base.get(); }
  T* end() const { return base.get() + base.extra(); }
};
typedef SyncVec<intptr_t> VecT;
typedef std::unordered_map<int64_t, VecT> Map;
const int mapCap = 1317;
const int nthrs = 297;
static Map map(mapCap);

// Each app thread inserts it's ID into every vec in map
// map is read only, so doesn't need any additional locking
void appThread(intptr_t id) {
  for (auto& kv : map) {
    kv.second.lock();
    kv.second.push_back(id);
    kv.second.unlock();
  }
}

TEST(PackedSyncPtr, Application) {
  for (int64_t i = 0; i < mapCap / 2; ++i) {
    map.insert(std::make_pair(i, VecT()));
  }
  std::vector<std::thread> thrs;
  for (intptr_t i = 0; i < nthrs; i++) {
    thrs.push_back(std::thread(appThread, i));
  }
  for (auto& t : thrs) {
    t.join();
  }

  for (auto& kv : map) {
    // Make sure every thread successfully inserted it's ID into every vec
    std::set<intptr_t> idsFound;
    for (auto& elem : kv.second) {
      EXPECT_TRUE(idsFound.insert(elem).second); // check for dups
    }
    EXPECT_EQ(idsFound.size(), nthrs); // check they are all there
  }
}

TEST(PackedSyncPtr, extraData) {
  PackedSyncPtr<int> p;
  p.init();
  int* unaligned = reinterpret_cast<int*>(0xf003);
  p.lock();
  p.set(unaligned);
  uintptr_t* bytes = reinterpret_cast<uintptr_t*>(&p);
  LOG(INFO) << "Bytes integer is: 0x" << std::hex << *bytes;
  EXPECT_EQ(p.get(), unaligned);
  p.unlock();
}
