/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/**
 * High performance primitive functions
 *
 **/

#ifndef PRIMITIVES_H_
#define PRIMITIVES_H_

#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <string>

#ifdef __GNUC__
#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)
#else
#define likely(x)       (x)
#define unlikely(x)     (x)
#endif

//#define SIMPLE_MEMCPY

#if !defined(SIMPLE_MEMCPY)
#define simple_memcpy memcpy
#define simple_memcpy2 memcpy
#else

/**
 * This memcpy assumes src & dest are not overlapped,
 * and len are normally very small(<64)
 * This function is primarily optimized for x86-64 processors,
 * on which unaligned 64-bit loads and stores are cheap
 *
 * @param dest: dest buffer
 * @param src:  src buffer
 * @param len: src buffer size, must be >0
 */
inline void simple_memcpy(void * dest, const void * src, size_t len) {
  const uint8_t * src8 = (const uint8_t*)src;
  uint8_t * dest8 = (uint8_t*)dest;
  switch (len) {
    case 0:
    return;
    case 1:
    dest8[0]=src8[0];
    return;
    case 2:
    *(uint16_t*)dest8=*(const uint16_t*)src8;
    return;
    case 3:
    *(uint16_t*)dest8 = *(const uint16_t*)src8;
    dest8[2]=src8[2];
    return;
    case 4:
    *(uint32_t*)dest8 = *(const uint32_t*)src8;
    return;
  }
  if (len<8) {
    *(uint32_t*)dest8 = *(const uint32_t*)src8;
    *(uint32_t*)(dest8+len-4) = *(const uint32_t*)(src8+len-4);
    return;
  }
  if (len<128) {
    int64_t cur = (int64_t)len - 8;
    while (cur>0) {
      *(uint64_t*)(dest8+cur) = *(const uint64_t*)(src8+cur);
      cur -= 8;
    }
    *(uint64_t*)(dest8) = *(const uint64_t*)(src8);
    return;
  }
  ::memcpy(dest, src, len);
}

#endif

/**
 * little-endian to big-endian or vice versa
 */
inline uint32_t bswap(uint32_t val) {
#ifdef __aarch64__
  __asm__("rev %w[dst], %w[src]" : [dst]"=r"(val) : [src]"r"(val));
#else
  __asm__("bswap %0" : "=r" (val) : "0" (val));
#endif
  return val;
}

inline uint64_t bswap64(uint64_t val) {
#ifdef __aarch64__
  __asm__("rev %[dst], %[src]" : [dst]"=r"(val) : [src]"r"(val));
#else
#ifdef __X64
  __asm__("bswapq %0" : "=r" (val) : "0" (val));
#else

  uint64_t lower = val & 0xffffffffU;
  uint32_t higher = (val >> 32) & 0xffffffffU;

  lower = bswap(lower);
  higher = bswap(higher);

  return (lower << 32) + higher;

#endif
#endif
  return val;
}

/**
 * Fast memcmp
 */
inline int64_t fmemcmp(const char * src, const char * dest, uint32_t len) {

#ifdef BUILDIN_MEMCMP
  return memcmp(src, dest, len);
#else

  const uint8_t * src8 = (const uint8_t*)src;
  const uint8_t * dest8 = (const uint8_t*)dest;
  switch (len) {
  case 0:
    return 0;
  case 1:
    return (int64_t)src8[0] - (int64_t)dest8[0];
  case 2: {
    int64_t ret = ((int64_t)src8[0] - (int64_t)dest8[0]);
    if (ret)
      return ret;
    return ((int64_t)src8[1] - (int64_t)dest8[1]);
  }
  case 3: {
    int64_t ret = ((int64_t)src8[0] - (int64_t)dest8[0]);
    if (ret)
      return ret;
    ret = ((int64_t)src8[1] - (int64_t)dest8[1]);
    if (ret)
      return ret;
    return ((int64_t)src8[2] - (int64_t)dest8[2]);
  }
  case 4: {
    return (int64_t)bswap(*(uint32_t*)src) - (int64_t)bswap(*(uint32_t*)dest);
  }
  }
  if (len < 8) {
    int64_t ret = ((int64_t)bswap(*(uint32_t*)src) - (int64_t)bswap(*(uint32_t*)dest));
    if (ret) {
      return ret;
    }
    return ((int64_t)bswap(*(uint32_t*)(src + len - 4))
        - (int64_t)bswap(*(uint32_t*)(dest + len - 4)));
  }
  uint32_t cur = 0;
  uint32_t end = len & (0xffffffffU << 3);
  while (cur < end) {
    uint64_t l = *(uint64_t*)(src8 + cur);
    uint64_t r = *(uint64_t*)(dest8 + cur);
    if (l != r) {
      l = bswap64(l);
      r = bswap64(r);
      return l > r ? 1 : -1;
    }
    cur += 8;
  }
  uint64_t l = *(uint64_t*)(src8 + len - 8);
  uint64_t r = *(uint64_t*)(dest8 + len - 8);
  if (l != r) {
    l = bswap64(l);
    r = bswap64(r);
    return l > r ? 1 : -1;
  }
  return 0;
#endif
}

