//
//  ByteCountCoding.h
//  libraries/shared/src
//
//  Created by Brad Hefta-Gaub on 6/3/14.
//  Copyright 2014 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//
// TODO:
//   * handle signed types better, check for - numeric_limits<type>::is_signed
//   * test extra long buffer with garbage data at end...

#ifndef hifi_ByteCountCoding_h
#define hifi_ByteCountCoding_h

#include <algorithm>
#include <cassert>
#include <climits>
#include <limits>

#include <QDebug>

#include <QBitArray>
#include <QByteArray>

#include "SharedUtil.h"

#include "NumericalConstants.h"

template<typename T> class ByteCountCoded {
public:
    T data;
    ByteCountCoded(T input = 0) : data(input) { 
        // only use this template for non-signed integer types
        assert(!std::numeric_limits<T>::is_signed && std::numeric_limits<T>::is_integer);
    };

    ByteCountCoded(const QByteArray& fromEncoded) : data(0) { decode(fromEncoded); }

    QByteArray encode() const;
    size_t decode(const QByteArray& fromEncoded);
    size_t decode(const char* encodedBuffer, int encodedSize);

    bool operator==(const ByteCountCoded& other) const { return data == other.data; }
    bool operator!=(const ByteCountCoded& other) const { return data != other.data; }
    bool operator!() const { return data == 0; }

     operator QByteArray() const { return encode(); };
     operator T() const { return data; };
};

template<typename T> inline QByteArray& operator<<(QByteArray& out, const ByteCountCoded<T>& value) {
    return out = value;
}

template<typename T> inline QByteArray& operator>>(QByteArray& in, ByteCountCoded<T>& value) {
    value.decode(in);
    return in;
}

template<typename T> inline QByteArray ByteCountCoded<T>::encode() const {
    QByteArray output;

    int totalBits = sizeof(data) * BITS_IN_BYTE;
    int valueBits = totalBits;
    bool firstValueFound = false;
    T temp = data;
    T lastBitMask = (T)(1) << (totalBits - 1);

    // determine the number of bits that the value takes
    for (int bitAt = 0; bitAt < totalBits; bitAt++) {
        T bitValue = (temp & lastBitMask) == lastBitMask;
        if (!firstValueFound) {
            if (bitValue == 0) {
                valueBits--;
            } else {
                firstValueFound = true;
            }
        }
        temp = temp << 1;
    }
    
    // calculate the number of total bytes, including our header
    // BITS_IN_BYTE-1 because we need to code the number of bytes in the header
    // + 1 because we always take at least 1 byte, even if number of bits is less than a bytes worth
    int numberOfBytes = (valueBits / (BITS_IN_BYTE - 1)) + 1; 

    output.fill(0, numberOfBytes);

    // next pack the number of header bits in, the first N-1 to be set to 1, the last to be set to 0
    for(int i = 0; i < numberOfBytes; i++) {
        int outputIndex = i;
        T bitValue = (i < (numberOfBytes - 1)  ? 1 : 0);
        char original = output.at(outputIndex / BITS_IN_BYTE);
        int shiftBy = BITS_IN_BYTE - ((outputIndex % BITS_IN_BYTE) + 1);
        char thisBit = ( bitValue << shiftBy);
        output[i / BITS_IN_BYTE] = (original | thisBit);
    }

    // finally pack the the actual bits from the bit array
    temp = data;
    for(int i = numberOfBytes; i < (numberOfBytes + valueBits); i++) {
        int outputIndex = i;
        T bitValue = (temp & 1);
        char original = output.at(outputIndex / BITS_IN_BYTE);
        int shiftBy = BITS_IN_BYTE - ((outputIndex % BITS_IN_BYTE) + 1);
        char thisBit = ( bitValue << shiftBy);
        output[i / BITS_IN_BYTE] = (original | thisBit);

        temp = temp >> 1;
    }
    return output;
}

template<typename T> inline size_t ByteCountCoded<T>::decode(const QByteArray& fromEncodedBytes) {
    return decode(fromEncodedBytes.constData(), fromEncodedBytes.size());
}

template<typename T> inline size_t ByteCountCoded<T>::decode(const char* encodedBuffer, int encodedSize) {
    data = 0; // reset data
    size_t bytesConsumed = 0;
    int bitCount = BITS_IN_BYTE * encodedSize;

    int encodedByteCount = 1; // there is at least 1 byte (after the leadBits)
    int leadBits = 1; // there is always at least 1 lead bit
    bool inLeadBits = true;
    int bitAt = 0;
    int expectedBitCount; // unknown at this point
    int lastValueBit = 0;
    T bitValue = 1;
    
    for(int byte = 0; byte < encodedSize; byte++) {
        char originalByte = encodedBuffer[byte];
        bytesConsumed++;
        unsigned char maskBit = 128; // LEFT MOST BIT set
        for(int bit = 0; bit < BITS_IN_BYTE; bit++) {
            bool bitIsSet = originalByte & maskBit;
            
            // Processing of the lead bits
            if (inLeadBits) {
                if (bitIsSet) {
                    encodedByteCount++;
                    leadBits++;
                } else {
                    inLeadBits = false; // once we hit our first 0, we know we're out of the lead bits
                    expectedBitCount = (encodedByteCount * BITS_IN_BYTE) - leadBits;
                    lastValueBit = expectedBitCount + bitAt;

                    // check to see if the remainder of our buffer is sufficient
                    if (expectedBitCount > (bitCount - leadBits)) {
                        break;
                    }
                }
            } else {
                if (bitAt > lastValueBit) {
                    break;
                }
                
                if(bitIsSet) {
                    data += bitValue;
                }
                bitValue *= 2;
            }
            bitAt++;
            maskBit = maskBit >> 1;
        }
        if (!inLeadBits && bitAt > lastValueBit) {
            break;
        }
    }
    return bytesConsumed;
}
#endif // hifi_ByteCountCoding_h

