import Foundation

/**
 Message Digest Algorithm 5

 - seealso:
  - https: //ja.wikipedia.org/wiki/MD5
  - https: //www.ietf.org/rfc/rfc1321.txt
 */
final class MD5 {

    static let a: UInt32 = 0x67452301
    static let b: UInt32 = 0xefcdab89
    static let c: UInt32 = 0x98badcfe
    static let d: UInt32 = 0x10325476

    static let S11: UInt32 = 7
    static let S12: UInt32 = 12
    static let S13: UInt32 = 17
    static let S14: UInt32 = 22
    static let S21: UInt32 = 5
    static let S22: UInt32 = 9
    static let S23: UInt32 = 14
    static let S24: UInt32 = 20
    static let S31: UInt32 = 4
    static let S32: UInt32 = 11
    static let S33: UInt32 = 16
    static let S34: UInt32 = 23
    static let S41: UInt32 = 6
    static let S42: UInt32 = 10
    static let S43: UInt32 = 15
    static let S44: UInt32 = 21

    struct Context {
        var a: UInt32 = MD5.a
        var b: UInt32 = MD5.b
        var c: UInt32 = MD5.c
        var d: UInt32 = MD5.d

        mutating func FF(_ x: UInt32, _ s: UInt32, _ k: UInt32) {
            let swap: UInt32 = d
            let F: UInt32 = (b & c) | ((~b) & d)
            d = c
            c = b
            b = b &+ rotateLeft(a &+ F &+ k &+ x, s)
            a = swap
        }

        mutating func GG(_ x: UInt32, _ s: UInt32, _ k: UInt32) {
            let swap: UInt32 = d
            let G: UInt32 = (d & b) | (c & (~d))
            d = c
            c = b
            b = b &+ rotateLeft(a &+ G &+ k &+ x, s)
            a = swap
        }

        mutating func HH(_ x: UInt32, _ s: UInt32, _ k: UInt32) {
            let swap: UInt32 = d
            let H: UInt32 = b ^ c ^ d
            d = c
            c = b
            b = b &+ rotateLeft(a &+ H &+ k &+ x, s)
            a = swap
        }

        mutating func II(_ x: UInt32, _ s: UInt32, _ k: UInt32) {
            let swap: UInt32 = d
            let I: UInt32 =  c ^ (b | (~d))
            d = c
            c = b
            b = b &+ rotateLeft(a &+ I &+ k &+ x, s)
            a = swap
        }

