/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

#include <cstdint>

#include "bits_util.h"

class Varint {
public:
    // Maximum lengths of varint encoding of uint64
    static constexpr int kMax64 = 10;

    // REQUIRES   "ptr" points to a buffer of length sufficient to hold "v".
    // EFFECTS    Encodes "v" into "ptr" and returns a pointer to the
    //            byte just past the last encoded byte.
    static char* Encode32(char* ptr, uint32_t v);
    static char* Encode64(char* ptr, uint64_t v);

    // EFFECTS    Returns the encoding length of the specified value.
    static int Length64(uint64_t v);

private:
    // A fully inlined version of Encode32: useful in the most time critical
    // routines, but its code size is large
    static char* Encode32Inline(char* ptr, uint32_t v);
};

inline int Varint::Length64(uint64_t v) {
    // This computes value == 0 ? 1 : floor(log2(v)) / 7 + 1 using an explicit
    // multiplication to implement the division of a number in the 1..63 range.
    // Explicit OR 0x1 to handle v == 0.
    uint32_t log2value = BitsUtil::Log2FloorNonZero64(v | 0x1);
    return static_cast<int>((log2value * 9 + 73) / 64);
}

inline char* Varint::Encode32Inline(char* sptr, uint32_t v) {
    // Operate on characters as unsigneds
    uint8_t* ptr = reinterpret_cast<uint8_t*>(sptr);
    static const uint32_t B = 128;
    if (v < (1 << 7)) {
        *(ptr++) = static_cast<uint8_t>(v);
    } else if (v < (1 << 14)) {
        *(ptr++) = static_cast<uint8_t>(v | B);
        *(ptr++) = static_cast<uint8_t>(v >> 7);
    } else if (v < (1 << 21)) {
        *(ptr++) = static_cast<uint8_t>(v | B);
        *(ptr++) = static_cast<uint8_t>((v >> 7) | B);
        *(ptr++) = static_cast<uint8_t>(v >> 14);
    } else if (v < (1 << 28)) {
        *(ptr++) = static_cast<uint8_t>(v | B);
        *(ptr++) = static_cast<uint8_t>((v >> 7) | B);
        *(ptr++) = static_cast<uint8_t>((v >> 14) | B);
        *(ptr++) = static_cast<uint8_t>(v >> 21);
    } else {
        *(ptr++) = static_cast<uint8_t>(v | B);
        *(ptr++) = static_cast<uint8_t>((v >> 7) | B);
        *(ptr++) = static_cast<uint8_t>((v >> 14) | B);
        *(ptr++) = static_cast<uint8_t>((v >> 21) | B);
        *(ptr++) = static_cast<uint8_t>(v >> 28);
    }
    return reinterpret_cast<char*>(ptr);
}