// 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

#include <iostream>
#include <cstdint>
#include <vector>

#include "assert.hpp"
#include "../lib/error.hpp"
#include "../lib/net/conversion/varint.hpp"

int main() {

    std::cout << "################################################################################" << std::endl;
    std::cout << "VarInt tests" << std::endl;
    std::cout << "################################################################################" << std::endl;
    // examples for numbers
    //
    // 32 Bit
    // 0000 0000 0000 0000 0000 0000 0000 0000 = 0 -> 0000 0000
    // 1111 1111 1111 1111 1111 1111 1111 1111 = -1 (unsigned int32 max) -> 1111 1111 1111 1111 1111 1111 1111 1111 0000 1111
    // 0000 0000 0010 0100 0011 1101 1011 1101 = 2375101 -> 1011 1101 1111 1011 1001 0000 0000 0001
    // 0000 0000 0000 0000 0001 0001 0001 0001 = 4369 -> 1001 0001 0010 0010
    //
    // 64 Bit
    // 0010 0000 0001 0000 0000 0000 1010 0010 1010 1000 0010 0000 1101 0000 1001 0011 = 2310347307446489235 -> 1101 0011 1010 0001 1000 0011 1100 0001 1010 1010 1001 0100 1000 0000 1000 1000 0010 0000
    // 1000 0000 0100 0000 0010 0000 0001 0000 0000 1000 0000 0100 0000 0010 0000 0001 = -9205322385119247871 -> 1000 0001 1000 0100 1001 0000 1100 0000 1000 0000 1000 0010 1000 1000 1010 0000 1000 0000 0000 0001
    // 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 = -1 (unsigned int64 max) -> 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 0000 0001

    {
        std::vector<uint8_t> data = std::vector<uint8_t>();
        uint8_t processedBytes;
        ErrorOr<int32_t> result = VarInt::fromVar32(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OUT_OF_RANGE);
    }

    {
        std::vector<uint8_t> data = { 0x84 };
        uint8_t processedBytes;
        ErrorOr<int32_t> result = VarInt::fromVar32(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OVERRUN);
    }

    {
        std::vector<uint8_t> data = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 };
        uint8_t processedBytes;
        ErrorOr<int32_t> result = VarInt::fromVar32(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OVERFLOW);
    }

    uint8_t zeroProcessedBytes = 0;
    std::vector<uint8_t> zeroData;
    zeroData.push_back(0);
    ErrorOr<int32_t> zero = VarInt::fromVar32(zeroData, 0, &zeroProcessedBytes);
    ASSERT(!zero.isError);
    ASSERT(zero.value == 0);
    ASSERT(zeroProcessedBytes == 1);

    uint8_t minusOneProcessedBytes = 0;
    std::vector<uint8_t> minusOneData;
    minusOneData.push_back(255);
    minusOneData.push_back(255);
    minusOneData.push_back(255);
    minusOneData.push_back(255);
    minusOneData.push_back(15);
    ErrorOr<int32_t> minusOne = VarInt::fromVar32(minusOneData, 0, &minusOneProcessedBytes);
    ASSERT(!minusOne.isError);
    ASSERT(minusOne.value == -1);
    ASSERT(minusOneProcessedBytes == 5);

    uint8_t smallProcessedBytes = 0;
    std::vector<uint8_t> smallData;
    // offset data by 3 to test initialPosition feature
    smallData.push_back(0b10010001);
    smallData.push_back(0b10010001);
    smallData.push_back(0b10010001);
    smallData.push_back(0b10010001);
    smallData.push_back(0b00100010);
    ErrorOr<int32_t> small = VarInt::fromVar32(smallData, 3, &smallProcessedBytes);
    ASSERT(!small.isError);
    ASSERT(small.value == 4369);
    ASSERT(smallProcessedBytes == 2);

    uint8_t bigProcessedBytes = 0;
    std::vector<uint8_t> bigData;
    bigData.push_back(0b10111101);
    bigData.push_back(0b11111011);
    bigData.push_back(0b10010000);
    bigData.push_back(0b00000001);
    ErrorOr<int32_t> big = VarInt::fromVar32(bigData, 0, &bigProcessedBytes);
    ASSERT(!big.isError);
    ASSERT(big.value == 2375101);
    ASSERT(bigProcessedBytes == 4);

    //TODO: test error conditions

    std::cout << "Passed fromVar32 test." << std::endl;

    {
        std::vector<uint8_t> data = std::vector<uint8_t>();
        uint8_t processedBytes;
        ErrorOr<int64_t> result = VarInt::fromVar64(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OUT_OF_RANGE);
    }

    {
        std::vector<uint8_t> data = { 0x84 };
        uint8_t processedBytes;
        ErrorOr<int64_t> result = VarInt::fromVar64(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OVERRUN);
    }

    {
        std::vector<uint8_t> data = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 };
        uint8_t processedBytes;
        ErrorOr<int64_t> result = VarInt::fromVar64(data, 0, &processedBytes);
        ASSERT(result.isError);
        ASSERT(result.errorCode == ErrorCodes::OVERFLOW);
    }

    uint8_t zero64ProcessedBytes = 0;
    std::vector<uint8_t> zero64Data;
    zero64Data.push_back(0);
    ErrorOr<int64_t> zero64 = VarInt::fromVar64(zero64Data, 0, &zero64ProcessedBytes);
    ASSERT(!zero64.isError);
    ASSERT(zero64.value == 0);
    ASSERT(zero64ProcessedBytes == 1);

    uint8_t minusOne64ProcessedBytes = 0;
    std::vector<uint8_t> minusOne64Data;
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(255);
    minusOne64Data.push_back(1);
    ErrorOr<int64_t> minusOne64 = VarInt::fromVar64(minusOne64Data, 0, &minusOne64ProcessedBytes);
    ASSERT(!minusOne64.isError);
    ASSERT(minusOne64.value == -1);
    ASSERT(minusOne64ProcessedBytes == 10);

    // 0010 0000 0001 0000 0000 0000 1010 0010 1010 1000 0010 0000 1101 0000 1001 0011 = 2310347307446489235 -> 1101 0011 1010 0001 1000 0011 1100 0001 1010 1010 1001 0100 1000 0000 1000 1000 0010 0000
    uint8_t small64ProcessedBytes = 0;
    std::vector<uint8_t> small64Data;
    // offset data by 3 to test initialPosition feature
    small64Data.push_back(0b11010011);
    small64Data.push_back(0b11010011);
    small64Data.push_back(0b11010011);
    small64Data.push_back(0b10010011);
    small64Data.push_back(0b10100001);
    small64Data.push_back(0b10000011);
    small64Data.push_back(0b11000001);
    small64Data.push_back(0b10101010);
    small64Data.push_back(0b10010100);
    small64Data.push_back(0b10000000);
    small64Data.push_back(0b10001000);
    small64Data.push_back(0b00100000);
    ErrorOr<int64_t> small64 = VarInt::fromVar64(small64Data, 3, &small64ProcessedBytes);
    ASSERT(!small64.isError);
    ASSERT(small64.value == 2310347307446489235);
    ASSERT(small64ProcessedBytes == 9);

    // 1000 0000 0100 0000 0010 0000 0001 0000 0000 1000 0000 0100 0000 0010 0000 0001 = -9205322385119247871 -> 1000 0001 1000 0100 1001 0000 1100 0000 1000 0000 1000 0010 1000 1000 1010 0000 1000 0000 0000 0001
    uint8_t big64ProcessedBytes = 0;
    std::vector<uint8_t> big64Data;
    big64Data.push_back(0b10000001);
    big64Data.push_back(0b10000100);
    big64Data.push_back(0b10010000);
    big64Data.push_back(0b11000000);
    big64Data.push_back(0b10000000);
    big64Data.push_back(0b10000010);
    big64Data.push_back(0b10001000);
    big64Data.push_back(0b10100000);
    big64Data.push_back(0b10000000);
    big64Data.push_back(0b00000001);
    ErrorOr<int64_t> big64 = VarInt::fromVar64(big64Data, 0, &big64ProcessedBytes);
    ASSERT(!big64.isError);
    ASSERT(big64.value == -9205322385119247871);
    ASSERT(big64ProcessedBytes == 10);

    //TODO: Test error conditions

    std::cout << "Passed fromVar64 test." << std::endl;

    // reversing all the previous tests
    std::vector<uint8_t> dataDump;

    VarInt::toVar32(0, dataDump);
    ASSERT(dataDump[0] == 0);

    VarInt::toVar32(-1, dataDump);
    ASSERT(
        dataDump[1]==255 &&
        dataDump[2]==255 &&
        dataDump[3]==255 &&
        dataDump[4]==255 &&
        dataDump[5]==15
    );

    VarInt::toVar32(4369, dataDump);
    ASSERT(
        dataDump[6]==0b10010001 &&
        dataDump[7]==0b00100010
    );

    VarInt::toVar32(2375101, dataDump);
    ASSERT(
        dataDump[8]==0b10111101 &&
        dataDump[9]==0b11111011 &&
        dataDump[10]==0b10010000 &&
        dataDump[11]==0b00000001
    );

    std::cout << "Passed toVar32 test." << std::endl;

    VarInt::toVar64(0, dataDump);
    ASSERT(dataDump[12]==0);

    VarInt::toVar64(-1, dataDump);
    ASSERT(
        dataDump[13]==255 &&
        dataDump[14]==255 &&
        dataDump[15]==255 &&
        dataDump[16]==255 &&
        dataDump[17]==255 &&
        dataDump[18]==255 &&
        dataDump[19]==255 &&
        dataDump[20]==255 &&
        dataDump[21]==255 &&
        dataDump[22]==1
    );

    VarInt::toVar64(2310347307446489235, dataDump);
    ASSERT(
        dataDump[23]==0b10010011 &&
        dataDump[24]==0b10100001 &&
        dataDump[25]==0b10000011 &&
        dataDump[26]==0b11000001 &&
        dataDump[27]==0b10101010 &&
        dataDump[28]==0b10010100 &&
        dataDump[29]==0b10000000 &&
        dataDump[30]==0b10001000 &&
        dataDump[31]==0b00100000
    );

    VarInt::toVar64(-9205322385119247871, dataDump);
    ASSERT(
        dataDump[32]==0b10000001 &&
        dataDump[33]==0b10000100 &&
        dataDump[34]==0b10010000 &&
        dataDump[35]==0b11000000 &&
        dataDump[36]==0b10000000 &&
        dataDump[37]==0b10000010 &&
        dataDump[38]==0b10001000 &&
        dataDump[39]==0b10100000 &&
        dataDump[40]==0b10000000 &&
        dataDump[41]==0b00000001
    )

    std::cout << "Passed toVar64 test." << std::endl;

    return 0;
}