inline int64_t fmemcmp(const char * src, const char * dest, uint32_t srcLen, uint32_t destLen) {
  uint32_t minlen = srcLen < destLen ? srcLen : destLen;
  int64_t ret = fmemcmp(src, dest, minlen);
  if (ret) {
    return ret;
  }
  return (int64_t)srcLen - (int64_t)destLen;
}

/**
 * Fast memory equal
 */
inline bool fmemeq(const char * src, const char * dest, uint32_t len) {
#ifdef BUILDIN_MEMCMP
  return 0 == memcmp(src, dest, len);
#else

  const uint8_t * src8 = (const uint8_t*)src;
  const uint8_t * dest8 = (const uint8_t*)dest;
  switch (len) {
  case 0:
    return true;
  case 1:
    return src8[0] == dest8[0];
  case 2:
    return *(uint16_t*)src8 == *(uint16_t*)dest8;
  case 3:
    return (*(uint16_t*)src8 == *(uint16_t*)dest8) && (src8[2] == dest8[2]);
  case 4:
    return *(uint32_t*)src8 == *(uint32_t*)dest8;
  }
  if (len < 8) {
    return (*(uint32_t*)src8 == *(uint32_t*)dest8)
        && (*(uint32_t*)(src8 + len - 4) == *(uint32_t*)(dest8 + len - 4));
  }
  uint32_t cur = 0;
  uint32_t end = len & (0xffffffff << 3);
  while (cur < end) {
    uint64_t l = *(uint64_t*)(src8 + cur);
    uint64_t r = *(uint64_t*)(dest8 + cur);
    if (l != r) {
      return false;
    }
    cur += 8;
  }
  uint64_t l = *(uint64_t*)(src8 + len - 8);
  uint64_t r = *(uint64_t*)(dest8 + len - 8);
  if (l != r) {
    return false;
  }
  return true;
#endif
}

inline bool fmemeq(const char * src, uint32_t srcLen, const char * dest, uint32_t destLen) {
  if (srcLen != destLen) {
    return false;
  }
  return fmemeq(src, dest, std::min(srcLen, destLen));
}

/**
 * Fast memory equal, reverse order
 */
inline bool frmemeq(const char * src, const char * dest, uint32_t len) {
  const uint8_t * src8 = (const uint8_t*)src;
  const uint8_t * dest8 = (const uint8_t*)dest;
  switch (len) {
  case 0:
    return true;
  case 1:
    return src8[0] == dest8[0];
  case 2:
    return *(uint16_t*)src8 == *(uint16_t*)dest8;
  case 3:
    return (src8[2] == dest8[2]) && (*(uint16_t*)src8 == *(uint16_t*)dest8);
  case 4:
    return *(uint32_t*)src8 == *(uint32_t*)dest8;
  }
  if (len < 8) {
    return (*(uint32_t*)(src8 + len - 4) == *(uint32_t*)(dest8 + len - 4))
        && (*(uint32_t*)src8 == *(uint32_t*)dest8);
  }
  int32_t cur = (int32_t)len - 8;
  while (cur > 0) {
    if (*(uint64_t*)(src8 + cur) != *(uint64_t*)(dest8 + cur)) {
      return false;
    }
    cur -= 8;
  }
  return *(uint64_t*)(src8) == *(uint64_t*)(dest8);
}

inline bool frmemeq(const char * src, const char * dest, uint32_t srcLen, uint32_t destLen) {
  if (srcLen != destLen) {
    return false;
  }
  return frmemeq(src, dest, std::min(srcLen, destLen));
}

#endif /* PRIMITIVES_H_ */
