/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SharedPrefMap.h"

#include "mozilla/dom/ipc/MemMapSnapshot.h"

#include "mozilla/BinarySearch.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ipc/FileDescriptor.h"

using namespace mozilla::loader;

namespace mozilla {

using namespace ipc;

static inline size_t GetAlignmentOffset(size_t aOffset, size_t aAlign) {
  auto mod = aOffset % aAlign;
  return mod ? aAlign - mod : 0;
}

SharedPrefMap::SharedPrefMap(const FileDescriptor& aMapFile, size_t aMapSize) {
  auto result = mMap.initWithHandle(aMapFile, aMapSize);
  MOZ_RELEASE_ASSERT(result.isOk());
  // We return literal nsCStrings pointing to the mapped data for preference
  // names and string values, which means that we may still have references to
  // the mapped data even after this instance is destroyed. That means that we
  // need to keep the mapping alive until process shutdown, in order to be safe.
  mMap.setPersistent();
}

SharedPrefMap::SharedPrefMap(SharedPrefMapBuilder&& aBuilder) {
  auto result = aBuilder.Finalize(mMap);
  MOZ_RELEASE_ASSERT(result.isOk());
  mMap.setPersistent();
}

mozilla::ipc::FileDescriptor SharedPrefMap::CloneFileDescriptor() const {
  return mMap.cloneHandle();
}

bool SharedPrefMap::Has(const char* aKey) const {
  size_t index;
  return Find(aKey, &index);
}

Maybe<const SharedPrefMap::Pref> SharedPrefMap::Get(const char* aKey) const {
  Maybe<const Pref> result;

  size_t index;
  if (Find(aKey, &index)) {
    result.emplace(Pref{this, &Entries()[index]});
  }

  return result;
}

bool SharedPrefMap::Find(const char* aKey, size_t* aIndex) const {
  const auto& keys = KeyTable();

  return BinarySearchIf(
      Entries(), 0, EntryCount(),
      [&](const Entry& aEntry) {
        return strcmp(aKey, keys.GetBare(aEntry.mKey));
      },
      aIndex);
}

void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags,
                               bool aDefaultValue, bool aUserValue) {
  mEntries.AppendElement(Entry{
      aKey.get(),
      mKeyTable.Add(aKey),
      {aDefaultValue, aUserValue},
      uint8_t(PrefType::Bool),
      aFlags.mHasDefaultValue,
      aFlags.mHasUserValue,
      aFlags.mIsSticky,
      aFlags.mIsLocked,
      aFlags.mIsSanitized,
      aFlags.mIsSkippedByIteration,
  });
}

void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags,
                               int32_t aDefaultValue, int32_t aUserValue) {
  ValueIdx index;
  if (aFlags.mHasUserValue) {
    index = mIntValueTable.Add(aDefaultValue, aUserValue);
  } else {
    index = mIntValueTable.Add(aDefaultValue);
  }

  mEntries.AppendElement(Entry{
      aKey.get(),
      mKeyTable.Add(aKey),
      {index},
      uint8_t(PrefType::Int),
      aFlags.mHasDefaultValue,
      aFlags.mHasUserValue,
      aFlags.mIsSticky,
      aFlags.mIsLocked,
      aFlags.mIsSanitized,
      aFlags.mIsSkippedByIteration,
  });
}

void SharedPrefMapBuilder::Add(const nsCString& aKey, const Flags& aFlags,
                               const nsCString& aDefaultValue,
                               const nsCString& aUserValue) {
  ValueIdx index;
  StringTableEntry defaultVal = mValueStringTable.Add(aDefaultValue);
  if (aFlags.mHasUserValue) {
    StringTableEntry userVal = mValueStringTable.Add(aUserValue);
    index = mStringValueTable.Add(defaultVal, userVal);
  } else {
    index = mStringValueTable.Add(defaultVal);
  }

  mEntries.AppendElement(Entry{
      aKey.get(),
      mKeyTable.Add(aKey),
      {index},
      uint8_t(PrefType::String),
      aFlags.mHasDefaultValue,
      aFlags.mHasUserValue,
      aFlags.mIsSticky,
      aFlags.mIsLocked,
      aFlags.mIsSanitized,
      aFlags.mIsSkippedByIteration,
  });
}