        func rotateLeft(_ x: UInt32, _ n: UInt32) -> UInt32 {
            return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))
        }

        var data: Data {
            return a.data + b.data + c.data + d.data
        }
    }

    static func base64(_ message: String) -> String {
        return calculate(message).base64EncodedString(options: .lineLength64Characters)
    }

    static func calculate(_ message: String) -> Data {
        return calculate(ByteArray().writeUTF8Bytes(message).data)
    }

    static func calculate(_ data: Data) -> Data {
        var context: Context = Context()

        let count: Data = UInt64(data.count * 8).bigEndian.data
        let message: ByteArray = ByteArray(data: data + [0x80])
        message.length += 64 - (message.length % 64)
        message[message.length - 8] = count[7]
        message[message.length - 7] = count[6]
        message[message.length - 6] = count[5]
        message[message.length - 5] = count[4]
        message[message.length - 4] = count[3]
        message[message.length - 3] = count[2]
        message[message.length - 2] = count[1]
        message[message.length - 1] = count[0]

        message.sequence(64) {
            let x: [UInt32] = $0.toUInt32()

            guard x.count == 16 else {
                return
            }

            var ctx: Context = Context()
            ctx.a = context.a
            ctx.b = context.b
            ctx.c = context.c
            ctx.d = context.d

            /* Round 1 */
            ctx.FF(x[ 0], S11, 0xd76aa478)
            ctx.FF(x[ 1], S12, 0xe8c7b756)
            ctx.FF(x[ 2], S13, 0x242070db)
            ctx.FF(x[ 3], S14, 0xc1bdceee)
            ctx.FF(x[ 4], S11, 0xf57c0faf)
            ctx.FF(x[ 5], S12, 0x4787c62a)
            ctx.FF(x[ 6], S13, 0xa8304613)
            ctx.FF(x[ 7], S14, 0xfd469501)
            ctx.FF(x[ 8], S11, 0x698098d8)
            ctx.FF(x[ 9], S12, 0x8b44f7af)
            ctx.FF(x[10], S13, 0xffff5bb1)
            ctx.FF(x[11], S14, 0x895cd7be)
            ctx.FF(x[12], S11, 0x6b901122)
            ctx.FF(x[13], S12, 0xfd987193)
            ctx.FF(x[14], S13, 0xa679438e)
            ctx.FF(x[15], S14, 0x49b40821)

            /* Round 2 */
            ctx.GG(x[ 1], S21, 0xf61e2562)
            ctx.GG(x[ 6], S22, 0xc040b340)
            ctx.GG(x[11], S23, 0x265e5a51)
            ctx.GG(x[ 0], S24, 0xe9b6c7aa)
            ctx.GG(x[ 5], S21, 0xd62f105d)
            ctx.GG(x[10], S22, 0x2441453)
            ctx.GG(x[15], S23, 0xd8a1e681)
            ctx.GG(x[ 4], S24, 0xe7d3fbc8)
            ctx.GG(x[ 9], S21, 0x21e1cde6)
            ctx.GG(x[14], S22, 0xc33707d6)
            ctx.GG(x[ 3], S23, 0xf4d50d87)
            ctx.GG(x[ 8], S24, 0x455a14ed)
            ctx.GG(x[13], S21, 0xa9e3e905)
            ctx.GG(x[ 2], S22, 0xfcefa3f8)
            ctx.GG(x[ 7], S23, 0x676f02d9)
            ctx.GG(x[12], S24, 0x8d2a4c8a)

            /* Round 3 */
            ctx.HH(x[ 5], S31, 0xfffa3942)
            ctx.HH(x[ 8], S32, 0x8771f681)
            ctx.HH(x[11], S33, 0x6d9d6122)
            ctx.HH(x[14], S34, 0xfde5380c)
            ctx.HH(x[ 1], S31, 0xa4beea44)
            ctx.HH(x[ 4], S32, 0x4bdecfa9)
            ctx.HH(x[ 7], S33, 0xf6bb4b60)
            ctx.HH(x[10], S34, 0xbebfbc70)
            ctx.HH(x[13], S31, 0x289b7ec6)
            ctx.HH(x[ 0], S32, 0xeaa127fa)
            ctx.HH(x[ 3], S33, 0xd4ef3085)
            ctx.HH(x[ 6], S34, 0x4881d05)
            ctx.HH(x[ 9], S31, 0xd9d4d039)
            ctx.HH(x[12], S32, 0xe6db99e5)
            ctx.HH(x[15], S33, 0x1fa27cf8)
            ctx.HH(x[ 2], S34, 0xc4ac5665)

            /* Round 4 */
            ctx.II(x[ 0], S41, 0xf4292244)
            ctx.II(x[ 7], S42, 0x432aff97)
            ctx.II(x[14], S43, 0xab9423a7)
            ctx.II(x[ 5], S44, 0xfc93a039)
            ctx.II(x[12], S41, 0x655b59c3)
            ctx.II(x[ 3], S42, 0x8f0ccc92)
            ctx.II(x[10], S43, 0xffeff47d)
            ctx.II(x[ 1], S44, 0x85845dd1)
            ctx.II(x[ 8], S41, 0x6fa87e4f)
            ctx.II(x[15], S42, 0xfe2ce6e0)
            ctx.II(x[ 6], S43, 0xa3014314)
            ctx.II(x[13], S44, 0x4e0811a1)
            ctx.II(x[ 4], S41, 0xf7537e82)
            ctx.II(x[11], S42, 0xbd3af235)
            ctx.II(x[ 2], S43, 0x2ad7d2bb)
            ctx.II(x[ 9], S44, 0xeb86d391)

            context.a = context.a &+ ctx.a
            context.b = context.b &+ ctx.b
            context.c = context.c &+ ctx.c
            context.d = context.d &+ ctx.d
        }

        return context.data
    }
}
