/*******************************************************************************
 * Copyright (c) 2025, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include "base64.h"
#include <array>
#include <algorithm>
#include <cctype>

namespace netplus {
    const std::string basis_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    static const std::array<unsigned char, 256> B64_REV = [] {
        std::array<unsigned char, 256> t;
        std::fill(t.begin(), t.end(), (unsigned char)-1);

        for (size_t i = 0; i < basis_64.length(); ++i) {
            t[basis_64[i]] =static_cast<signed char>(i);
        }
        return t; // std::array is returned by value, avoiding the stack corruption warning.
    }();
};

netplus::base64::base64() {}

netplus::base64::base64(const base64& data) {
    _data.clear();
    std::copy(data._data.begin(), data._data.end(), std::back_inserter(_data));
}

void netplus::base64::clear() {
    _data.clear();
}

netplus::base64& netplus::base64::setEncodedData(const std::vector<char>& encoded_data) {
    _data.clear();
    std::copy(encoded_data.begin(), encoded_data.end(), std::back_inserter(_data));
    return *this;
}

netplus::base64 &netplus::base64::setEncodedData(const std::string& encoded_data){
    _data.clear();
    std::copy(encoded_data.begin(), encoded_data.end(), std::back_inserter(_data));
    return *this;
}

// --- DECODE to vector<char> ---
const netplus::base64& netplus::base64::operator>>(std::vector<char> &src) const {
    src.clear(); // Ensure the destination string is empty

    // Array to temporarily hold the 4 six-bit index values (0-63)
    unsigned char block[4] = {0};
    int i = 0; // Index for block[4], counts how many valid chars we've collected

    // Iterate over the internal vector<char> data
    for (char c : _data) {
        if (c == '=') break; // Stop on padding

        // Use the safe reverse lookup table for fast index retrieval
        signed char idx_val = B64_REV[(unsigned char)c];

        if (idx_val == -1 || std::isspace((unsigned char)c)) {
            continue; // Skip invalid or whitespace characters
        }

        // Store the 6-bit index
        block[i++] = (unsigned char)idx_val;

        // Once we have a full block of 4 characters, decode them into 3 bytes
        if (i == 4) {
            // Byte 1: indices[0] (6 bits) + indices[1] (2 highest bits)
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));

            // Byte 2: indices[1] (4 lowest bits) + indices[2] (4 highest bits)
            src.push_back((char)((block[1] << 4) | (block[2] >> 2)));

            // Byte 3: indices[2] (2 lowest bits) + indices[3] (6 bits)
            src.push_back((char)((block[2] << 6) | block[3]));

            // Reset the index counter for the next block
            i = 0;
        }
    }

    // --- Handle the Final Partial Block (Padding) ---
    // i will be 2 or 3 if padding was present or stream ended partially
    if (i > 0) {
        if (i == 3) {
            // Two full bytes (input ended with '=')
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));
            src.push_back((char)((block[1] << 4) | (block[2] >> 2)));
        } else if (i == 2) {
            // One full byte (input ended with '==')
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));
        }
        // If i == 1, the input is corrupt and we ignore it.
    }

    return *this;
}

// --- DECODE to string ---
const netplus::base64& netplus::base64::operator>>(std::string& src) const {
    src.clear(); // Ensure the destination string is empty

    // Array to temporarily hold the 4 six-bit index values (0-63)
    unsigned char block[4] = {0};
    int i = 0; // Index for block[4], counts how many valid chars we've collected

    // Iterate over the internal vector<char> data
    for (char c : _data) {
        if (c == '=') break; // Stop on padding

        // Use the safe reverse lookup table for fast index retrieval
        signed char idx_val = B64_REV[(unsigned char)c];

        if (idx_val == -1 || std::isspace((unsigned char)c)) {
            continue; // Skip invalid or whitespace characters
        }

        // Store the 6-bit index
        block[i++] = (unsigned char)idx_val;

        // Once we have a full block of 4 characters, decode them into 3 bytes
        if (i == 4) {
            // Byte 1: indices[0] (6 bits) + indices[1] (2 highest bits)
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));

            // Byte 2: indices[1] (4 lowest bits) + indices[2] (4 highest bits)
            src.push_back((char)((block[1] << 4) | (block[2] >> 2)));

            // Byte 3: indices[2] (2 lowest bits) + indices[3] (6 bits)
            src.push_back((char)((block[2] << 6) | block[3]));

            // Reset the index counter for the next block
            i = 0;
        }
    }

    // --- Handle the Final Partial Block (Padding) ---
    // i will be 2 or 3 if padding was present or stream ended partially
    if (i > 0) {
        if (i == 3) {
            // Two full bytes (input ended with '=')
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));
            src.push_back((char)((block[1] << 4) | (block[2] >> 2)));
        } else if (i == 2) {
            // One full byte (input ended with '==')
            src.push_back((char)((block[0] << 2) | (block[1] >> 4)));
        }
        // If i == 1, the input is corrupt and we ignore it.
    }

    return *this;
}

// --- ENCODE from vector<char> ---
netplus::base64 &netplus::base64::operator<<(const std::vector<char> &data) {
    size_t missing = 0;
    size_t ret_size = data.size();
    std::vector<char> ret;

    while ((ret_size % 3) != 0) {
        ++ret_size;
        ++missing;
    }
    ret_size = 4 * ret_size / 3;
    ret.reserve(ret_size);

    for (unsigned int i = 0; i < ret_size / 4; ++i) {
        size_t index = i * 3;
        char b3[3];
        b3[0] = (index + 0 < data.size()) ? data[index + 0] : 0;
        b3[1] = (index + 1 < data.size()) ? data[index + 1] : 0;
        b3[2] = (index + 2 < data.size()) ? data[index + 2] : 0;

        char b4[4];
        b4[0] = ((b3[0] & 0xfc) >> 2);
        b4[1] = ((b3[0] & 0x03) << 4) + ((b3[1] & 0xf0) >> 4);
        b4[2] = ((b3[1] & 0x0f) << 2) + ((b3[2] & 0xc0) >> 6);
        b4[3] = ((b3[2] & 0x3f) << 0);

        ret.emplace_back(basis_64[b4[0]]);
        ret.emplace_back(basis_64[b4[1]]);
        ret.emplace_back(basis_64[b4[2]]);
        ret.emplace_back(basis_64[b4[3]]);
    }

    for (size_t i = 0; i < missing; ++i)
        ret[ret_size - i - 1] = '=';

    std::copy(ret.begin(), ret.end(), std::back_inserter(_data));
    return *this;
}

// --- ENCODE from string ---
netplus::base64 &netplus::base64::operator<<(const std::string &data) {
    std::vector<char> vec(data.begin(), data.end());
    return (*this << vec);
}

netplus::base64 &netplus::base64::operator<<(const base64 &data) {
    std::copy(data._data.begin(), data._data.end(), std::back_inserter(_data));
    return *this;
}

const std::vector<unsigned char> &netplus::base64::encoded() const {
    return _data;
}

// --- OUTPUT encoded string ---
std::ostream& operator<<(std::ostream& os, const netplus::base64& src) {
    const std::vector<unsigned char>& enc = src.encoded();
    if (!enc.empty())
        os.write(reinterpret_cast<const char*>(enc.data()), static_cast<std::streamsize>(enc.size()));
    return os;
}

// --- INPUT: read plain text and encode it ---
std::istream& operator>>(std::istream& is, netplus::base64& dest) {
    std::string input;
    if (is >> input) {
        dest << input;
    }
    return is;
}
