//===- LEB128.cpp ---------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/Support/LEB128.h>

namespace mcld {

namespace leb128 {

//===---------------------- LEB128 Encoding APIs -------------------------===//
template<>
size_t encode<uint64_t>(ByteType *&pBuf, uint64_t pValue) {
  size_t size = 0;
  do {
    ByteType byte = pValue & 0x7f;
    pValue >>= 7;
    if (pValue)
      byte |= 0x80;
    *pBuf++ = byte;
    size++;
  } while (pValue);

  return size;
}

/*
 * Fast version for encoding 32-bit integer. This unrolls the loop in the
 * generic version defined above.
 */
template<>
size_t encode<uint32_t>(ByteType *&pBuf, uint32_t pValue) {
  if ((pValue & ~0x7f) == 0) {
    *pBuf++ = static_cast<ByteType>(pValue);
    return 1;
  } else if ((pValue & ~0x3fff) == 0){
    *pBuf++ = static_cast<ByteType>((pValue         & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>((pValue  >>  7) & 0x7f);
    return 2;
  } else if ((pValue & ~0x1fffff) == 0) {
    *pBuf++ = static_cast<ByteType>((pValue         & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >>  7) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>((pValue  >> 14) & 0x7f);
    return 3;
  } else if ((pValue & ~0xfffffff) == 0) {
    *pBuf++ = static_cast<ByteType>((pValue         & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >>  7) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >> 14) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>((pValue  >> 21) & 0x7f);
    return 4;
  } else {
    *pBuf++ = static_cast<ByteType>((pValue         & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >>  7) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >> 14) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>(((pValue >> 21) & 0x7f) | 0x80);
    *pBuf++ = static_cast<ByteType>((pValue  >> 28) & 0x7f);
    return 5;
  }
  // unreachable
}

template<>
size_t encode<int64_t>(ByteType *&pBuf, int64_t pValue) {
  size_t size = 0;
  bool more = true;

  do {
    ByteType byte = pValue & 0x7f;
    pValue >>= 7;

    if (((pValue ==  0) && ((byte & 0x40) == 0)) ||
        ((pValue == -1) && ((byte & 0x40) == 0x40)))
      more = false;
    else
      byte |= 0x80;

    *pBuf++ = byte;
    size++;
  } while (more);

  return size;
}

template<>
size_t encode<int32_t>(ByteType *&pBuf, int32_t pValue) {
  return encode<int64_t>(pBuf, static_cast<int64_t>(pValue));
}

//===---------------------- LEB128 Decoding APIs -------------------------===//

template<>
uint64_t decode<uint64_t>(const ByteType *pBuf, size_t &pSize) {
  uint64_t result = 0;

  if ((*pBuf & 0x80) == 0) {
    pSize = 1;
    return *pBuf;
  } else if ((*(pBuf + 1) & 0x80) == 0) {
    pSize = 2;
    return ((*(pBuf + 1) & 0x7f) << 7) |
           (*pBuf & 0x7f);
  } else if ((*(pBuf + 2) & 0x80) == 0) {
    pSize = 3;
    return ((*(pBuf + 2) & 0x7f) << 14) |
           ((*(pBuf + 1) & 0x7f) <<  7) |
           (*pBuf & 0x7f);
  } else {
    pSize = 4;
    result = ((*(pBuf + 3) & 0x7f) << 21) |
             ((*(pBuf + 2) & 0x7f) << 14) |
             ((*(pBuf + 1) & 0x7f) <<  7) |
             (*pBuf & 0x7f);
  }

  if ((*(pBuf + 3) & 0x80) != 0) {
    // Large number which is an unusual case.
    unsigned shift;
    ByteType byte;

    // Start the read from the 4th byte.
    shift = 28;
    pBuf += 4;
    do {
      byte = *pBuf;
      pBuf++;
      pSize++;
      result |= (static_cast<uint64_t>(byte & 0x7f) << shift);
      shift += 7;
    } while (byte & 0x80);
  }

  return result;
}

template<>
uint64_t decode<uint64_t>(const ByteType *&pBuf) {
  ByteType byte;
  uint64_t result;

  byte = *pBuf++;
  result = byte & 0x7f;
  if ((byte & 0x80) == 0) {
    return result;
  } else {
    byte = *pBuf++;
    result |=  ((byte & 0x7f) << 7);
    if ((byte & 0x80) == 0) {
      return result;
    } else {
      byte = *pBuf++;
      result |= (byte & 0x7f) << 14;
      if ((byte & 0x80) == 0) {
        return result;
      } else {
        byte = *pBuf++;
        result |= (byte & 0x7f) << 21;
        if ((byte & 0x80) == 0) {
          return result;
        }
      }
    }
  }

  // Large number which is an unusual case.
  unsigned shift;

  // Start the read from the 4th byte.
  shift = 28;
  do {
    byte = *pBuf++;
    result |= (static_cast<uint64_t>(byte & 0x7f) << shift);
    shift += 7;
  } while (byte & 0x80);

  return result;
}

/*
 * Signed LEB128 decoding is Similar to the unsigned version but setup the sign
 * bit if necessary. This is rarely used, therefore we don't provide unrolling
 * version like decode() to save the code size.
 */
template<>
int64_t decode<int64_t>(const ByteType *pBuf, size_t &pSize) {
  uint64_t result = 0;
  ByteType byte;
  unsigned shift = 0;

  pSize = 0;
  do {
    byte = *pBuf;
    pBuf++;
    pSize++;
    result |= (static_cast<uint64_t>(byte & 0x7f) << shift);
    shift += 7;
  } while (byte & 0x80);

  if ((shift < (8 * sizeof(result))) && (byte & 0x40))
    result |= ((static_cast<uint64_t>(-1)) << shift);

  return result;
}

template<>
int64_t decode<int64_t>(const ByteType *&pBuf) {
  uint64_t result = 0;
  ByteType byte;
  unsigned shift = 0;

  do {
    byte = *pBuf;
    pBuf++;
    result |= (static_cast<uint64_t>(byte & 0x7f) << shift);
    shift += 7;
  } while (byte & 0x80);

  if ((shift < (8 * sizeof(result))) && (byte & 0x40))
    result |= ((static_cast<uint64_t>(-1)) << shift);

  return result;
}

} // namespace of leb128
} // namespace of mcld