// Copyright 2022, FOSS-VG Developers and Contributers
//
// Author(s):
//   BodgeMaster, Shwoomple
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// version 3 along with this program.
// If not, see https://www.gnu.org/licenses/agpl-3.0.en.html

#pragma once

#include <cstdint>
#include <vector>

#include "../../error.hpp"

namespace VarInt {

    // up to 5 bytes, least significant byte first, most significant bit
    // indicates whether the next byte is still part of the number
    ErrorOr<int32_t> fromVar32(std::vector<uint8_t> data, uint64_t initialPosition=0, uint8_t* processedBytes=nullptr) {
        if (initialPosition >= data.size()) {
            return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
        }

        int32_t returnValue = 0;
        uint64_t currentPosition = initialPosition;
        uint8_t bits = 0;
        while (data[currentPosition] & 0b10000000 && currentPosition < initialPosition+4) {
            returnValue = returnValue + (((int32_t) 0b01111111 & data[currentPosition]) << bits);

            (*processedBytes)++;
            bits += 7;
            currentPosition++;
            // check after increasing so we don't need to check outside the loop
            if (currentPosition >= data.size()) {
                return ErrorOr<int32_t>(true, ErrorCodes::OVERRUN);
            }

        }
        if (data[currentPosition] & 0b10000000) {
            return ErrorOr<int32_t>(true, ErrorCodes::OVERFLOW);
        }
        returnValue = returnValue + (((int32_t) 0b01111111 & data[currentPosition]) << bits);
        (*processedBytes)++;

        return ErrorOr<int32_t>(returnValue);
    }

    // up to 10 bytes, least significant byte first, most significant bit
    // indicates whether the next byte is still part of the number
    ErrorOr<int64_t> fromVar64(std::vector<uint8_t> data, uint64_t initialPosition=0, uint8_t* processedBytes=nullptr) {
        if (initialPosition >= data.size()) {
            return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
        }

        int64_t returnValue = 0;
        uint64_t currentPosition = initialPosition;
        uint8_t bits = 0;
        while (data[currentPosition] & 0b10000000 && currentPosition < initialPosition+9) {
            returnValue = returnValue + (((int64_t) 0b01111111 & data[currentPosition]) << bits);

            (*processedBytes)++;
            bits += 7;
            currentPosition++;
            // check after increasing so we don't need to check outside the loop
            if (currentPosition >= data.size()) {
                return ErrorOr<int64_t>(true, ErrorCodes::OVERRUN);
            }
        }
        if (data[currentPosition] & 0b10000000) {
            return ErrorOr<int64_t>(true, ErrorCodes::OVERFLOW);
        }
        returnValue = returnValue + (((int64_t) 0b01111111 & data[currentPosition]) << bits);
        (*processedBytes)++;

        return ErrorOr<int64_t>(returnValue);
    }

    // appends to the vector
    void toVar32(int32_t value, std::vector<uint8_t> &data) {
        uint8_t nextByte;
        uint8_t shift = 0;
        // do something else after the 4th shift
        while (shift < 4) {
            if (shift>0 && value==0) {
                return;
            }

            nextByte = value & 0b01111111;

            value = (value >> 7) & 0x01FFFFFF;
            shift++;


            if (value>0) {
                nextByte = nextByte + 0b10000000;
            }
            data.push_back(nextByte);

        }
        if (value>0) {
            data.push_back(value & 0b00001111);
        }
    }

    // appends to the vector
    void toVar64(int64_t value, std::vector<uint8_t> &data) {
        uint8_t nextByte;
        uint8_t shift = 0;
        // do something else after the 4th shift
        while (shift < 9) {
            if (shift>0 && value==0) {
                return;
            }

            nextByte = value & 0b01111111;

            value = (value >> 7) & 0x01FFFFFFFFFFFFFF;
            shift++;

            if (value>0) {
                nextByte = nextByte + 0b10000000;
            }
            data.push_back(nextByte);

        }
        if (value>0) {
            data.push_back(value & 0b00000001);
        }
    }
}
