/*
 * Copyright (C) 2019 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.
 */

#ifndef INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_
#define INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_

#include <string.h>

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <type_traits>

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"

namespace perfetto {
namespace base {

// A helper class which writes formatted data to a string buffer.
// This is used in the trace processor where we write O(GBs) of strings and
// sprintf is too slow.
class FixedStringWriter {
 public:
  // Creates a string buffer from a char buffer and length.
  FixedStringWriter(char* buffer, size_t size) : buffer_(buffer), size_(size) {}

  // Appends n instances of a char to the buffer.
  void AppendChar(char in, size_t n = 1) {
    PERFETTO_DCHECK(pos_ + n <= size_);
    memset(&buffer_[pos_], in, n);
    pos_ += n;
  }

  // Appends a length delimited string to the buffer.
  void AppendString(const char* in, size_t n) {
    PERFETTO_DCHECK(pos_ + n <= size_);
    memcpy(&buffer_[pos_], in, n);
    pos_ += n;
  }

  void AppendStringView(StringView sv) { AppendString(sv.data(), sv.size()); }

  // Appends a null-terminated string literal to the buffer.
  template <size_t N>
  inline void AppendLiteral(const char (&in)[N]) {
    AppendString(in, N - 1);
  }

  // Appends a StringView to the buffer.
  void AppendString(StringView data) { AppendString(data.data(), data.size()); }

  // Appends an integer to the buffer.
  void AppendInt(int64_t value) { AppendPaddedInt<'0', 0>(value); }

  // Appends an integer to the buffer, padding with |padchar| if the number of
  // digits of the integer is less than |padding|.
  template <char padchar, uint64_t padding>
  void AppendPaddedInt(int64_t sign_value) {
    const bool negate = std::signbit(static_cast<double>(sign_value));
    uint64_t absolute_value;
    if (sign_value == std::numeric_limits<int64_t>::min()) {
      absolute_value =
          static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1;
    } else {
      absolute_value = static_cast<uint64_t>(std::abs(sign_value));
    }
    AppendPaddedInt<padchar, padding>(absolute_value, negate);
  }

  void AppendUnsignedInt(uint64_t value) {
    AppendPaddedUnsignedInt<'0', 0>(value);
  }

  // Appends an unsigned integer to the buffer, padding with |padchar| if the
  // number of digits of the integer is less than |padding|.
  template <char padchar, uint64_t padding>
  void AppendPaddedUnsignedInt(uint64_t value) {
    AppendPaddedInt<padchar, padding>(value, false);
  }

  template <typename IntType>
  void AppendPaddedHexInt(IntType value, char padchar, uint64_t padding) {
    using UnsignedType = std::make_unsigned_t<IntType>;
    constexpr size_t kMaxHexDigits = sizeof(IntType) * 2;
    // 32 bytes is more than enough for any integer type (max 16 hex digits for
    // 64-bit)
    constexpr size_t kBufferSize = 32;
    auto size_needed =
        kMaxHexDigits > padding ? kMaxHexDigits : static_cast<size_t>(padding);
    PERFETTO_DCHECK(size_needed <= kBufferSize);
    PERFETTO_DCHECK(pos_ + size_needed <= size_);

    std::array<char, kBufferSize> data;
    constexpr char hex_asc[] = "0123456789abcdef";

    size_t idx = size_needed - 1;
    auto uvalue = static_cast<UnsignedType>(value);
    do {
      data[idx--] = hex_asc[uvalue & 0xF];
      uvalue >>= 4;
    } while (uvalue != 0);

    if (padding > 0) {
      const auto num_digits = static_cast<uint64_t>(size_needed - 1 - idx);
      // std::max() needed to work around GCC not being able to tell that
      // padding > 0.
      for (auto i = num_digits; i < std::max(uint64_t{1u}, padding); i++) {
        data[idx--] = padchar;
      }
    }
    AppendString(&data[idx + 1], size_needed - idx - 1);
  }

  // Appends a hex integer to the buffer.
  template <typename IntType>
  void AppendHexInt(IntType value) {
    AppendPaddedHexInt(value, '0', 0);
  }

  // Appends a hex string to the buffer.
  void AppendHexString(const uint8_t* data, size_t size, char separator);

  void AppendHexString(StringView data, char separator) {
    AppendHexString(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
                    separator);
  }

  // Appends a double to the buffer.
  void AppendDouble(double value) {
    // TODO(lalitm): trying to optimize this is premature given we almost never
    // print doubles. Reevaluate this in the future if we do print them more.
    size_t res = base::SprintfTrunc(buffer_ + pos_, size_ - pos_, "%lf", value);
    PERFETTO_DCHECK(pos_ + res <= size_);
    pos_ += res;
  }

  void AppendBool(bool value) {
    if (value) {
      AppendLiteral("true");
      return;
    }
    AppendLiteral("false");
  }

  StringView GetStringView() {
    PERFETTO_DCHECK(pos_ <= size_);
    return StringView(buffer_, pos_);
  }

  char* CreateStringCopy() {
    char* dup = reinterpret_cast<char*>(malloc(pos_ + 1));
    if (dup) {
      memcpy(dup, buffer_, pos_);
      dup[pos_] = '\0';
    }
    return dup;
  }

  size_t pos() const { return pos_; }
  size_t size() const { return size_; }
  void reset() { pos_ = 0; }

 private:
  template <char padchar, uint64_t padding>
  void AppendPaddedInt(uint64_t absolute_value, bool negate) {
    // Need to add 2 to the number of digits to account for minus sign and
    // rounding down of digits10.
    constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
    constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
    PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);

    char data[kSizeNeeded];

    size_t idx;
    for (idx = kSizeNeeded - 1; absolute_value >= 10;) {
      char digit = absolute_value % 10;
      absolute_value /= 10;
      data[idx--] = digit + '0';
    }
    data[idx--] = static_cast<char>(absolute_value) + '0';

    if (padding > 0) {
      size_t num_digits = kSizeNeeded - 1 - idx;
      // std::max() needed to work around GCC not being able to tell that
      // padding > 0.
      for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) {
        data[idx--] = padchar;
      }
    }

    if (negate)
      buffer_[pos_++] = '-';
    AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
  }

  char* buffer_ = nullptr;
  size_t size_ = 0;
  size_t pos_ = 0;
};

}  // namespace base
}  // namespace perfetto

#endif  // INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_
