/*
 * Copyright Red Hat
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/uds-releases/jasper/src/uds/numeric.h#2 $
 */

#ifndef NUMERIC_H
#define NUMERIC_H 1

#include "compiler.h"
#include "numericDefs.h"
#include "typeDefs.h"

#if !defined(__ORDER_LITTLE_ENDIAN__) || !defined(__ORDER_BIG_ENDIAN__) \
  || !defined(__BYTE_ORDER__)
#error "GCC byte order macros not defined?"
#endif

/*
 * Define a type describing an integer value that is only byte-aligned
 * and may explicitly alias other types.  GCC keeps getting better
 * about type-based alias analysis (both for optimization and for
 * warnings), so simply casting a pointer to pointer-to-uintXX_t isn't
 * good enough.
 *
 * C is okay with defining the structures directly in a cast, but
 * C++ is not, and we use this header in some C++ code internally.
 */
#define UNALIGNED_WRAPPER(TYPE)                 \
  unaligned_wrap_##TYPE
#define UNALIGNED_WRAPPER_DEF(TYPE)                                 \
  typedef struct __attribute__((packed, may_alias)) { TYPE value; } \
  UNALIGNED_WRAPPER(TYPE)
UNALIGNED_WRAPPER_DEF(int64_t);
UNALIGNED_WRAPPER_DEF(uint64_t);
UNALIGNED_WRAPPER_DEF(int32_t);
UNALIGNED_WRAPPER_DEF(uint32_t);
UNALIGNED_WRAPPER_DEF(uint16_t);

#define GET_UNALIGNED(TYPE,ADDR)                        \
  (((const UNALIGNED_WRAPPER(TYPE) *)(ADDR))->value)
#define PUT_UNALIGNED(TYPE,ADDR,VALUE)                  \
  (((UNALIGNED_WRAPPER(TYPE) *)(ADDR))->value = (VALUE))

/**
 * Find the minimum of two ints.
 *
 * @param a The first int
 * @param b The second int
 *
 * @return The lesser of a and b
 **/
__attribute__((warn_unused_result))
static INLINE int minInt(int a, int b)
{
  return ((a < b) ? a : b);
}

/**
 * Find the maximum of two ints.
 *
 * @param a The first int
 * @param b The second int
 *
 * @return The greater of a and b
 **/
__attribute__((warn_unused_result))
static INLINE int maxInt(int a, int b)
{
  return ((a > b) ? a : b);
}

/**
 * Find the maximum of two unsigned ints.
 *
 * @param a The first value
 * @param b The second value
 *
 * @return The greater of a and b
 **/
__attribute__((warn_unused_result))
static INLINE unsigned int maxUInt(unsigned int a, unsigned int b)
{
  return ((a > b) ? a : b);
}

/**
 * Find the maximum of two signed longs.
 *
 * @param a The first int
 * @param b The second int
 *
 * @return The greater of a and b
 **/
__attribute__((warn_unused_result))
static INLINE long maxLong(long a, long b)
{
  return ((a > b) ? a : b);
}

/**
 * Find the maximum of two unsigned longs.
 *
 * @param a The first int
 * @param b The second int
 *
 * @return The greater of a and b
 **/
__attribute__((warn_unused_result))
static INLINE unsigned long maxULong(unsigned long a, unsigned long b)
{
  return ((a > b) ? a : b);
}

/**
 * Find the minimum of two size_ts.
 *
 * @param a The first size_t
 * @param b The second size_t
 *
 * @return The lesser of a and b
 **/
__attribute__((warn_unused_result))
static INLINE size_t minSizeT(size_t a, size_t b)
{
  return ((a < b) ? a : b);
}

/**
 * Find the maximum of two size_ts.
 *
 * @param a The first size_t
 * @param b The second size_t
 *
 * @return The greater of a and b
 **/
__attribute__((warn_unused_result))
static INLINE size_t maxSizeT(size_t a, size_t b)
{
  return ((a > b) ? a : b);
}

/**
 * Find the minimum of two uint64_ts.
 *
 * @param a The first uint64_t
 * @param b The second uint64_t
 *
 * @return The lesser of a and b
 **/
__attribute__((warn_unused_result))
static INLINE uint64_t minUInt64(uint64_t a, uint64_t b)
{
  return ((a < b) ? a : b);
}

/**
 * Multiply two uint64_t and check for overflow. Does division.
 **/
bool multiplyWouldOverflow(uint64_t a, uint64_t b);

