/*
	PIP - Platform Independent Primitives
	Digest algorithms
	Ivan Pelipenko peri4ko@yandex.ru

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

	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 Lesser General Public License for more details.

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

#include "pidigest_md5_p.h"

template<typename T>
inline T rotate_u(T v, int bits) {
	return (v << bits) | (v >> ((sizeof(T) * 8) - bits));
}

PIByteArray MD5::md5(const PIByteArray & in) {
	constexpr int part_size         = 64;

	static constexpr uint32_t K[64] = {
		0xD76AA478u, 0xE8C7B756u, 0x242070DBu, 0xC1BDCEEEu, 0xF57C0FAFu, 0x4787C62Au, 0xA8304613u, 0xFD469501u, 0x698098D8u, 0x8B44F7AFu,
		0xFFFF5BB1u, 0x895CD7BEu, 0x6B901122u, 0xFD987193u, 0xA679438Eu, 0x49B40821u, 0xF61E2562u, 0xC040B340u, 0x265E5A51u, 0xE9B6C7AAu,
		0xD62F105Du, 0x02441453u, 0xD8A1E681u, 0xE7D3FBC8u, 0x21E1CDE6u, 0xC33707D6u, 0xF4D50D87u, 0x455A14EDu, 0xA9E3E905u, 0xFCEFA3F8u,
		0x676F02D9u, 0x8D2A4C8Au, 0xFFFA3942u, 0x8771F681u, 0x6D9D6122u, 0xFDE5380Cu, 0xA4BEEA44u, 0x4BDECFA9u, 0xF6BB4B60u, 0xBEBFBC70u,
		0x289B7EC6u, 0xEAA127FAu, 0xD4EF3085u, 0x04881D05u, 0xD9D4D039u, 0xE6DB99E5u, 0x1FA27CF8u, 0xC4AC5665u, 0xF4292244u, 0x432AFF97u,
		0xAB9423A7u, 0xFC93A039u, 0x655B59C3u, 0x8F0CCC92u, 0xFFEFF47Du, 0x85845DD1u, 0x6FA87E4Fu, 0xFE2CE6E0u, 0xA3014314u, 0x4E0811A1u,
		0xF7537E82u, 0xBD3AF235u, 0x2AD7D2BBu, 0xEB86D391u};
	static constexpr uint32_t s[64] = {7,  12, 17, 22, 7,  12, 17, 22, 7,  12, 17, 22, 7,  12, 17, 22, 5,  9,  14, 20, 5,  9,
									   14, 20, 5,  9,  14, 20, 5,  9,  14, 20, 4,  11, 16, 23, 4,  11, 16, 23, 4,  11, 16, 23,
									   4,  11, 16, 23, 6,  10, 15, 21, 6,  10, 15, 21, 6,  10, 15, 21, 6,  10, 15, 21};

	uint32_t a0                     = 0x67452301u;
	uint32_t b0                     = 0xefcdab89u;
	uint32_t c0                     = 0x98badcfeu;
	uint32_t d0                     = 0x10325476u;

	uint64_t l                      = in.size() * 8;
	PIByteArray msg                 = in;
	int add_l                       = (part_size - 8) - (msg.size() % (part_size));
	if (add_l <= 0) add_l += part_size;
	msg.append(0x80);
	for (int i = 1; i < add_l; ++i)
		msg.append(0x0);
	msg << l;
	int parts = msg.size_s() / part_size;
	// piCout << l << "add" << add_l << "=" << (in.size() + add_l + sizeof(l)) << msg.size() << parts;
	for (int p = 0; p < parts; ++p) {
		uint32_t * mw = (uint32_t *)msg.data(p * part_size);

		uint32_t A    = a0;
		uint32_t B    = b0;
		uint32_t C    = c0;
		uint32_t D    = d0;
		for (int i = 0; i < 64; ++i) {
			uint32_t F = 0, g = 0;
			if (i >= 0 && i <= 15) {
				F = (B & C) | ((~B) & D);
				g = i;
			} else if (i >= 16 && i <= 31) {
				F = (D & B) | ((~D) & C);
				g = (5 * i + 1) % 16;
			} else if (i >= 32 && i <= 47) {
				F = B ^ C ^ D;
				g = (3 * i + 5) % 16;
			} else if (i >= 48 && i <= 63) {
				F = C ^ (B | (~D));
				g = (7 * i) % 16;
			}
			F = F + A + K[i] + mw[g];
			A = D;
			D = C;
			C = B;
			B = B + rotate_u(F, s[i]);
		}
		a0 = a0 + A;
		b0 = b0 + B;
		c0 = c0 + C;
		d0 = d0 + D;
	}
	PIByteArray ret;
	ret << a0 << b0 << c0 << d0;
	return ret;
}