Result<Ok, nsresult> SharedPrefMapBuilder::Finalize(loader::AutoMemMap& aMap) {
  using Header = SharedPrefMap::Header;

  // Create an array of entry pointers for the entry array, and sort it by
  // preference name prior to serialization, so that entries can be looked up
  // using binary search.
  nsTArray<Entry*> entries(mEntries.Length());
  for (auto& entry : mEntries) {
    entries.AppendElement(&entry);
  }
  entries.Sort([](const Entry* aA, const Entry* aB) {
    return strcmp(aA->mKeyString, aB->mKeyString);
  });

  Header header = {uint32_t(entries.Length())};

  size_t offset = sizeof(header);
  offset += GetAlignmentOffset(offset, alignof(Header));

  offset += entries.Length() * sizeof(SharedPrefMap::Entry);

  header.mKeyStrings.mOffset = offset;
  header.mKeyStrings.mSize = mKeyTable.Size();
  offset += header.mKeyStrings.mSize;

  offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
  header.mUserIntValues.mOffset = offset;
  header.mUserIntValues.mSize = mIntValueTable.UserSize();
  offset += header.mUserIntValues.mSize;

  offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
  header.mDefaultIntValues.mOffset = offset;
  header.mDefaultIntValues.mSize = mIntValueTable.DefaultSize();
  offset += header.mDefaultIntValues.mSize;

  offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
  header.mUserStringValues.mOffset = offset;
  header.mUserStringValues.mSize = mStringValueTable.UserSize();
  offset += header.mUserStringValues.mSize;

  offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
  header.mDefaultStringValues.mOffset = offset;
  header.mDefaultStringValues.mSize = mStringValueTable.DefaultSize();
  offset += header.mDefaultStringValues.mSize;

  header.mValueStrings.mOffset = offset;
  header.mValueStrings.mSize = mValueStringTable.Size();
  offset += header.mValueStrings.mSize;

  MemMapSnapshot mem;
  MOZ_TRY(mem.Init(offset));

  auto headerPtr = mem.Get<Header>();
  headerPtr[0] = header;

  auto* entryPtr = reinterpret_cast<SharedPrefMap::Entry*>(&headerPtr[1]);
  for (auto* entry : entries) {
    *entryPtr = {
        entry->mKey,
        GetValue(*entry),
        entry->mType,
        entry->mHasDefaultValue,
        entry->mHasUserValue,
        entry->mIsSticky,
        entry->mIsLocked,
        entry->mIsSanitized,
        entry->mIsSkippedByIteration,
    };
    entryPtr++;
  }

  auto ptr = mem.Get<uint8_t>();

  mKeyTable.Write({&ptr[header.mKeyStrings.mOffset], header.mKeyStrings.mSize});

  mValueStringTable.Write(
      {&ptr[header.mValueStrings.mOffset], header.mValueStrings.mSize});

  mIntValueTable.WriteDefaultValues(
      {&ptr[header.mDefaultIntValues.mOffset], header.mDefaultIntValues.mSize});
  mIntValueTable.WriteUserValues(
      {&ptr[header.mUserIntValues.mOffset], header.mUserIntValues.mSize});

  mStringValueTable.WriteDefaultValues(
      {&ptr[header.mDefaultStringValues.mOffset],
       header.mDefaultStringValues.mSize});
  mStringValueTable.WriteUserValues(
      {&ptr[header.mUserStringValues.mOffset], header.mUserStringValues.mSize});

  mKeyTable.Clear();
  mValueStringTable.Clear();
  mIntValueTable.Clear();
  mStringValueTable.Clear();
  mEntries.Clear();

  return mem.Finalize(aMap);
}

}  // namespace mozilla
