/* Copyright 2012 Fredrik Wikstrom. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
**
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef ENDIAN_H
#define ENDIAN_H

#include <stdint.h>

#ifndef WORDS_BIGENDIAN
 #if defined(__LINUX__)
  #if __BYTE_ORDER == __BIG_ENDIAN
   #define WORDS_BIGENDIAN 1
  #elif __BYTE_ORDER == __LITTLE_ENDIAN
   #define WORDS_BIGENDIAN 0
  #else
   #error "Unsupported endian format."
  #endif
 #elif defined(__AROS__)
  #if _BYTE_ORDER == _BIG_ENDIAN
   #define WORDS_BIGENDIAN 1
  #elif _BYTE_ORDER == _LITTLE_ENDIAN
   #define WORDS_BIGENDIAN 0
  #else
   #error "Unsupported endian format."
  #endif
 #elif defined(_WIN32)
  #define WORDS_BIGENDIAN 0
 #else
  #define WORDS_BIGENDIAN 1
 #endif
#endif

#define swap16(x) generic_swap16(x)
#define swap32(x) generic_swap32(x)
#define swap64(x) generic_swap64(x)
#define rbe16(p) generic_rbe16(p)
#define rbe32(p) generic_rbe32(p)
#define rbe64(p) generic_rbe64(p)
#define rle16(p) generic_rle16(p)
#define rle32(p) generic_rle32(p)
#define rle64(p) generic_rle64(p)
#define wbe16(p,x) generic_wbe16(p,x)
#define wbe32(p,x) generic_wbe32(p,x)
#define wbe64(p,x) generic_wbe64(p,x)
#define wle16(p,x) generic_wle16(p,x)
#define wle32(p,x) generic_wle32(p,x)
#define wle64(p,x) generic_wle64(p,x)

#if !defined(__LINUX__)
 #if WORDS_BIGENDIAN
  #define h2be16(x)  ((uint16_t)x)
  #define h2be32(x)  ((uint32_t)x)
  #define h2be64(x)  ((uint64_t)x)
  #define h2le16(x)  swap16(x)
  #define h2le32(x)  swap32(x)
  #define h2le64(x)  swap64(x)
 #else
  #define h2le16(x)  ((uint16_t)x)
  #define h2le32(x)  ((uint32_t)x)
  #define h2le64(x)  ((uint64_t)x)
  #define h2be16(x)  swap16(x)
  #define h2be32(x)  swap32(x)
  #define h2be64(x)  swap64(x)
 #endif
#endif

#if defined(__VBCC__) && defined(__PPC__)

#undef swap16
#undef swap32
#undef swap64

uint16_t swap16(__reg("r4") uint16_t) =
	"\trlwinm\t3,4,8,16,24\n"
	"\trlwimi\t3,4,24,24,31";

uint32_t swap32(__reg("r4") uint32_t) =
	"\trlwinm\t3,4,24,0,31\n"
	"\trlwimi\t3,4,8,8,15\n"
	"\trlwimi\t3,4,8,24,31";

uint64_t swap64(__reg("r5/r6") uint64_t) =
	"\trlwinm\t4,5,24,0,31\n"
	"\trlwinm\t3,6,24,0,31\n"
	"\trlwimi\t4,5,8,8,15\n"
	"\trlwimi\t3,6,8,8,15\n"
	"\trlwimi\t4,5,8,24,31\n"
	"\trlwimi\t3,6,8,24,31";

#undef rle16
#undef rle32
#undef rle64

uint16_t rle16(__reg("r3") void *) =
	"\tlhbrx\t3,0,3";

uint32_t rle32(__reg("r3") void *) =
	"\tlwbrx\t3,0,3";

uint64_t rle64(__reg("r3") void *) =
	"\taddi\t5,3,4\n" // r5 = r3 + 4
	"\tlwbrx\t4,0,3\n"
	"\tlwbrx\t3,0,5";

#undef wle16
#undef wle32
#undef wle64

void wle16(__reg("r3") void *, __reg("r4") uint16_t) =
	"\tsthbrx\t4,0,3";

void wle32(__reg("r3") void *, __reg("r4") uint32_t) =
	"\tstwbrx\t4,0,3";

void wle64(__reg("r3") void *, __reg("r5/r6") uint64_t) =
	"\taddi\t4,3,4\n" // r4 = r3 + 4
	"\tstwbrx\t6,0,3\n"
	"\tstwbrx\t5,0,4";

#endif /* defined(__VBCC__) && defined(__PPC__) */

#if defined(__GNUC__) && defined(__PPC__)

#undef swap16
#undef swap32
#undef swap64

static inline uint32_t swap16(uint16_t x) {
	uint32_t res;
	asm("rlwinm %0,%1,8,16,23\n"
		"\trlwimi %0,%1,24,24,31"
		: "=&r" (res)
		: "r" (x));
	return res;
}

static inline uint32_t swap32(uint32_t x) {
	uint32_t res;
	asm("rlwinm %0,%1,24,0,31\n"
		"\trlwimi %0,%1,8,8,15\n"
		"\trlwimi %0,%1,8,24,31"
		: "=&r" (res)
		: "r" (x));
	return res;
}

static inline uint64_t swap64(uint64_t x) {
	uint64_t res;
	asm("rlwinm %L0,%H1,24,0,31\n"
		"\trlwinm %H0,%L1,24,0,31\n"
		"\trlwimi %L0,%H1,8,8,15\n"
		"\trlwimi %H0,%L1,8,8,15\n"
		"\trlwimi %L0,%H1,8,24,31\n"
		"\trlwimi %H0,%L1,8,24,31"
		: "=&r" (res)
		: "r" (x));
	return res;
}

#undef rle16
#undef rle32
#undef rle64

static inline uint32_t rle16(const void *p) {
	uint32_t res;
	const uint16_t *p0 = p;
	asm("lhbrx %0,%y1"
		: "=r" (res)
		: "Z" (*p0));
	return res;
}

static inline uint32_t rle32(const void *p) {
	uint32_t res;
	const uint32_t *p0 = p;
	asm("lwbrx %0,%y1"
		: "=r" (res)
		: "Z" (*p0));
	return res;
}

static inline uint64_t rle64(const void *p) {
	uint64_t res;
	const uint32_t *p0 = p;
	const uint32_t *p1 = p0+1;
	asm("lwbrx %L0,%y1\n"
		"\tlwbrx %H0,%y2"
		: "=&r" (res)
		: "Z" (*p0), "Z" (*p1));
	return res;
}

#undef wle16
#undef wle32
#undef wle64

static inline void wle16(void *p, uint16_t x) {
	uint16_t *p0 = p;
	asm("sthbrx %1,%y0"
		:
		: "Z" (*p0), "r" (x));
}

static inline void wle32(void *p, uint32_t x) {
	uint32_t *p0 = p;
	asm("stwbrx %1,%y0"
		:
		: "Z" (*p0), "r" (x));
}

static inline void wle64(void *p, uint64_t x) {
	uint32_t *p0 = p;
	uint32_t *p1 = p0+1;
	asm("stwbrx %L2,%y0\n"
		"\tstwbrx %H2,%y1"
		:
		: "Z" (*p0), "Z" (*p1), "r" (x));
}

#endif /* defined(__GNUC__) && defined(__PPC__) */

#if defined(__GNUC__) && (defined(__i386__) || defined(__i686__))

#ifndef USE_BSWAP
#define USE_BSWAP 1
#endif

#undef swap16
#undef swap32

static inline uint16_t swap16(uint16_t x) {
	uint16_t res;
	asm("rorw $8,%w0"
		: "=r" (res)
		: "0" (x)
		: "cc");
	return res;
}

static inline uint32_t swap32(uint32_t x) {
	uint32_t res;
#if USE_BSWAP
	asm("bswap %0"
		: "=r" (res)
		: "0" (x));
#else
	asm("rorw $8,%w0\n"
		"\trorl $16,%0\n"
		"\trorw $8,%w0"
		: "=r" (res)
		: "0" (x)
		: "cc");
#endif
	return res;
}

#endif /* defined(__GNUC__) && (defined(__i386__) || defined(__i686__)) */

#if defined(__VBCC__) && defined(__M68K__)

#undef swap16
#undef swap32
#undef swap64

static inline uint16_t swap16(__reg("d0") uint16_t) =
	"\trol.w\t#8,d0";

static inline uint32_t swap32(__reg("d0") uint32_t) =
	"\trol.w\t#8,d0\n"
	"\tswap\td0\n"
	"\trol.w\t#8,d0";

static inline uint64_t swap64(__reg("d0/d1") uint64_t) =
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1\n"
	"\tswap\td0\n"
	"\tswap\td1\n"
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1\n"
	"\teor.l\td0,d1\n"
	"\teor.l\td1,d0\n"
	"\teor.l\td0,d1";

#undef rle64

static inline uint64_t rle64(__reg("a0") void *) =
	"\tmove.l\t4(a0),d0\n"
	"\tmove.l\t(a0),d1\n"
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1\n"
	"\tswap\td0\n"
	"\tswap\td1\n"
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1";

#undef wle64

static inline uint64_t wle64(__reg("a0") void *, __reg("d0/d1") uint64_t) =
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1\n"
	"\tswap\td0\n"
	"\tswap\td1\n"
	"\trol.w\t#8,d0\n"
	"\trol.w\t#8,d1\n"
	"\tmove.l\td0,4(a0)\n"
	"\tmove.l\td1,(a0)";

#endif /* defined(__VBCC__) && defined(__M68K__) */

static inline uint16_t generic_swap16 (uint16_t x) {
	return (x << 8)|(x >> 8);
}

static inline uint32_t generic_swap32 (uint32_t x) {
	return (x >> 24)|((x & 0x00ff0000) >> 8)|((x & 0x0000ff00) << 8)|(x << 24);
}

static inline uint64_t generic_swap64 (uint64_t x) {
	return (uint64_t)swap32(x >> 32)|((uint64_t)swap32(x) << 32);
}

static inline uint16_t generic_rbe16 (const void *p) {
	return h2be16(*(const uint16_t *)p);
}

static inline uint32_t generic_rbe32 (const void *p) {
	return h2be32(*(const uint32_t *)p);
}

static inline uint64_t generic_rbe64 (const void *p) {
	return h2be64(*(const uint64_t *)p);
}

static inline uint16_t generic_rle16 (const void *p) {
	return h2le16(*(const uint16_t *)p);
}

static inline uint32_t generic_rle32 (const void *p) {
	return h2le32(*(const uint32_t *)p);
}

static inline uint64_t generic_rle64 (const void *p) {
	return h2le64(*(const uint64_t *)p);
}

static inline void generic_wbe16 (void *p, uint16_t x) {
	*(uint16_t *)p = h2be16(x);
}

static inline void generic_wbe32 (void *p, uint32_t x) {
	*(uint32_t *)p = h2be32(x);
}

static inline void generic_wbe64 (void *p, uint64_t x) {
	*(uint64_t *)p = h2be64(x);
}

static inline void generic_wle16 (void *p, uint16_t x) {
	*(uint16_t *)p = h2le16(x);
}

static inline void generic_wle32 (void *p, uint32_t x) {
	*(uint32_t *)p = h2le32(x);
}

static inline void generic_wle64 (void *p, uint64_t x) {
	*(uint64_t *)p = h2le64(x);
}

#endif /* ENDIAN_H */
