// Copyright (C) 2003  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_MD5_KERNEL_1_CPp_
#define DLIB_MD5_KERNEL_1_CPp_
#include "md5_kernel_1.h"
#include "../uintn.h"

#include <sstream>
#include <cstring>

namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    namespace md5_stuff
    {
        
        inline uint32 F (
            uint32 x, 
            uint32 y, 
            uint32 z
        )
        {
            return ( (x&y) | ((~x)&z) );
        }

    // ------------------------------------------------------------------------------------

        inline uint32 G (
            uint32 x, 
            uint32 y, 
            uint32 z
        )
        {
            return ( (x&z) | (y&(~z)) );
        }

    // ------------------------------------------------------------------------------------
        
        inline uint32 H (
            uint32 x, 
            uint32 y, 
            uint32 z
        ) 
        {
            return ( x^y^z );
        }

    // ------------------------------------------------------------------------------------

        inline uint32 I (
            uint32 x, 
            uint32 y, 
            uint32 z
        )
        {
            return ( y ^ (x|(~z)) );
        }

    // ------------------------------------------------------------------------------------

        inline uint32 rotate_left (
            uint32 x, 
            uint32 n
        )
        {
            return  ( (x<<n) | (x>>(32-n)) );
        }

    // ------------------------------------------------------------------------------------

        inline void FF (
            uint32& a, 
            uint32 b, 
            uint32 c, 
            uint32 d, 
            uint32 x, 
            uint32 s, 
            uint32 ac
        ) 
        { 
            a += F(b, c, d) + x + ac; 
            a = rotate_left(a, s); 
            a += b; 
        }

    // ------------------------------------------------------------------------------------

        inline void GG (
            uint32& a, 
            uint32 b, 
            uint32 c, 
            uint32 d, 
            uint32 x, 
            uint32 s, 
            uint32 ac
        ) 
        { 
            a += G(b, c, d) + x + ac; 
            a = rotate_left(a, s); 
            a += b; 
        }

    // ------------------------------------------------------------------------------------

        inline void HH (
            uint32& a, 
            uint32 b, 
            uint32 c, 
            uint32 d, 
            uint32 x, 
            uint32 s, 
            uint32 ac
        ) 
        { 
            a += H(b, c, d) + x + ac; 
            a = rotate_left(a, s); 
            a += b; 
        }

    // ------------------------------------------------------------------------------------

        inline void II (
            uint32& a, 
            uint32 b, 
            uint32 c, 
            uint32 d, 
            uint32 x, 
            uint32 s, 
            uint32 ac
        ) 
        { 
            a += I(b, c, d) + x + ac; 
            a = rotate_left(a, s); 
            a += b; 
        }

    // ------------------------------------------------------------------------------------

        void scramble_block (
            uint32& a,
            uint32& b,
            uint32& c,
            uint32& d,
            uint32* x
        )
        {
            const uint32 S11 = 7;
            const uint32 S12 = 12;
            const uint32 S13 = 17;
            const uint32 S14 = 22;
            const uint32 S21 = 5;
            const uint32 S22 = 9;
            const uint32 S23 = 14;
            const uint32 S24 = 20;
            const uint32 S31 = 4;
            const uint32 S32 = 11;
            const uint32 S33 = 16;
            const uint32 S34 = 23;
            const uint32 S41 = 6;
            const uint32 S42 = 10;
            const uint32 S43 = 15;
            const uint32 S44 = 21;


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

            // Round 2 
            GG (a, b, c, d, x[ 1], S21, 0xf61e2562); // 17
            GG (d, a, b, c, x[ 6], S22, 0xc040b340); // 18
            GG (c, d, a, b, x[11], S23, 0x265e5a51); // 19
            GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); // 20
            GG (a, b, c, d, x[ 5], S21, 0xd62f105d); // 21
            GG (d, a, b, c, x[10], S22,  0x2441453); // 22
            GG (c, d, a, b, x[15], S23, 0xd8a1e681); // 23
            GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); // 24
            GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); // 25
            GG (d, a, b, c, x[14], S22, 0xc33707d6); // 26
            GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); // 27
            GG (b, c, d, a, x[ 8], S24, 0x455a14ed); // 28
            GG (a, b, c, d, x[13], S21, 0xa9e3e905); // 29
            GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); // 30
            GG (c, d, a, b, x[ 7], S23, 0x676f02d9); // 31
            GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); // 32

            // Round 3 
            HH (a, b, c, d, x[ 5], S31, 0xfffa3942); // 33
            HH (d, a, b, c, x[ 8], S32, 0x8771f681); // 34
            HH (c, d, a, b, x[11], S33, 0x6d9d6122); // 35
            HH (b, c, d, a, x[14], S34, 0xfde5380c); // 36
            HH (a, b, c, d, x[ 1], S31, 0xa4beea44); // 37
            HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); // 38
            HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); // 39
            HH (b, c, d, a, x[10], S34, 0xbebfbc70); // 40
            HH (a, b, c, d, x[13], S31, 0x289b7ec6); // 41
            HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); // 42
            HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); // 43
            HH (b, c, d, a, x[ 6], S34,  0x4881d05); // 44
            HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); // 45
            HH (d, a, b, c, x[12], S32, 0xe6db99e5); // 46
            HH (c, d, a, b, x[15], S33, 0x1fa27cf8); // 47
            HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); // 48

            // Round 4 
            II (a, b, c, d, x[ 0], S41, 0xf4292244); // 49
            II (d, a, b, c, x[ 7], S42, 0x432aff97); // 50
            II (c, d, a, b, x[14], S43, 0xab9423a7); // 51
            II (b, c, d, a, x[ 5], S44, 0xfc93a039); // 52
            II (a, b, c, d, x[12], S41, 0x655b59c3); // 53
            II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); // 54
            II (c, d, a, b, x[10], S43, 0xffeff47d); // 55
            II (b, c, d, a, x[ 1], S44, 0x85845dd1); // 56
            II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); // 57
            II (d, a, b, c, x[15], S42, 0xfe2ce6e0); // 58
            II (c, d, a, b, x[ 6], S43, 0xa3014314); // 59
            II (b, c, d, a, x[13], S44, 0x4e0811a1); // 60
            II (a, b, c, d, x[ 4], S41, 0xf7537e82); // 61
            II (d, a, b, c, x[11], S42, 0xbd3af235); // 62
            II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); // 63
            II (b, c, d, a, x[ 9], S44, 0xeb86d391); // 64
        }

    } 

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------


    const std::string md5 (
        const std::string& input
    )
    {
        unsigned char output[16];
        md5 ( 
            reinterpret_cast<const unsigned char*>(input.data()),
            static_cast<unsigned long>(input.size()),
            output
            );

        
        std::stringstream temp;
        for (int i = 0; i < 16; ++i)
        {
            temp.fill('0');
            temp.width(2);
            temp << std::hex << static_cast<unsigned int>(output[i]);
        }
        
        return temp.str();
    }

