#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# md6hash for Python 2, 3
#
# Usage:
#     md6 = md6hash()
#     data = "md6 FTW"
#     result = md6.hex(data)
#     print(result)
#

import binascii

class md6hash():
	def __to_word(self, i_byte):
		length = len(i_byte)
		o_word = []

		for i in range(0, length, 8):
			o_word.append(
				((i_byte[i + 0] & 0xff) << 56) |
				((i_byte[i + 1] & 0xff) << 48) |
				((i_byte[i + 2] & 0xff) << 40) |
				((i_byte[i + 3] & 0xff) << 32) |
				((i_byte[i + 4] & 0xff) << 24) |
				((i_byte[i + 5] & 0xff) << 16) |
				((i_byte[i + 6] & 0xff) <<  8) |
				((i_byte[i + 7] & 0xff) <<  0)
			)

		return o_word

	def __from_word(self, i_word):
		length = len(i_word)
		o_byte = []

		for i in range(length):
			o_byte.append((i_word[i] >> 56) & 0xff)
			o_byte.append((i_word[i] >> 48) & 0xff)
			o_byte.append((i_word[i] >> 40) & 0xff)
			o_byte.append((i_word[i] >> 32) & 0xff)
			o_byte.append((i_word[i] >> 24) & 0xff)
			o_byte.append((i_word[i] >> 16) & 0xff)
			o_byte.append((i_word[i] >>  8) & 0xff)
			o_byte.append((i_word[i] >>  0) & 0xff)

		return o_byte

	def __crop(self, size, data, right):
		length = int((size + 7) / 8)
		remain = size % 8

		if right:
			data = data[len(data) - length:]
		else:
			data = data[:length]

		if remain > 0:
			data[length - 1] &= (0xff << (8 - remain)) & 0xff

		return data

	def __hash(self, size, data, key, levels):
		b = 512
		c = 128
		n = 89
		d = size
		M = data

		K = key[:64]
		k = len(K)

		while len(K) < 64:
			K.append(0x00)

		K = self.__to_word(K)

		r = max(80 if k else 0, 40 + int(d / 4))

		L = levels
		ell = 0

		S0 = 0x0123456789abcdef
		Sm = 0x7311c2812425cfa0

		Q = [
			0x7311c2812425cfa0, 0x6432286434aac8e7, 0xb60450e9ef68b7c1,
			0xe8fb23908d9f06f1, 0xdd2e76cba691e5bf, 0x0cd0d63b2c30bc41,
			0x1f8ccf6823058f8a, 0x54e5ed5b88e3775d, 0x4ad12aae0a6d6031,
			0x3e7f16bb88222e0d, 0x8af8671d3fb50c2c, 0x995ad1178bd25c31,
			0xc878c1dd04c4b633, 0x3b72066c7a1552ac, 0x0d6f3522631effcb
		];

		t  = [17, 18, 21, 31, 67, 89]
		rs = [10,  5, 13, 10, 11, 12,  2,  7, 14, 15,  7, 13, 11,  7,  6, 12]
		ls = [11, 24,  9, 16, 15,  9, 27, 15,  6,  2, 29,  8, 15,  5, 31,  9]

		def f(N):
			S = S0
			A = list(N)

			j = 0
			i = n

			while j < r:
				for s in range(16):
					x = S
					x ^= A[i + s - t[5]]
					x ^= A[i + s - t[0]]
					x ^= A[i + s - t[1]] & A[i + s - t[2]]
					x ^= A[i + s - t[3]] & A[i + s - t[4]]
					x ^= x >> rs[s]

					if len(A) <= i + s:
						while len(A) <= i + s:
							A.append(0x00)

					A[i + s] = x ^ ((x << ls[s]) & 0xffffffffffffffff)

				S = (((S << 1) & 0xffffffffffffffff) ^ (S >> 63)) ^ (S & Sm)

				j += 1
				i += 16

			return A[(len(A) - 16):]

		def mid(B, C, i, p, z):
			U = ((ell & 0xff) << 56) | i & 0xffffffffffffff
			V = ((r & 0xfff) << 48) | ((L & 0xff) << 40) | ((z & 0xf) << 36) | ((p & 0xffff) << 20) | ((k & 0xff) << 12) | (d & 0xfff)

			return f(Q + K + [U, V] + C + B)

		def par(M):
			P = 0
			B = []
			C = []
			z = 0 if len(M) > b else 1

			while len(M) < 1 or (len(M) % b) > 0:
				M.append(0x00)
				P += 8

			M = self.__to_word(M)

			while len(M) > 0:
				B.append(M[:int(b / 8)])
				M = M[int(b / 8):]

			i = 0
			p = 0
			l = len(B)

			while i < l:
				p = P if i == (len(B) - 1) else 0
				C += mid(B[i], [], i, p, z)

				i += 1
				p = 0

			return self.__from_word(C)

		def seq(M):
			P = 0
			B = []
			C = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]

			while len(M) < 1 or (len(M) % (b - c)) > 0:
				M.append(0x00)
				P += 8

			M = self.__to_word(M)

			while len(M) > 0:
				B.append(M[:int((b - c) / 8)])
				M = M[int((b - c) / 8):]

			i = 0
			p = 0
			l = len(B)

			while i < l:
				p = P if i == (len(B) - 1) else 0
				z = 1 if i == (len(B) - 1) else 0
				C = mid(B[i], C, i, p, z)

				i += 1
				p = 0

			return self.__from_word(C)

		while True:
			ell += 1
			M = seq(M) if ell > L else par(M)

			if len(M) == c:
				break

		return self.__crop(d, M, True)

	def __bytes(self, i_str):
		i_str = binascii.hexlify(i_str.encode("ascii"))
		o_byte = [int(i_str[i:i+2], 16) for i in range(0, len(i_str), 2)]

		return o_byte

	def __prehash(self, data, size, key, levels):
		data = self.__bytes(data)
		key = self.__bytes(key)

		if size <= 0:
			size = 1
		elif size > 512:
			size = 512

		return self.__hash(size, data, key, levels)

	def hex(self, data="", size=256, key="", levels=64):
		byte = self.__prehash(data, size, key, levels)
		hexstr = ""

		for i in byte:
			hexstr += "%02x" % i

		return hexstr

	def raw(self, data="", size=256, key="", levels=64):
		byte = self.__prehash(data, size, key, levels)
		rawstr = ""

		for i in byte:
			rawstr += chr(i)

		return rawstr