/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "BitTools.hpp"

#include <array>
#include <map>
#include <cstddef>
#include <cassert>

namespace BitTools
{
    namespace
    {
        const std::map<BitPack, const std::vector<std::uint8_t>> bitPacks = {
            {BitPack::_71, {7, 1}},
            {BitPack::_44, {4, 4}},
            {BitPack::_431, {4, 3, 1}},
            {BitPack::_21, {2, 1}}
        };

        constexpr std::array<std::uint8_t, 7> uint8_pow2 =
            {2, 4, 8, 16, 32, 64, 128};
    }

    std::uint8_t packUInt8(BitPack alignment,
                           const std::vector<std::uint8_t> &values)
    {
        const std::vector<std::uint8_t> &bits = bitPacks.at(alignment);
        assert(!values.empty() && values.size() == bits.size());

        std::uint8_t packed = values.front();
        std::uint8_t exponent = uint8_pow2[bits.front() - 1];
        std::size_t valueIx = 1;
        while (valueIx < bits.size())
        {
            std::uint8_t quotient = values[valueIx];
            for (std::uint8_t i = 0; i < bits[valueIx]; i++)
            {
                if ((quotient % 2) == 1)
                    packed += exponent;

                exponent *= 2;
                quotient /= 2;
            }

            valueIx++;
        }

        return packed;
    }

    std::vector<std::uint8_t> unpackUInt8(BitPack alignment,
                                          const std::uint8_t &packed)
    {
        const std::vector<std::uint8_t> &bits = bitPacks.at(alignment);

        std::vector<std::uint8_t> unpacked;
        unpacked.reserve(bits.size());

        std::size_t valueIx = 0;
        std::uint8_t quotient = packed;
        while (valueIx < bits.size())
        {
            std::uint8_t value = 0, exponent = 1;
            for (std::uint8_t i = 0; i < bits[valueIx]; i++)
            {
                if ((quotient % 2) == 1)
                    value += exponent;

                exponent *= 2;
                quotient /= 2;
            }

            unpacked.push_back(value);
            valueIx++;
        }

        assert(unpacked.size() == bits.size());
        return unpacked;
    }
}
