/* KinoSearch/Util/MathUtils.h -- various math utilities
 * 
 * Provide various math related utilities, including endcoding/decoding of
 * VInts and integers in guaranteed Big-endian byte order.
 */

#ifndef H_KINO_MATHUTILS
#define H_KINO_MATHUTILS 1

#include "charmony.h"

#ifdef CHY_BIG_END

/* Encode an unsigned 32-bit integer as 4 bytes in the buffer provided, using
 * big-endian byte order.  Advance buffer pointer by 4 bytes.
 */
  #define KINO_MATH_ENCODE_U32(_num, _dest) \
    do { \
        if (sizeof(_num) == sizeof(chy_u32_t)) { \
            memcpy(_dest, &_num, sizeof(chy_u32_t)); \
        } \
        else { \
            const chy_u32_t _num_copy = _num; \
            memcpy(_dest, &_num_copy, sizeof(chy_u32_t)); \
        } \
    } while (0)

/* Encode an unsigned 16-bit integer as 2 bytes in the buffer provided, using
 * big-endian byte order.  Advance buffer pointer by 2 bytes.
 */
  #define KINO_MATH_ENCODE_U16(_num, _dest) \
    do { \
        if (sizeof(_num) == sizeof(chy_u16_t)) { \
            memcpy(_dest, &_num, sizeof(chy_u16_t)); \
        } \
        else { \
            const chy_u16_t _num_copy = _num; \
            memcpy(_dest, &_num_copy, sizeof(chy_u16_t)); \
        } \
    } while (0)
    
#else /* little endian */

  #define KINO_MATH_ENCODE_U32(_num, _dest) \
    do { \
        const u32_t _num_copy  = _num; \
        u8_t *const _dest_copy = (u8_t*)_dest; \
        _dest_copy[0] = (_num_copy & 0xff000000) >> 24; \
        _dest_copy[1] = (_num_copy & 0x00ff0000) >> 16; \
        _dest_copy[2] = (_num_copy & 0x0000ff00) >> 8; \
        _dest_copy[3] = (_num_copy & 0x000000ff); \
    } while (0)

  #define KINO_MATH_ENCODE_U16(_num, _dest) \
    do { \
        const u16_t _num_copy  = _num; \
        u8_t *const _dest_copy = (u8_t*)_dest; \
        _dest_copy[0] = (_num_copy & 0xff00) >> 8; \
        _dest_copy[1] = (_num_copy & 0x000000ff); \
    } while (0)

#endif /* CHY_BIG_END (and little endian) */

/* Interpret a sequence of bytes as a big-endian unsigned 32-bit int.  The
 * buffer need not be aligned to word size.  
 */
#define KINO_MATH_DECODE_U32(_num, _source) \
    do { \
        u8_t *const _buf = (u8_t*)_source; \
        _num =  (_buf[0]  << 24) | \
                (_buf[1]  << 16) | \
                (_buf[2]  << 8)  | \
                (_buf[3]); \
    } while (0)

/* Wrapper around DECODE_U32 suitable for C89 initialization.
 */
chy_u32_t 
kino_Math_decode_bigend_u32(void *vbuf);

/* Interpret a sequence of bytes as a big-endian unsigned 16-bit int.  The
 * buffer need not be aligned to word size.  
 */
#define KINO_MATH_DECODE_U16(_num, _source) \
    do { \
        u8_t *const _buf = (u8_t*)_source; \
        _num = (_buf[0] << 8) | _buf[1]; \
    } while (0)

/* Wrapper around DECODE_U16 suitable for C89 initialization.
 */
chy_u16_t 
kino_Math_decode_bigend_u16(void *vbuf);

#define KINO_MATH_VINT_MAX_BYTES  ((sizeof(u32_t) * 8 / 7) + 1)   /* 5 */
#define KINO_MATH_VLONG_MAX_BYTES ((sizeof(u64_t) * 8 / 7) + 1)   /* 10 */

/* Encode a VInt at the space pointed to by the provided pointer, which must
 * be either a char* or a uchar*.  The pointer will be advanced to immediately
 * after the end of the VInt.
 */
#define KINO_MATH_ENCODE_VINT(_number, _out_buf) \
    do { \
        chy_u8_t _buf[KINO_MATH_VINT_MAX_BYTES]; \
        chy_u32_t _aU32        = _number; \
        chy_u8_t *const _limit = _buf + sizeof(_buf); \
        chy_u8_t *_ptr         = _limit - 1; \
        int       _num_bytes; \
        /* write last byte first, which has no continue bit */ \
        *_ptr = _aU32 & 0x7f; \
        _aU32 >>= 7; \
        while (_aU32) { \
            /* work backwards, writing bytes with continue bits set */ \
            *--_ptr = ((_aU32 & 0x7f) | 0x80); \
            _aU32 >>= 7; \
        } \
        _num_bytes = _limit - _ptr; \
        memcpy(_out_buf, _ptr, _num_bytes); \
        _out_buf += _num_bytes; \
    } while (0)

