#include "crc_generic.h"

namespace codings
{
    namespace crc
    {
        GenericCRC::GenericCRC(unsigned num_bits,
                               uint64_t poly,
                               uint64_t initial_value,
                               uint64_t final_xor,
                               bool input_reflected,
                               bool result_reflected)
            : d_num_bits(num_bits),
              d_mask(num_bits == 64 ? ~static_cast<uint64_t>(0)
                                    : (static_cast<uint64_t>(1) << num_bits) - 1),
              d_initial_value(initial_value & d_mask),
              d_final_xor(final_xor & d_mask),
              d_input_reflected(input_reflected),
              d_result_reflected(result_reflected)
        {
            d_table[0] = 0;
            if (d_input_reflected)
            {
                poly = reflect(poly);
                uint64_t crc = 1;
                int i = 128;
                do
                {
                    if (crc & 1)
                        crc = (crc >> 1) ^ poly;
                    else
                        crc >>= 1;
                    for (int j = 0; j < 256; j += 2 * i)
                        d_table[i + j] = (crc ^ d_table[j]) & d_mask;
                    i >>= 1;
                } while (i > 0);
            }
            else
            {
                const uint64_t msb = static_cast<uint64_t>(1) << (num_bits - 1);
                uint64_t crc = msb;
                int i = 1;
                do
                {
                    if (crc & msb)
                        crc = (crc << 1) ^ poly;
                    else
                        crc <<= 1;
                    for (int j = 0; j < i; ++j)
                        d_table[i + j] = (crc ^ d_table[j]) & d_mask;
                    i <<= 1;
                } while (i < 256);
            }
        }

        uint64_t GenericCRC::compute(uint8_t *data, unsigned int len)
        {
            uint64_t rem = d_initial_value;

            if (d_input_reflected)
            {
                for (unsigned int i = 0; i < len; ++i)
                {
                    uint8_t byte = data[i];
                    uint8_t idx = (rem ^ byte) & 0xff;
                    rem = d_table[idx] ^ (rem >> 8);
                }
            }
            else
            {
                for (unsigned int i = 0; i < len; ++i)
                {
                    uint8_t byte = data[i];
                    uint8_t idx = ((rem >> (d_num_bits - 8)) ^ byte) & 0xff;
                    rem = (d_table[idx] ^ (rem << 8)) & d_mask;
                }
            }

            if (d_input_reflected != d_result_reflected)
                rem = reflect(rem);

            rem = rem ^ d_final_xor;
            return rem;
        }

        uint64_t GenericCRC::reflect(uint64_t word) const
        {
            uint64_t ret;
            ret = word & 1;
            for (unsigned i = 1; i < d_num_bits; ++i)
            {
                word >>= 1;
                ret = (ret << 1) | (word & 1);
            }
            return ret;
        }
    }
}