// ----------------------------------------------------------------------------------------

    void md5 (
        const unsigned char* input,
        unsigned long len,
        unsigned char* output
    )
    {
        using namespace md5_stuff;




        // make a temp version of input with enough space for padding and len appended
        unsigned long extra_len = 64-len%64;
        if (extra_len <= 8)
            extra_len += 64;
        unsigned char* temp = new unsigned char[extra_len + len];

        // number of 16 word blocks
        const unsigned long N = (extra_len + len)/64;

        const unsigned char* input2 = input;
        unsigned char* temp2 = temp;
        unsigned char* end = temp+len;

        // copy input into temp
        while (temp2 != end)
        {
            *temp2 = *input2;
            ++temp2;
            ++input2;
        }

        // pad temp
        end += extra_len-8;
        *temp2 = static_cast<unsigned char>(0x80);
        ++temp2;
        while (temp2 != end)
        {
            *temp2 = 0;
            ++temp2;
        }

        // make len the number of bits in the original message
        // but first multiply len by 8 and since len is only 32 bits the number might
        // overflow so we will carry out the multiplication manually and end up with
        // the result in the base 65536 number with three digits
        // result = low + high*65536 + upper*65536*65536
        unsigned long low = len & 0xFFFF;
        unsigned long high = len >> 16;
        unsigned long upper;
        unsigned long tmp;
        tmp = low * 8;
        low = tmp & 0xFFFF;
        tmp = high * 8 + (tmp>>16);
        high = tmp & 0xFFFF;
        upper = tmp >> 16;
        

        // append the length
        *temp2 = static_cast<unsigned char>(low&0xFF);
        ++temp2;
        *temp2 = static_cast<unsigned char>((low>>8)&0xFF);
        ++temp2;
        *temp2 = static_cast<unsigned char>((high)&0xFF);
        ++temp2;
        *temp2 = static_cast<unsigned char>((high>>8)&0xFF);
        ++temp2;
        *temp2 = static_cast<unsigned char>((upper)&0xFF);;
        ++temp2;
        *temp2 = static_cast<unsigned char>((upper>>8)&0xFF);;
        ++temp2;
        *temp2 = 0;
        ++temp2;
        *temp2 = 0;
        

        uint32 a = 0x67452301;
        uint32 b = 0xefcdab89;
        uint32 c = 0x98badcfe;
        uint32 d = 0x10325476;


        // an array of 16 words
        uint32 x[16];

        for (unsigned long i = 0; i < N; ++i)
        {
    
            // copy a block of 16 words from m into x
            for (unsigned long j = 0; j < 16; ++j)
            {
                x[j] = (
                    (static_cast<uint32>(temp[4*(j + 16*i) + 3]) << 24) |
                    (static_cast<uint32>(temp[4*(j + 16*i) + 2]) << 16) |
                    (static_cast<uint32>(temp[4*(j + 16*i) + 1]) << 8 ) |
                    (static_cast<uint32>(temp[4*(j + 16*i)    ])      )
                    );               
            }

            uint32 aa = a;
            uint32 bb = b;
            uint32 cc = c;
            uint32 dd = d;


            scramble_block(a,b,c,d,x);


            a = a + aa;
            b = b + bb;
            c = c + cc;
            d = d + dd;

        }


        // put a, b, c, and d into output
        output[0]   = static_cast<unsigned char>((a)    &0xFF);
        output[1]   = static_cast<unsigned char>((a>>8) &0xFF);
        output[2]   = static_cast<unsigned char>((a>>16)&0xFF);
        output[3]   = static_cast<unsigned char>((a>>24)&0xFF);

        output[4]   = static_cast<unsigned char>((b)    &0xFF);
        output[5]   = static_cast<unsigned char>((b>>8) &0xFF);
        output[6]   = static_cast<unsigned char>((b>>16)&0xFF);
        output[7]   = static_cast<unsigned char>((b>>24)&0xFF);

        output[8]   = static_cast<unsigned char>((c)    &0xFF);
        output[9]   = static_cast<unsigned char>((c>>8) &0xFF);
        output[10]  = static_cast<unsigned char>((c>>16)&0xFF);
        output[11]  = static_cast<unsigned char>((c>>24)&0xFF);

        output[12]  = static_cast<unsigned char>((d)    &0xFF);
        output[13]  = static_cast<unsigned char>((d>>8) &0xFF);
        output[14]  = static_cast<unsigned char>((d>>16)&0xFF);
        output[15]  = static_cast<unsigned char>((d>>24)&0xFF);

        delete [] temp;
    }