/* Same as above, but using 64-bit vars.
 */
#define KINO_MATH_ENCODE_VLONG(_number, _out_buf) \
    do { \
        chy_u8_t _buf[KINO_MATH_VLONG_MAX_BYTES]; \
        chy_u64_t _aQuad       = _number; \
        chy_u8_t *const _limit = _buf + sizeof(_buf); \
        chy_u8_t *_ptr         = _limit - 1; \
        int       _num_bytes; \
        /* write last byte first, which has no continue bit */ \
        *_ptr = _aQuad & 0x7f; \
        _aQuad >>= 7; \
        while (_aQuad) { \
            /* work backwards, writing bytes with continue bits set */ \
            *--_ptr = ((_aQuad & 0x7f) | 0x80); \
            _aQuad >>= 7; \
        } \
        _num_bytes = _limit - _ptr; \
        memcpy(_out_buf, _ptr, _num_bytes); \
        _out_buf += _num_bytes; \
    } while (0)

/* Encode a VInt, as above, but add "leading zeroes" so that the space
 * consumed will always be 5 bytes.
 */
#define KINO_MATH_ENCODE_FULL_VINT(_number, _out_buf) \
    do { \
        chy_u8_t _buf[KINO_MATH_VINT_MAX_BYTES] \
            = { 0x80, 0x80, 0x80, 0x80, 0x80 }; \
        chy_u32_t _aU32        = _number; \
        chy_u8_t *const _limit = _buf + sizeof(_buf); \
        chy_u8_t *_ptr         = _limit - 1; \
        /* write last byte first, which has no continue bit */ \
        *_ptr = _aU32 & 0x7f; \
        _aU32 >>= 7; \
        while (_aU32) { \
            /* work backwards, writing bytes with continue bits set */ \
            *--_ptr = ((_aU32 & 0x7f) | 0x80); \
            _aU32 >>= 7; \
        } \
        memcpy(_out_buf, _buf, KINO_MATH_VINT_MAX_BYTES); \
    } while (0)

/* Read a varible integer from the buffer pointed to by the source pointer.
 * While reading, advance the pointer, consuming the bytes occupied by the
 * VInt.
 */
#define KINO_MATH_DECODE_VINT(_var, _source_ptr) \
    do { \
        _var = 0; \
        do { \
            _var = (_var << 7) | (*(chy_u8_t*)(_source_ptr) & 0x7f); \
        } while ((*(chy_u8_t*)((_source_ptr)++) & 0x80) != 0); \
    } while (0)

/* So long as _var is a u64_t, the VINT macro works for a VLong as well.
 */
#define KINO_MATH_DECODE_VLONG KINO_MATH_DECODE_VINT

/* Wrapper function for DECODE_VINT suitable C89 initialization.
 */
chy_u32_t
kino_Math_decode_vint(char **source_ptr);

/* Advance a pointer past one encoded VInt.
 */
#define KINO_MATH_SKIP_VINT(_source_ptr) \
    do { \
        while ((*(chy_u8_t*)((_source_ptr)++) & 0x80) != 0) { } \
    } while (0)

#ifdef KINO_USE_SHORT_NAMES
  #define MATH_ENCODE_U32           KINO_MATH_ENCODE_U32
  #define MATH_ENCODE_U16           KINO_MATH_ENCODE_U16
  #define MATH_DECODE_U32           KINO_MATH_DECODE_U32
  #define MATH_DECODE_U16           KINO_MATH_DECODE_U16
  #define Math_decode_bigend_u32    kino_Math_decode_bigend_u32
  #define Math_decode_bigend_u16    kino_Math_decode_bigend_u16
  #define VINT_MAX_BYTES            KINO_MATH_VINT_MAX_BYTES
  #define VLONG_MAX_BYTES           KINO_MATH_VLONG_MAX_BYTES
  #define ENCODE_VINT               KINO_MATH_ENCODE_VINT
  #define ENCODE_VLONG              KINO_MATH_ENCODE_VLONG
  #define ENCODE_FULL_VINT          KINO_MATH_ENCODE_FULL_VINT
  #define DECODE_VINT               KINO_MATH_DECODE_VINT
  #define DECODE_VLONG              KINO_MATH_DECODE_VLONG
  #define Math_decode_vint          kino_Math_decode_vint
  #define SKIP_VINT                 KINO_MATH_SKIP_VINT
#endif

#endif /* H_KINO_MATHUTILS */

/* Copyright 2006-2007 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */