/**
 * (c) 2014-2016 Alexandro Sanchez Bach. All rights reserved.
 * Released under GPL v2 license. Read LICENSE for more details.
 */

#pragma once

#include "nucleus/architecture.h"
#include "nucleus/compiler.h"
#include "nucleus/types.h"

// Endianness detection
#if defined(NUCLEUS_ARCH_X86)
#define NUCLEUS_ENDIAN_LITTLE
#else
#define NUCLEUS_ENDIAN_BIG
#endif

// Swap endianness macros
#ifdef NUCLEUS_COMPILER_MSVC
#define SE16(val)  _byteswap_ushort(val)
#define SE32(val)  _byteswap_ulong(val)
#define SE64(val)  _byteswap_uint64(val)
#define SE128(val) U128{SE64((val).hi), SE64((val).lo)}
#else
#define SE16(val)  __builtin_bswap16(val)
#define SE32(val)  __builtin_bswap32(val)
#define SE64(val)  __builtin_bswap64(val)
#define SE128(val) U128{SE64((val).hi), SE64((val).lo)}
#endif

/**
 * Native endianness
 */
template <typename T>
using NativeEndian = T;

/**
 * Swapped endianness
 */
template <typename T, int size = sizeof(T)> struct se_t;
template <typename T> struct se_t<T, 1> { static inline void func(T& dst, const T src) { (U08&)dst = (U08&)src; } };
template <typename T> struct se_t<T, 2> { static inline void func(T& dst, const T src) { (U16&)dst = SE16((U16&)src); } };
template <typename T> struct se_t<T, 4> { static inline void func(T& dst, const T src) { (U32&)dst = SE32((U32&)src); } };
template <typename T> struct se_t<T, 8> { static inline void func(T& dst, const T src) { (U64&)dst = SE64((U64&)src); } };

template <typename T>
class SwappedEndian {
    static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8,
        "SwappedEndian only accepts types of 1, 2, 4 or 8 bytes in size");

private:
    T data;

public:
    const T& ToBE() const {
        return data;
    }

    T ToLE() const {
        T res;
        se_t<T>::func(res, data);
        return res;
    }

    void FromBE(const T& value) {
        data = value;
    }

    void FromLE(const T& value) {
        se_t<T>::func(data, value);
    }

    operator const T() const {
        return ToLE();
    }

    template <typename T1>
    operator const SwappedEndian<T1>() const {
        SwappedEndian<T1> res;
        if (sizeof(T1) < sizeof(T)) {
            res.FromBE(ToBE() >> ((sizeof(T)-sizeof(T1)) * 8));
        }
        else if (sizeof(T1) > sizeof(T)) {
            res.FromLE(ToLE());
        }
        else {
            res.FromBE(ToBE());
        }
        return res;
    }

    SwappedEndian<T>& operator = (const T& right) { FromLE(right); return *this; }
    SwappedEndian<T>& operator = (const SwappedEndian<T>& right) = default;

    template <typename T1> SwappedEndian<T>& operator += (T1 right) { return *this = T(*this) + right; }
    template <typename T1> SwappedEndian<T>& operator -= (T1 right) { return *this = T(*this) - right; }
    template <typename T1> SwappedEndian<T>& operator *= (T1 right) { return *this = T(*this) * right; }
    template <typename T1> SwappedEndian<T>& operator /= (T1 right) { return *this = T(*this) / right; }
    template <typename T1> SwappedEndian<T>& operator %= (T1 right) { return *this = T(*this) % right; }
    template <typename T1> SwappedEndian<T>& operator &= (T1 right) { return *this = T(*this) & right; }
    template <typename T1> SwappedEndian<T>& operator |= (T1 right) { return *this = T(*this) | right; }
    template <typename T1> SwappedEndian<T>& operator ^= (T1 right) { return *this = T(*this) ^ right; }
    template <typename T1> SwappedEndian<T>& operator <<=(T1 right) { return *this = T(*this) << right; }
    template <typename T1> SwappedEndian<T>& operator >>=(T1 right) { return *this = T(*this) >> right; }

    template <typename T1> SwappedEndian<T>& operator += (const SwappedEndian<T1>& right) { return *this = ToLE() + right.ToLE(); }
    template <typename T1> SwappedEndian<T>& operator -= (const SwappedEndian<T1>& right) { return *this = ToLE() - right.ToLE(); }
    template <typename T1> SwappedEndian<T>& operator *= (const SwappedEndian<T1>& right) { return *this = ToLE() * right.ToLE(); }
    template <typename T1> SwappedEndian<T>& operator /= (const SwappedEndian<T1>& right) { return *this = ToLE() / right.ToLE(); }
    template <typename T1> SwappedEndian<T>& operator %= (const SwappedEndian<T1>& right) { return *this = ToLE() % right.ToLE(); }
    template <typename T1> SwappedEndian<T>& operator &= (const SwappedEndian<T1>& right) { return *this = ToBE() & right.ToBE(); }
    template <typename T1> SwappedEndian<T>& operator |= (const SwappedEndian<T1>& right) { return *this = ToBE() | right.ToBE(); }
    template <typename T1> SwappedEndian<T>& operator ^= (const SwappedEndian<T1>& right) { return *this = ToBE() ^ right.ToBE(); }

    template <typename T1> SwappedEndian<T> operator & (const SwappedEndian<T1>& right) const { return SwappedEndian<T>{ ToBE() & right.ToBE() }; }
    template <typename T1> SwappedEndian<T> operator | (const SwappedEndian<T1>& right) const { return SwappedEndian<T>{ ToBE() | right.ToBE() }; }
    template <typename T1> SwappedEndian<T> operator ^ (const SwappedEndian<T1>& right) const { return SwappedEndian<T>{ ToBE() ^ right.ToBE() }; }

    template <typename T1> bool operator == (T1 right) const { return (T1)ToLE() == right; }
    template <typename T1> bool operator != (T1 right) const { return !(*this == right); }
    template <typename T1> bool operator >  (T1 right) const { return (T1)ToLE() >  right; }
    template <typename T1> bool operator <  (T1 right) const { return (T1)ToLE() <  right; }
    template <typename T1> bool operator >= (T1 right) const { return (T1)ToLE() >= right; }
    template <typename T1> bool operator <= (T1 right) const { return (T1)ToLE() <= right; }

    template <typename T1> bool operator == (const SwappedEndian<T1>& right) const { return ToBE() == right.ToBE(); }
    template <typename T1> bool operator != (const SwappedEndian<T1>& right) const { return !(*this == right); }
    template <typename T1> bool operator >  (const SwappedEndian<T1>& right) const { return (T1)ToLE() >  right.ToLE(); }
    template <typename T1> bool operator <  (const SwappedEndian<T1>& right) const { return (T1)ToLE() <  right.ToLE(); }
    template <typename T1> bool operator >= (const SwappedEndian<T1>& right) const { return (T1)ToLE() >= right.ToLE(); }
    template <typename T1> bool operator <= (const SwappedEndian<T1>& right) const { return (T1)ToLE() <= right.ToLE(); }

    SwappedEndian<T> operator++ (int) { SwappedEndian<T> res = *this; *this += 1; return res; }
    SwappedEndian<T> operator-- (int) { SwappedEndian<T> res = *this; *this -= 1; return res; }
    SwappedEndian<T>& operator++ () { *this += 1; return *this; }
    SwappedEndian<T>& operator-- () { *this -= 1; return *this; }
};
