/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.

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 TENSORFLOW_CORE_PLATFORM_CTSTRING_INTERNAL_H_
#define TENSORFLOW_CORE_PLATFORM_CTSTRING_INTERNAL_H_

#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
     __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) ||                  \
    defined(_WIN32)
#define TF_TSTRING_LITTLE_ENDIAN 1
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
    __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define TF_TSTRING_LITTLE_ENDIAN 0
#else
#error "Unable to detect endianness."
#endif

#if defined(__clang__) || \
    (defined(__GNUC__) && \
     ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
static inline uint32_t TF_swap32(uint32_t host_int) {
  return __builtin_bswap32(host_int);
}

#elif defined(_MSC_VER)
static inline uint32_t TF_swap32(uint32_t host_int) {
  return _byteswap_ulong(host_int);
}

#elif defined(__APPLE__)
static inline uint32_t TF_swap32(uint32_t host_int) {
  return OSSwapInt32(host_int);
}

#else
static inline uint32_t TF_swap32(uint32_t host_int) {
#if defined(__GLIBC__)
  return bswap_32(host_int);
#else   // defined(__GLIBC__)
  return (((host_int & uint32_t{0xFF}) << 24) |
          ((host_int & uint32_t{0xFF00}) << 8) |
          ((host_int & uint32_t{0xFF0000}) >> 8) |
          ((host_int & uint32_t{0xFF000000}) >> 24));
#endif  // defined(__GLIBC__)
}
#endif

#if TF_TSTRING_LITTLE_ENDIAN
#define TF_le32toh(x) x
#else  // TF_TSTRING_LITTLE_ENDIAN
#define TF_le32toh(x) TF_swap32(x)
#endif  // TF_TSTRING_LITTLE_ENDIAN

static inline size_t TF_align16(size_t i) { return (i + 0xF) & ~0xF; }

static inline size_t TF_max(size_t a, size_t b) { return a > b ? a : b; }
static inline size_t TF_min(size_t a, size_t b) { return a < b ? a : b; }

typedef enum TF_TString_Type {  // NOLINT
  TF_TSTR_SMALL = 0x00,
  TF_TSTR_LARGE = 0x01,
  TF_TSTR_OFFSET = 0x02,
  TF_TSTR_VIEW = 0x03,
  TF_TSTR_TYPE_MASK = 0x03
} TF_TString_Type;

typedef struct TF_TString_Large {  // NOLINT
  size_t size;
  size_t cap;
  char *ptr;
} TF_TString_Large;

typedef struct TF_TString_Offset {  // NOLINT
  uint32_t size;
  uint32_t offset;
  uint32_t count;
} TF_TString_Offset;

typedef struct TF_TString_View {  // NOLINT
  size_t size;
  const char *ptr;
} TF_TString_View;

typedef struct TF_TString_Raw {  // NOLINT
  uint8_t raw[24];
} TF_TString_Raw;

typedef union TF_TString_Union {  // NOLINT
  TF_TString_Large large;
  TF_TString_Offset offset;
  TF_TString_View view;
  TF_TString_Raw raw;
} TF_TString_Union;

enum {
  TF_TString_SmallCapacity =
      (sizeof(TF_TString_Union) - sizeof(/* null delim */ char) -
       sizeof(/* uint8_t size */ uint8_t)),
};

typedef struct TF_TString_Small {  // NOLINT
  uint8_t size;
  char str[TF_TString_SmallCapacity + sizeof(/* null delim */ char)];
} TF_TString_Small;

typedef struct TF_TString {  // NOLINT
  union {
    // small conflicts with '#define small char' in RpcNdr.h for MSVC, so we use
    // smll instead.
    TF_TString_Small smll;
    TF_TString_Large large;
    TF_TString_Offset offset;
    TF_TString_View view;
    TF_TString_Raw raw;
  } u;
} TF_TString;

// TODO(dero): Fix for OSS, and add C only build test.
// _Static_assert(CHAR_BIT == 8);
// _Static_assert(sizeof(TF_TString) == 24);

static inline TF_TString_Type TF_TString_GetType(const TF_TString *str) {
  return (TF_TString_Type)(str->u.raw.raw[0] & TF_TSTR_TYPE_MASK);  // NOLINT
}

// XXX(dero): For the big-endian case, this function could potentially be more
// performant and readable by always storing the string size as little-endian
// and always byte-swapping on big endian, resulting in a simple 'bswap'+'shr'
// (for architectures that have a bswap op).
static inline size_t TF_TString_ToActualSizeT(size_t size) {
#if TF_TSTRING_LITTLE_ENDIAN
  return size >> 2;
#else   // TF_TSTRING_LITTLE_ENDIAN
  // 0xFF000000 or 0xFF00000000000000 depending on platform
  static const size_t mask = ~((~(size_t)0) >> 8);

  return (((mask << 2) & size) >> 2) | (~mask & size);
#endif  // TF_TSTRING_LITTLE_ENDIAN
}

static inline size_t TF_TString_ToInternalSizeT(size_t size,
                                                TF_TString_Type type) {
#if TF_TSTRING_LITTLE_ENDIAN
  return (size << 2) | type;
#else   // TF_TSTRING_LITTLE_ENDIAN
  // 0xFF000000 or 0xFF00000000000000 depending on platform
  static const size_t mask = ~((~(size_t)0) >> 8);

  return (mask & (size << 2)) | (~mask & size) |
         ((size_t)type << ((sizeof(size_t) - 1) * 8));  // NOLINT
#endif  // TF_TSTRING_LITTLE_ENDIAN
}

static inline void TF_TString_Init(TF_TString *str) {
  memset(str->u.raw.raw, 0, sizeof(TF_TString_Raw));
}

static inline void TF_TString_Dealloc(TF_TString *str) {
  if (TF_TString_GetType(str) == TF_TSTR_LARGE &&
      str->u.large.ptr != NULL) {  // NOLINT
    free(str->u.large.ptr);
    TF_TString_Init(str);
  }
}

static inline size_t TF_TString_GetSize(const TF_TString *str) {
  switch (TF_TString_GetType(str)) {
    case TF_TSTR_SMALL:
      return str->u.smll.size >> 2;
    case TF_TSTR_LARGE:
      return TF_TString_ToActualSizeT(str->u.large.size);
    case TF_TSTR_OFFSET:
      return TF_le32toh(str->u.offset.size) >> 2;
    case TF_TSTR_VIEW:
      return TF_TString_ToActualSizeT(str->u.view.size);
    default:
      return 0;  // Unreachable.
  }
}

static inline size_t TF_TString_GetCapacity(const TF_TString *str) {
  switch (TF_TString_GetType(str)) {
    case TF_TSTR_SMALL:
      return TF_TString_SmallCapacity;
    case TF_TSTR_LARGE:
      return str->u.large.cap;
    case TF_TSTR_OFFSET:
    case TF_TSTR_VIEW:
    default:
      return 0;
  }
}

static inline const char *TF_TString_GetDataPointer(const TF_TString *str) {
  switch (TF_TString_GetType(str)) {
    case TF_TSTR_SMALL:
      return str->u.smll.str;
    case TF_TSTR_LARGE:
      return str->u.large.ptr;
    case TF_TSTR_OFFSET:
      return (const char *)str + str->u.offset.offset;  // NOLINT
    case TF_TSTR_VIEW:
      return str->u.view.ptr;
    default:
      // Unreachable.
      return NULL;  // NOLINT
  }
}

static inline char *TF_TString_ResizeUninitialized(TF_TString *str,
                                                   size_t new_size) {
  size_t curr_size = TF_TString_GetSize(str);
  size_t copy_size = TF_min(new_size, curr_size);

  TF_TString_Type curr_type = TF_TString_GetType(str);
  const char *curr_ptr = TF_TString_GetDataPointer(str);

  // Case: SMALL/LARGE/VIEW/OFFSET -> SMALL
  if (new_size <= TF_TString_SmallCapacity) {
    str->u.smll.size = (uint8_t)((new_size << 2) | TF_TSTR_SMALL);  // NOLINT
    str->u.smll.str[new_size] = '\0';

    if (curr_type != TF_TSTR_SMALL && copy_size) {
      memcpy(str->u.smll.str, curr_ptr, copy_size);
    }

    if (curr_type == TF_TSTR_LARGE) {
      free((void *)curr_ptr);  // NOLINT
    }

    // We do not clear out the newly excluded region.

    return str->u.smll.str;
  }

  // Case: SMALL/LARGE/VIEW/OFFSET -> LARGE
  size_t new_cap;
  size_t curr_cap = TF_TString_GetCapacity(str);

  if (new_size < curr_size && new_size < curr_cap / 2) {
    // TODO(dero): Replace with shrink_to_fit flag.
    new_cap = TF_align16(curr_cap / 2 + 1) - 1;
  } else if (new_size > curr_cap) {
    new_cap = TF_align16(new_size + 1) - 1;
  } else {
    new_cap = curr_cap;
  }

  char *new_ptr;
  if (new_cap == curr_cap) {
    new_ptr = str->u.large.ptr;
  } else if (curr_type == TF_TSTR_LARGE) {
    new_ptr = (char *)realloc(str->u.large.ptr, new_cap + 1);  // NOLINT
  } else {
    new_ptr = (char *)malloc(new_cap + 1);  // NOLINT
    if (copy_size) {
      memcpy(new_ptr, curr_ptr, copy_size);
    }
  }

  str->u.large.size = TF_TString_ToInternalSizeT(new_size, TF_TSTR_LARGE);
  str->u.large.ptr = new_ptr;
  str->u.large.ptr[new_size] = '\0';
  str->u.large.cap = new_cap;

  return str->u.large.ptr;
}

static inline char *TF_TString_GetMutableDataPointer(TF_TString *str) {
  switch (TF_TString_GetType(str)) {
    case TF_TSTR_SMALL:
      return str->u.smll.str;
    case TF_TSTR_OFFSET:
    case TF_TSTR_VIEW:
      // Convert OFFSET/VIEW to SMALL/LARGE
      TF_TString_ResizeUninitialized(str, TF_TString_GetSize(str));
      return (TF_TString_GetType(str) == TF_TSTR_SMALL) ? str->u.smll.str
                                                        : str->u.large.ptr;
    case TF_TSTR_LARGE:
      return str->u.large.ptr;
    default:
      // Unreachable.
      return NULL;  // NOLINT
  }
}

static inline void TF_TString_Reserve(TF_TString *str, size_t new_cap) {
  TF_TString_Type curr_type = TF_TString_GetType(str);

  if (new_cap <= TF_TString_SmallCapacity) {
    // We do nothing, we let Resize/GetMutableDataPointer handle the
    // conversion to SMALL from VIEW/OFFSET when the need arises.
    // In the degenerate case, where new_cap <= TF_TString_SmallCapacity,
    // curr_size > TF_TString_SmallCapacity, and the type is VIEW/OFFSET, we
    // defer the malloc to Resize/GetMutableDataPointer.
    return;
  }

  if (curr_type == TF_TSTR_LARGE && new_cap <= str->u.large.cap) {
    // We handle reduced cap in resize.
    return;
  }

  // Case: VIEW/OFFSET -> LARGE or grow an existing LARGE type
  size_t curr_size = TF_TString_GetSize(str);
  const char *curr_ptr = TF_TString_GetDataPointer(str);

  // Since VIEW and OFFSET types are read-only, their capacity is effectively 0.
  // So we make sure we have enough room in the VIEW and OFFSET cases.
  new_cap = TF_align16(TF_max(new_cap, curr_size) + 1) - 1;

  if (curr_type == TF_TSTR_LARGE) {
    str->u.large.ptr =
        (char *)realloc(str->u.large.ptr, new_cap + 1);  // NOLINT
  } else {
    // Convert to Large
    char *new_ptr = (char *)malloc(new_cap + 1);  // NOLINT
    memcpy(new_ptr, curr_ptr, curr_size);

    str->u.large.size = TF_TString_ToInternalSizeT(curr_size, TF_TSTR_LARGE);
    str->u.large.ptr = new_ptr;
    str->u.large.ptr[curr_size] = '\0';
  }

  str->u.large.cap = new_cap;
}

static inline void TF_TString_ReserveAmortized(TF_TString *str,
                                               size_t new_cap) {
  const size_t curr_cap = TF_TString_GetCapacity(str);
  if (new_cap > curr_cap) {
    TF_TString_Reserve(str, new_cap > 2 * curr_cap ? new_cap : 2 * curr_cap);
  }
}

static inline char *TF_TString_Resize(TF_TString *str, size_t new_size,
                                      char c) {
  size_t curr_size = TF_TString_GetSize(str);
  char *cstr = TF_TString_ResizeUninitialized(str, new_size);

  if (new_size > curr_size) {
    memset(cstr + curr_size, c, new_size - curr_size);
  }

  return cstr;
}

static inline void TF_TString_AssignView(TF_TString *dst, const char *src,
                                         size_t size) {
  TF_TString_Dealloc(dst);

  dst->u.view.size = TF_TString_ToInternalSizeT(size, TF_TSTR_VIEW);
  dst->u.view.ptr = src;
}

static inline void TF_TString_AppendN(TF_TString *dst, const char *src,
                                      size_t src_size) {
  if (!src_size) return;

  size_t dst_size = TF_TString_GetSize(dst);

  // For append use cases, we want to ensure amortized growth.
  TF_TString_ReserveAmortized(dst, dst_size + src_size);
  char *dst_c = TF_TString_ResizeUninitialized(dst, dst_size + src_size);

  memcpy(dst_c + dst_size, src, src_size);
}

static inline void TF_TString_Append(TF_TString *dst, const TF_TString *src) {
  const char *src_c = TF_TString_GetDataPointer(src);
  size_t size = TF_TString_GetSize(src);

  TF_TString_AppendN(dst, src_c, size);
}

static inline void TF_TString_Copy(TF_TString *dst, const char *src,
                                   size_t size) {
  char *dst_c = TF_TString_ResizeUninitialized(dst, size);

  if (size) memcpy(dst_c, src, size);
}

static inline void TF_TString_Assign(TF_TString *dst, const TF_TString *src) {
  if (dst == src) return;

  TF_TString_Dealloc(dst);

  switch (TF_TString_GetType(src)) {
    case TF_TSTR_SMALL:
    case TF_TSTR_VIEW:
      *dst = *src;
      return;
    case TF_TSTR_LARGE: {
      const char *src_c = TF_TString_GetDataPointer(src);
      size_t size = TF_TString_GetSize(src);

      TF_TString_Copy(dst, src_c, size);
    }
      return;
    case TF_TSTR_OFFSET: {
      const char *src_c = TF_TString_GetDataPointer(src);
      size_t size = TF_TString_GetSize(src);

      TF_TString_AssignView(dst, src_c, size);
    }
      return;
    default:
      return;  // Unreachable.
  }
}

static inline void TF_TString_Move(TF_TString *dst, TF_TString *src) {
  if (dst == src) return;

  TF_TString_Dealloc(dst);

  switch (TF_TString_GetType(src)) {
    case TF_TSTR_SMALL:
    case TF_TSTR_VIEW:
      *dst = *src;
      return;
    case TF_TSTR_LARGE:
      *dst = *src;
      TF_TString_Init(src);
      return;
    case TF_TSTR_OFFSET: {
      const char *src_c = TF_TString_GetDataPointer(src);
      size_t size = TF_TString_GetSize(src);

      TF_TString_AssignView(dst, src_c, size);
    }
      return;
    default:
      return;  // Unreachable.
  }
}

#endif  // TENSORFLOW_CORE_PLATFORM_CTSTRING_INTERNAL_H_