// ----------------------------------------------------------------------------------------

    const std::string md5 (
        std::istream& input
    )
    {
        unsigned char output[16];
        md5 ( 
            input,
            output
            );

        
        std::stringstream temp;
        for (int i = 0; i < 16; ++i)
        {
            temp.fill('0');
            temp.width(2);
            temp << std::hex << static_cast<unsigned int>(output[i]);
        }
        
        return temp.str();
    }

// ----------------------------------------------------------------------------------------

    void md5 (
        std::istream& input,
        unsigned char* output
    )
    {
        using namespace md5_stuff;


                

        uint32 a = 0x67452301;
        uint32 b = 0xefcdab89;
        uint32 c = 0x98badcfe;
        uint32 d = 0x10325476;



        std::streamsize len = 0;

        // an array of 16 words
        uint32 x[16];
        unsigned char temp[64];



        bool write_length = false;
        bool at_end = false;
        std::streambuf& inputbuf = *input.rdbuf();
        while(!at_end)
        {
            std::streamsize num = inputbuf.sgetn(reinterpret_cast<char*>(temp),64);
            len += num;

            // if we hit the end of the stream then pad and add length
            if (num < 64)
            {
                at_end = true;
                unsigned char* temp2 = temp;
                unsigned char* end;
                if (num < 56)
                    end = temp+56;
                else
                    end = temp+64;

                temp2 += num;

                // apply padding
                *temp2 = 0x80;
                ++temp2;
                while (temp2 != end)
                {
                    *temp2 = 0;
                    ++temp2;
                }


                if (num < 56)
                {
                    write_length = true;
                    // make len the number of bits in the original message
                    // but first multiply len by 8 and since len is only 32 bits the number might
                    // overflow so we will carry out the multiplication manually and end up with
                    // the result in the base 65536 number with three digits
                    // result = low + high*65536 + upper*65536*65536
                    unsigned long low = len & 0xFFFF;
                    unsigned long high = len >> 16;
                    unsigned long upper;
                    unsigned long tmp;
                    tmp = low * 8;
                    low = tmp & 0xFFFF;
                    tmp = high * 8 + (tmp>>16);
                    high = tmp & 0xFFFF;
                    upper = tmp >> 16;


                    // append the length
                    *temp2 = static_cast<unsigned char>(low&0xFF);
                    ++temp2;
                    *temp2 = static_cast<unsigned char>((low>>8)&0xFF);
                    ++temp2;
                    *temp2 = static_cast<unsigned char>((high)&0xFF);
                    ++temp2;
                    *temp2 = static_cast<unsigned char>((high>>8)&0xFF);
                    ++temp2;
                    *temp2 = static_cast<unsigned char>((upper)&0xFF);;
                    ++temp2;
                    *temp2 = static_cast<unsigned char>((upper>>8)&0xFF);;
                    ++temp2;
                    *temp2 = 0;
                    ++temp2;
                    *temp2 = 0;
                }


            }


            // copy a block of 16 words from m into x
            for (unsigned long i = 0; i < 16; ++i)
            {
                x[i] = (
                    (static_cast<uint32>(temp[4*i + 3]) << 24) |
                    (static_cast<uint32>(temp[4*i + 2]) << 16) |
                    (static_cast<uint32>(temp[4*i + 1]) << 8 ) |
                    (static_cast<uint32>(temp[4*i    ])      )
                    );               
            }
            

            uint32 aa = a;
            uint32 bb = b;
            uint32 cc = c;
            uint32 dd = d;


            scramble_block(a,b,c,d,x);


            a = a + aa;
            b = b + bb;
            c = c + cc;
            d = d + dd;

        }

        if (!write_length)
        {
            uint64 temp = len*8;

            uint32 aa = a;
            uint32 bb = b;
            uint32 cc = c;
            uint32 dd = d;

            std::memset(x, 0, sizeof(x));
            x[15] = (temp>>32);
            x[14] = (temp&0xFFFFFFFF);

            scramble_block(a,b,c,d,x);


            a = a + aa;
            b = b + bb;
            c = c + cc;
            d = d + dd;

        }


        // put a, b, c, and d into output
        output[0]   = static_cast<unsigned char>((a)    &0xFF);
        output[1]   = static_cast<unsigned char>((a>>8) &0xFF);
        output[2]   = static_cast<unsigned char>((a>>16)&0xFF);
        output[3]   = static_cast<unsigned char>((a>>24)&0xFF);

        output[4]   = static_cast<unsigned char>((b)    &0xFF);
        output[5]   = static_cast<unsigned char>((b>>8) &0xFF);
        output[6]   = static_cast<unsigned char>((b>>16)&0xFF);
        output[7]   = static_cast<unsigned char>((b>>24)&0xFF);

        output[8]   = static_cast<unsigned char>((c)    &0xFF);
        output[9]   = static_cast<unsigned char>((c>>8) &0xFF);
        output[10]  = static_cast<unsigned char>((c>>16)&0xFF);
        output[11]  = static_cast<unsigned char>((c>>24)&0xFF);

        output[12]  = static_cast<unsigned char>((d)    &0xFF);
        output[13]  = static_cast<unsigned char>((d>>8) &0xFF);
        output[14]  = static_cast<unsigned char>((d>>16)&0xFF);
        output[15]  = static_cast<unsigned char>((d>>24)&0xFF);

        input.clear(std::ios::eofbit);
    }

// ----------------------------------------------------------------------------------------

}
#endif // DLIB_MD5_KERNEL_1_CPp_