/**
 * Extract a 64 bit unsigned number from a buffer stored in
 * big-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint64_t getUInt64BE(const byte* data)
{
  uint64_t num = GET_UNALIGNED(uint64_t, data);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  return num;
}

/**
 * Extract a 64 bit unsigned big-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt64BE(const byte *buffer,
                                  size_t     *offset,
                                  uint64_t   *decoded)
{
  *decoded = getUInt64BE(buffer + *offset);
  *offset += sizeof(uint64_t);
}

/**
 * Store a 64 bit unsigned number in a buffer in
 * big-endian representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt64BE(byte* data, uint64_t num)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  PUT_UNALIGNED(uint64_t, data, num);
}

/**
 * Encode a 64 bit unsigned number into a buffer at a given offset
 * using a big-endian representation. The offset will be advanced to
 * first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt64BE(byte     *data,
                                  size_t   *offset,
                                  uint64_t  toEncode)
{
  storeUInt64BE(data + *offset, toEncode);
  *offset += sizeof(uint64_t);
}

/**
 * Extract a 32 bit unsigned number from a buffer stored in big-endian
 * representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint32_t getUInt32BE(const byte* data)
{
  uint32_t num = GET_UNALIGNED(uint32_t, data);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  return num;
}

/**
 * Extract a 32 bit unsigned big-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt32BE(const byte *buffer,
                                  size_t     *offset,
                                  uint32_t   *decoded)
{
  *decoded = getUInt32BE(buffer + *offset);
  *offset += sizeof(uint32_t);
}

/**
 * Store a 32 bit number in a buffer in
 * big-endian representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt32BE(byte* data, uint32_t num)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  PUT_UNALIGNED(uint32_t, data, num);
}

/**
 * Encode a 32 bit number into a buffer at a given offset using a
 * big-endian representation. The offset will be advanced to first byte
 * after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt32BE(byte     *data,
                                  size_t   *offset,
                                  uint32_t  toEncode)
{
  storeUInt32BE(data + *offset, toEncode);
  *offset += sizeof(uint32_t);
}

/**
 * Extract a 16 bit number from a buffer stored in
 * big-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint16_t getUInt16BE(const byte* data)
{
  uint16_t num = GET_UNALIGNED(uint16_t, data);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = bswap_16(num);
#endif
  return num;
}

/**
 * Extract a 16 bit, big-endian number from a buffer at a specified offset.
 * The offset will be advanced to the first byte after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to
 *                extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt16BE(const byte *buffer,
                                  size_t     *offset,
                                  uint16_t   *decoded)
{
  *decoded = getUInt16BE(buffer + *offset);
  *offset += sizeof(uint16_t);
}

/**
 * Store a 16 bit number in a buffer in
 * big-endian representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt16BE(byte* data, uint16_t num)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  num = bswap_16(num);
#endif
  PUT_UNALIGNED(uint16_t, data, num);
}

/**
 * Encode a 16 bit number into a buffer at a given offset using a
 * big-endian representation. The offset will be advanced to first byte
 * after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt16BE(byte     *data,
                                  size_t   *offset,
                                  uint16_t  toEncode)
{
  storeUInt16BE(data + *offset, toEncode);
  *offset += sizeof(uint16_t);
}

/**
 * Extract a 64 bit signed number from a buffer stored in
 * little-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE int64_t getInt64LE(const byte* data)
{
  int64_t num = GET_UNALIGNED(int64_t, data);
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  return num;
}

/**
 * Extract a 64 bit signed little-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeInt64LE(const byte *buffer,
                                 size_t     *offset,
                                 int64_t   *decoded)
{
  *decoded = getInt64LE(buffer + *offset);
  *offset += sizeof(int64_t);
}

/**
 * Store a signed 64 bit number in a buffer in little-endian
 * representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeInt64LE(byte* data, int64_t num)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  PUT_UNALIGNED(int64_t, data, num);
}

/**
 * Encode a 64 bit signed number into a buffer at a given offset using
 * a little-endian representation. The offset will be advanced to
 * first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeInt64LE(byte    *data,
                                 size_t  *offset,
                                 int64_t  toEncode)
{
  storeInt64LE(data + *offset, toEncode);
  *offset += sizeof(int64_t);
}

/**
 * Extract a 64 bit number from a buffer stored in
 * little-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint64_t getUInt64LE(const byte* data)
{
  uint64_t num = GET_UNALIGNED(uint64_t, data);
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  return num;
}

/**
 * Extract a 64 bit unsigned little-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt64LE(const byte *buffer,
                                  size_t     *offset,
                                  uint64_t   *decoded)
{
  *decoded = getUInt64LE(buffer + *offset);
  *offset += sizeof(uint64_t);
}

/**
 * Store a 64 bit unsigned number in a buffer in little-endian
 * representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt64LE(byte* data, uint64_t num)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap64(num);
#endif
  PUT_UNALIGNED(uint64_t, data, num);
}

/**
 * Encode a 64 bit unsigned number into a buffer at a given offset
 * using a little-endian representation. The offset will be advanced
 * to first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt64LE(byte     *data,
                                  size_t   *offset,
                                  uint64_t  toEncode)
{
  storeUInt64LE(data + *offset, toEncode);
  *offset += sizeof(uint64_t);
}

/**
 * Extract a 32 bit signed number from a buffer stored in
 * little-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE int32_t getInt32LE(const byte* data)
{
  int32_t num = GET_UNALIGNED(int32_t, data);
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  return num;
}

/**
 * Extract a 32 bit signed little-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeInt32LE(const byte *buffer,
                                 size_t     *offset,
                                 int32_t   *decoded)
{
  *decoded = getInt32LE(buffer + *offset);
  *offset += sizeof(int32_t);
}

/**
 * Store a signed 32 bit number in a buffer in little-endian
 * representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeInt32LE(byte* data, int32_t num)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  PUT_UNALIGNED(int32_t, data, num);
}

/**
 * Encode a 32 bit signed number into a buffer at a given offset using
 * a little-endian representation. The offset will be advanced to
 * first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeInt32LE(byte    *data,
                                 size_t  *offset,
                                 int32_t  toEncode)
{
  storeInt32LE(data + *offset, toEncode);
  *offset += sizeof(int32_t);
}

/**
 * Extract a 32 bit unsigned number from a buffer stored in
 * little-endian representation.

 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint32_t getUInt32LE(const byte* data)
{
  uint32_t num = GET_UNALIGNED(uint32_t, data);
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  return num;
}

/**
 * Extract a 32 bit unsigned little-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt32LE(const byte *buffer,
                                  size_t     *offset,
                                  uint32_t   *decoded)
{
  *decoded = getUInt32LE(buffer + *offset);
  *offset += sizeof(uint32_t);
}

/**
 * Store a 32 bit unsigned number in a buffer in little-endian
 * representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt32LE(byte* data, uint32_t num)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = __builtin_bswap32(num);
#endif
  PUT_UNALIGNED(uint32_t, data, num);
}

/**
 * Encode a 32 bit unsigned number into a buffer at a given offset
 * using a little-endian representation. The offset will be advanced
 * to first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt32LE(byte     *data,
                                  size_t   *offset,
                                  uint32_t  toEncode)
{
  storeUInt32LE(data + *offset, toEncode);
  *offset += sizeof(uint32_t);
}

/**
 * Extract a 16 bit number from a buffer stored in
 * little-endian representation.
 *
 * @param data The buffer from which to extract the number
 *
 * @return The extracted quantity
 **/
