////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Jan Steemann
////////////////////////////////////////////////////////////////////////////////

#ifndef ARANGOD_MMFILES_MMFILES_DATAFILE_HELPER_H
#define ARANGOD_MMFILES_MMFILES_DATAFILE_HELPER_H 1

#include "Basics/Common.h"
#include "Basics/encoding.h"
#include "MMFiles/MMFilesDatafile.h"

namespace arangodb {
namespace MMFilesDatafileHelper {

////////////////////////////////////////////////////////////////////////////////
/// @brief bit mask for datafile ids (fids) that indicates whether a file is
/// a WAL file (bit set) or a datafile (bit not set)
////////////////////////////////////////////////////////////////////////////////

constexpr inline uint64_t WalFileBitmask() { return 0x8000000000000000ULL; }

////////////////////////////////////////////////////////////////////////////////
/// @brief maximal size of a marker
////////////////////////////////////////////////////////////////////////////////

constexpr inline uint32_t MaximalMarkerSize() {
  return 2UL * 1024UL * 1024UL * 1024UL; // 2 GB
}

////////////////////////////////////////////////////////////////////////////////
/// @brief journal overhead
////////////////////////////////////////////////////////////////////////////////

constexpr inline uint32_t JournalOverhead() {
  return sizeof(MMFilesDatafileHeaderMarker) + sizeof(MMFilesDatafileFooterMarker);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the 8-byte aligned size for the marker
////////////////////////////////////////////////////////////////////////////////

template <typename T>
static inline T AlignedMarkerSize(MMFilesMarker const* marker) {
  size_t value = marker->getSize();
  return static_cast<T>((value + 7) - ((value + 7) & 7));
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific offset to the vpack payload
/// note that this function is also used to determine the base length of a
/// marker type
////////////////////////////////////////////////////////////////////////////////

static inline size_t VPackOffset(MMFilesMarkerType type) noexcept {
  if (type == TRI_DF_MARKER_VPACK_DOCUMENT ||
      type == TRI_DF_MARKER_VPACK_REMOVE) {
    // VPack is located after transaction id
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tid_t);
  }
  if (type == TRI_DF_MARKER_VPACK_CREATE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_DROP_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_RENAME_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CHANGE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CREATE_INDEX ||
      type == TRI_DF_MARKER_VPACK_DROP_INDEX ||
      type == TRI_DF_MARKER_VPACK_CREATE_VIEW ||
      type == TRI_DF_MARKER_VPACK_DROP_VIEW ||
      type == TRI_DF_MARKER_VPACK_RENAME_VIEW ||
      type == TRI_DF_MARKER_VPACK_CHANGE_VIEW) {
    // VPack is located after database id and collection id
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t) + sizeof(TRI_voc_cid_t);
  }
  if (type == TRI_DF_MARKER_VPACK_CREATE_DATABASE ||
      type == TRI_DF_MARKER_VPACK_DROP_DATABASE) {
    // VPack is located after database id
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t);
  }
  if (type == TRI_DF_MARKER_VPACK_BEGIN_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_COMMIT_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_ABORT_TRANSACTION) {
    // these marker types do not have any VPack
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t) + sizeof(TRI_voc_tid_t);
  }
  if (type == TRI_DF_MARKER_PROLOGUE) {
    // this type does not have any VPack
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t) + sizeof(TRI_voc_cid_t);
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific database id offset
////////////////////////////////////////////////////////////////////////////////

static inline size_t DatabaseIdOffset(MMFilesMarkerType type) noexcept {
  if (type == TRI_DF_MARKER_PROLOGUE ||
      type == TRI_DF_MARKER_VPACK_CREATE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_DROP_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_RENAME_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CHANGE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CREATE_INDEX ||
      type == TRI_DF_MARKER_VPACK_DROP_INDEX ||
      type == TRI_DF_MARKER_VPACK_CREATE_VIEW ||
      type == TRI_DF_MARKER_VPACK_DROP_VIEW ||
      type == TRI_DF_MARKER_VPACK_RENAME_VIEW ||
      type == TRI_DF_MARKER_VPACK_CHANGE_VIEW ||
      type == TRI_DF_MARKER_VPACK_CREATE_DATABASE ||
      type == TRI_DF_MARKER_VPACK_DROP_DATABASE ||
      type == TRI_DF_MARKER_VPACK_BEGIN_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_COMMIT_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_ABORT_TRANSACTION) {
    return sizeof(MMFilesMarker);
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific database id
////////////////////////////////////////////////////////////////////////////////

static inline TRI_voc_tick_t DatabaseId(MMFilesMarker const* marker) noexcept {
  MMFilesMarkerType type = marker->getType();
  if (type == TRI_DF_MARKER_PROLOGUE ||
      type == TRI_DF_MARKER_VPACK_CREATE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_DROP_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_RENAME_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CHANGE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CREATE_INDEX ||
      type == TRI_DF_MARKER_VPACK_DROP_INDEX ||
      type == TRI_DF_MARKER_VPACK_CREATE_VIEW ||
      type == TRI_DF_MARKER_VPACK_DROP_VIEW ||
      type == TRI_DF_MARKER_VPACK_RENAME_VIEW ||
      type == TRI_DF_MARKER_VPACK_CHANGE_VIEW ||
      type == TRI_DF_MARKER_VPACK_CREATE_DATABASE ||
      type == TRI_DF_MARKER_VPACK_DROP_DATABASE ||
      type == TRI_DF_MARKER_VPACK_BEGIN_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_COMMIT_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_ABORT_TRANSACTION) {
    return encoding::readNumber<TRI_voc_tick_t>(reinterpret_cast<uint8_t const*>(marker) + DatabaseIdOffset(type), sizeof(TRI_voc_tick_t));
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific collection id offset
////////////////////////////////////////////////////////////////////////////////

static inline size_t CollectionIdOffset(MMFilesMarkerType type) noexcept {
  if (type == TRI_DF_MARKER_PROLOGUE ||
      type == TRI_DF_MARKER_VPACK_CREATE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_DROP_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_RENAME_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CHANGE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CREATE_INDEX ||
      type == TRI_DF_MARKER_VPACK_DROP_INDEX) {
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t);
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific collection id
////////////////////////////////////////////////////////////////////////////////

static inline TRI_voc_tick_t CollectionId(MMFilesMarker const* marker) noexcept {
  MMFilesMarkerType type = marker->getType();
  if (type == TRI_DF_MARKER_PROLOGUE ||
      type == TRI_DF_MARKER_VPACK_CREATE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_DROP_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_RENAME_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CHANGE_COLLECTION ||
      type == TRI_DF_MARKER_VPACK_CREATE_INDEX ||
      type == TRI_DF_MARKER_VPACK_DROP_INDEX) {
    return encoding::readNumber<TRI_voc_cid_t>(reinterpret_cast<uint8_t const*>(marker) + CollectionIdOffset(type), sizeof(TRI_voc_cid_t));
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific view id offset
////////////////////////////////////////////////////////////////////////////////

static inline size_t ViewIdOffset(MMFilesMarkerType type) noexcept {
  if (type == TRI_DF_MARKER_VPACK_CREATE_VIEW ||
      type == TRI_DF_MARKER_VPACK_DROP_VIEW ||
      type == TRI_DF_MARKER_VPACK_CHANGE_VIEW ||
      type == TRI_DF_MARKER_VPACK_RENAME_VIEW) {
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t);
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific view id
////////////////////////////////////////////////////////////////////////////////

static inline TRI_voc_tick_t ViewId(MMFilesMarker const* marker) noexcept {
  MMFilesMarkerType type = marker->getType();
  if (type == TRI_DF_MARKER_VPACK_CREATE_VIEW ||
      type == TRI_DF_MARKER_VPACK_DROP_VIEW ||
      type == TRI_DF_MARKER_VPACK_CHANGE_VIEW ||
      type == TRI_DF_MARKER_VPACK_RENAME_VIEW) {
    return encoding::readNumber<TRI_voc_cid_t>(reinterpret_cast<uint8_t const*>(marker) + ViewIdOffset(type), sizeof(TRI_voc_cid_t));
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific transaction id offset
////////////////////////////////////////////////////////////////////////////////

static inline TRI_voc_tick_t TransactionIdOffset(MMFilesMarkerType type) noexcept {
  if (type == TRI_DF_MARKER_VPACK_DOCUMENT ||
      type == TRI_DF_MARKER_VPACK_REMOVE) {
    return sizeof(MMFilesMarker);
  }
  if (type == TRI_DF_MARKER_VPACK_BEGIN_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_COMMIT_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_ABORT_TRANSACTION) {
    return sizeof(MMFilesMarker) + sizeof(TRI_voc_tick_t);
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the marker-specific transaction id
////////////////////////////////////////////////////////////////////////////////

static inline TRI_voc_tick_t TransactionId(MMFilesMarker const* marker) noexcept {
  MMFilesMarkerType type = marker->getType();
  if (type == TRI_DF_MARKER_VPACK_DOCUMENT ||
      type == TRI_DF_MARKER_VPACK_REMOVE ||
      type == TRI_DF_MARKER_VPACK_BEGIN_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_COMMIT_TRANSACTION ||
      type == TRI_DF_MARKER_VPACK_ABORT_TRANSACTION) {
    return encoding::readNumber<TRI_voc_tid_t>(reinterpret_cast<uint8_t const*>(marker) + TransactionIdOffset(type), sizeof(TRI_voc_tid_t));
  }
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief initializes a marker, using user-defined tick
////////////////////////////////////////////////////////////////////////////////

static inline void InitMarker(MMFilesMarker* marker,
                   MMFilesMarkerType type, uint32_t size, TRI_voc_tick_t tick) {
  TRI_ASSERT(marker != nullptr);
  TRI_ASSERT(type > TRI_DF_MARKER_MIN && type < TRI_DF_MARKER_MAX);
  TRI_ASSERT(size > 0);

  marker->setSize(size);
  marker->setTypeAndTick(type, tick);
  marker->setCrc(0);
}

////////////////////////////////////////////////////////////////////////////////
/// @brief initializes a marker, using tick 0
////////////////////////////////////////////////////////////////////////////////

static inline void InitMarker(MMFilesMarker* marker,
                              MMFilesMarkerType type, uint32_t size) {
  InitMarker(marker, type, size, 0); // always use tick 0
}

////////////////////////////////////////////////////////////////////////////////
/// @brief create a header marker
////////////////////////////////////////////////////////////////////////////////

static inline MMFilesDatafileHeaderMarker CreateHeaderMarker(uint32_t maximalSize, TRI_voc_tick_t fid) {
  static_assert(sizeof(TRI_voc_tick_t) == sizeof(TRI_voc_fid_t), "invalid tick/fid sizes");

  MMFilesDatafileHeaderMarker header;
  InitMarker(reinterpret_cast<MMFilesMarker*>(&header), TRI_DF_MARKER_HEADER, sizeof(MMFilesDatafileHeaderMarker), fid);

  header._version = TRI_DF_VERSION;
  header._maximalSize = maximalSize;
  header._fid = fid;

  return header;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief create a prologue marker
////////////////////////////////////////////////////////////////////////////////

static inline MMFilesPrologueMarker CreatePrologueMarker(TRI_voc_tick_t databaseId, TRI_voc_cid_t collectionId) {
  MMFilesPrologueMarker header;
  InitMarker(reinterpret_cast<MMFilesMarker*>(&header), TRI_DF_MARKER_PROLOGUE, sizeof(MMFilesPrologueMarker));

  encoding::storeNumber<decltype(databaseId)>(reinterpret_cast<uint8_t*>(&header) + DatabaseIdOffset(TRI_DF_MARKER_PROLOGUE), databaseId, sizeof(decltype(databaseId)));
  encoding::storeNumber<decltype(collectionId)>(reinterpret_cast<uint8_t*>(&header) + CollectionIdOffset(TRI_DF_MARKER_PROLOGUE), collectionId, sizeof(decltype(collectionId)));

  return header;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief create a footer marker, using a user-defined tick
////////////////////////////////////////////////////////////////////////////////

static inline MMFilesDatafileFooterMarker CreateFooterMarker(TRI_voc_tick_t tick) {
  MMFilesDatafileFooterMarker footer;
  InitMarker(reinterpret_cast<MMFilesMarker*>(&footer), TRI_DF_MARKER_FOOTER, sizeof(MMFilesDatafileFooterMarker), tick);

  return footer;
}

////////////////////////////////////////////////////////////////////////////////
/// @brief create a footer marker, using tick 0
////////////////////////////////////////////////////////////////////////////////

static inline MMFilesDatafileFooterMarker CreateFooterMarker() {
  return CreateFooterMarker(0); // always use tick 0
}

}
} // namespace

#endif