__attribute__((warn_unused_result))
static INLINE uint16_t getUInt16LE(const byte* data)
{
  uint16_t num = GET_UNALIGNED(uint16_t, data);
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = bswap_16(num);
#endif
  return num;
}

/**
 * Extract a 16 bit unsigned little-endian number from a buffer at a
 * specified offset.  The offset will be advanced to the first byte
 * after the number.
 *
 * @param buffer  The buffer from which to extract the number
 * @param offset  A pointer to the offset into the buffer at which to
 *                extract
 * @param decoded A pointer to hold the extracted number
 **/
static INLINE void decodeUInt16LE(const byte *buffer,
                                  size_t     *offset,
                                  uint16_t   *decoded)
{
  *decoded = getUInt16LE(buffer + *offset);
  *offset += sizeof(uint16_t);
}

/**
 * Store a 16 bit number in a buffer in little-endian representation.
 *
 * @param data The buffer in which to store the number
 * @param num  The number to store
 **/
static INLINE void storeUInt16LE(byte* data, uint16_t num)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  num = bswap_16(num);
#endif
  PUT_UNALIGNED(uint16_t, data, num);
}

/**
 * Encode a 16 bit unsigned number into a buffer at a given offset
 * using a little-endian representation. The offset will be advanced
 * to first byte after the encoded number.
 *
 * @param data     The buffer to encode into
 * @param offset   A pointer to the offset at which to start encoding
 * @param toEncode The number to encode
 **/
static INLINE void encodeUInt16LE(byte     *data,
                                  size_t   *offset,
                                  uint16_t  toEncode)
{
  storeUInt16LE(data + *offset, toEncode);
  *offset += sizeof(uint16_t);
}

/**
 * Special function wrapper required for compile-time assertions. This
 * function will fail to compile if any of the uint*_t types are not of the
 * size we expect. This function should never be called.
 **/
void numericCompileTimeAssertions(void);

#endif /* NUMERIC_H */
