/**
 * Ytils JS Framework
 * hash
 *
 * Ytils WebToolBox.
 *
 * Copyright (c) 2011 Kim Schneider, SIT - Schneider Internet Technologies.
 * The Ytils framework is a collection of PHP- and JavaScript utility- and widget-classes written by Kim Schneider.
 *
 * The Ytils WebToolBox is published under the GPL v3-licence. See LICENCE.txt for the licence-text.
 * The official source of the Ytils project is http://www.ytils.com.
 * The official website of SIT - Schneider Internet Technologies is http://www.schneidersit.de.
 *
 * Kiel, Germany, 2011-05-17.
 *
 * @category  YTILS
 * @package   util.js.ytils.com
 * @copyright Copyright (c) 2010 Schneider IT-Services (http://www.schneider.ws)
 * @author    Kim Schneider
 * @license   http://ytils.schneider.ws/licence/
 * @version   0.1.2.0
 *
 * Notice:
 * JSLint will fail with a "weird relation" on row 153.
 * This is the original authors' intention and will not be fixed.
 *
 * Notice:
 * This is an improvement of the md5-implementation for Javascript by Henri
 * Torgemane and Ralf Mieke.
 * Please notice the original copyright information:
 *
 * > md5.js 1.0b 27/06/96
 * >
 * > Javascript implementation of the RSA Data Security, Inc. MD5
 * > Message-Digest Algorithm.
 * >
 * > Copyright (c) 1996 Henri Torgemane. All Rights Reserved.
 * >
 * > Permission to use, copy, modify, and distribute this software
 * > and its documentation for any purposes and without
 * > fee is hereby granted provided that this copyright notice
 * > appears in all copies.
 * >
 * > Of course, this soft is provided "as is" without express or implied
 * > warranty of any kind.
 * >
 * > Modified with german comments and some information about collisions.
 * > (Ralf Mieke, ralf@miekenet.de, http://mieke.home.pages.de)
 *
 * Notice:
 * The additional comments by Ralf Mieke have been translated to
 * german language.
 *
 * Changes on original md5.js 1.0b:
 * - Changed filename to hash.js.
 * - Refactored old main function MD5() to md5() as part of namespace YTILS.
 * - Removed leading capital letters from some functions that have no class character.
 * - Much better code-indention and loosened code, based on a tab represented by
 *   two white-spaces.
 * - Avoids quite dangerous function names with single characters. New names make use
 *   of intercaps.
 * - Followed the rules of Douglas Crockford's JSLint to ensure that there is no use of
 *   global variables. For example, the usage of the old MD5() would have cause
 *   fatal intransparent errors when using MD5() within a loop using i as iteration
 *   variable.
 *
 *   In this solution you will find now globals compromising the rest of your language
 *   code or used libraries except for the one named YTILS.
 *
 *   This file fulfills the basic rules of JSLint. The strict validation ("good parts")
 *   for this is not applicable as we need the bitwise operators. Furthermore this file
 *   file is not compliant to the code-indention rules of JSLint as I think, that four
 *   spaces wide tabs are not state of the art.
 *
 *   See: http://www.jslint.com
 *
 * Notice:
 * If you want to be sure that the new md5() works correctly, you can download a testing
 * environment that compares thousands of random strings transformed to md5-hashes and
 * compares it to the results of PHP's function md5().
 *
 * Example:
 * <code>
 * var myHash = YTILS.md5("Hello World!");
 * </code>
 */

/*global YTILS */
/*jslint browser: true */
/*jslint strict: true */
(function(){

"use strict";

if (typeof YTILS.util === "undefined" || !YTILS.util)
{
  YTILS.util = {};
}

YTILS.util.hash = {};
YTILS.util.hash.md5 = function(str)
{
  function Arr(n)
  {
    var i;
    for (i = 0; i < n; i = i + 1)
    {
      this[i] = 0;
    }

    this.length = n;
  }

  /**
   * Notice:
   *
   * Some basic operators and types must be rewritten
   * by the use of special functions here because javascript has
   * some internal errors.
   *
   * Although they are, of course, not as fast as the originals,
   * they do their work.
   */

  // New pseudo-types and -operators.
  function integer(n)
  {
    return n % (0xffffffff + 1);
  }

  function shr(a, b)
  {
    a = integer(a);
    b = integer(b);

    if (a - 0x80000000 >= 0)
    {
      a = a % 0x80000000;
      a >>= b;
      a += 0x40000000 >> (b - 1);
    }
    else
    {
      a >>= b;
    }

    return a;
  }

  function shl1(a)
  {
    a = a % 0x80000000;

    if (a & 0x40000000 === 0x40000000)
    {
      a -= 0x40000000;
      a *= 2;
      a += 0x80000000;
    }
    else
    {
      a *= 2;
    }

    return a;
  }

  function shl(a, b)
  {
    a = integer(a);
    b = integer(b);

    var i;
    for (i = 0; i < b; i = i + 1)
    {
      a = shl1(a);
    }

    return a;
  }

  function and(a, b)
  {
    a = integer(a);
    b = integer(b);

    var t1 = (a - 0x80000000),
        t2 = (b - 0x80000000);

    if (t1 >= 0)
    {
      if (t2 >= 0)
      {
        return ((t1 & t2) + 0x80000000);
      }
      else
      {
        return (t1 & b);
      }
    }
    else
    {
      if (t2 >= 0)
      {
        return (a & t2);
      }
      else
      {
        return (a & b);
      }
    }
  }

  function or(a, b)
  {
    a = integer(a);
    b = integer(b);

    var t1=(a-0x80000000),
        t2=(b-0x80000000);

    if (t1 >= 0)
    {
      if (t2 >= 0)
      {
        return ((t1 | t2) + 0x80000000);
      }
      else
      {
        return ((t1 | b) + 0x80000000);
      }
    }
    else
    {
      if (t2 >= 0)
      {
        return ((a | t2) + 0x80000000);
      }
      else
      {
        return (a | b);
      }
    }
  }

  function xor(a, b)
  {
    a = integer(a);
    b = integer(b);

    var t1 = (a-0x80000000),
        t2 = (b-0x80000000);

    if (t1 >= 0)
    {
      if (t2 >= 0)
      {
        return (t1 ^ t2);
      }
      else
      {
        return ((t1 ^ b) + 0x80000000);
      }
    }
    else
    {
      if (t2 >= 0)
      {
        return ((a ^ t2) + 0x80000000);
      }
      else
      {
        return (a ^ b);
      }
    }
  }

  function not(a)
  {
    a = integer(a);
    return (0xffffffff - a);
  }

  // Beginning the algorism:

  var state = new Arr(4),
      count = new Arr(2),
      buffer = new Arr(64),
      transformBuffer = new Arr(16),
      digestBits = new Arr(16),
      s11 = 7,
      s12 = 12,
      s13 = 17,
      s14 = 22,
      s21 = 5,
      s22 = 9,
      s23 = 14,
      s24 = 20,
      s31 = 4,
      s32 = 11,
      s33 = 16,
      s34 = 23,
      s41 = 6,
      s42 = 10,
      s43 = 15,
      s44 = 21,
      ascii = "01234567890123456789012345678901" +
              " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
              "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

  count[0] = 0;
  count[1] = 0;

  function fF(x, y, z)
  {
    return or(and(x, y), and(not(x), z));
  }

  function fG(x, y, z)
  {
    return or(and(x, z), and(y, not(z)));
  }

  function fH(x, y, z)
  {
    return xor(xor(x, y), z);
  }

  function fI(x, y, z)
  {
    return xor(y, or(x, not(z)));
  }

  function rotateLeft(a, n)
  {
    return or(shl(a, n), (shr(a, (32 - n))));
  }

  function fFf(a, b, c, d, x, s, ac)
  {
    a = a + fF(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a = a + b;
    return a;
  }

  function fGg(a, b, c, d, x, s, ac)
  {
    a = a + fG(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a = a + b;
    return a;
  }

  function fHh(a, b, c, d, x, s, ac)
  {
    a = a + fH(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a = a + b;
    return a;
  }

  function fIi(a, b, c, d, x, s, ac)
  {
    a = a + fI(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a = a + b;
    return a;
  }

  function transform(buf, offset)
  {
    var a = 0,
        b = 0,
        c = 0,
        d = 0,
        x = transformBuffer,
        i,
        j;

    a = state[0];
    b = state[1];
    c = state[2];
    d = state[3];

    for (i = 0; i < 16; i = i + 1)
    {
      x[i] = and(buf[i * 4 + offset], 0xff);
      for (j = 1; j < 4; j = j + 1)
      {
        x[i] += shl(and(buf[i * 4 + j + offset], 0xff), j * 8);
      }
    }

    // Round 1
    a = fFf(a, b, c, d, x[0], s11, 0xd76aa478);
    d = fFf(d, a, b, c, x[1], s12, 0xe8c7b756);
    c = fFf(c, d, a, b, x[2], s13, 0x242070db);
    b = fFf(b, c, d, a, x[3], s14, 0xc1bdceee);
    a = fFf(a, b, c, d, x[4], s11, 0xf57c0faf);
    d = fFf(d, a, b, c, x[5], s12, 0x4787c62a);
    c = fFf(c, d, a, b, x[6], s13, 0xa8304613);
    b = fFf(b, c, d, a, x[7], s14, 0xfd469501);
    a = fFf(a, b, c, d, x[8], s11, 0x698098d8);
    d = fFf(d, a, b, c, x[9], s12, 0x8b44f7af);
    c = fFf(c, d, a, b, x[10], s13, 0xffff5bb1);
    b = fFf(b, c, d, a, x[11], s14, 0x895cd7be);
    a = fFf(a, b, c, d, x[12], s11, 0x6b901122);
    d = fFf(d, a, b, c, x[13], s12, 0xfd987193);
    c = fFf(c, d, a, b, x[14], s13, 0xa679438e);
    b = fFf(b, c, d, a, x[15], s14, 0x49b40821);

    // Round 2
    a = fGg(a, b, c, d, x[1], s21, 0xf61e2562);
    d = fGg(d, a, b, c, x[6], s22, 0xc040b340);
    c = fGg(c, d, a, b, x[11], s23, 0x265e5a51);
    b = fGg(b, c, d, a, x[0], s24, 0xe9b6c7aa);
    a = fGg(a, b, c, d, x[5], s21, 0xd62f105d);
    d = fGg(d, a, b, c, x[10], s22, 0x2441453);
    c = fGg(c, d, a, b, x[15], s23, 0xd8a1e681);
    b = fGg(b, c, d, a, x[4], s24, 0xe7d3fbc8);
    a = fGg(a, b, c, d, x[9], s21, 0x21e1cde6);
    d = fGg(d, a, b, c, x[14], s22, 0xc33707d6);
    c = fGg(c, d, a, b, x[3], s23, 0xf4d50d87);
    b = fGg(b, c, d, a, x[8], s24, 0x455a14ed);
    a = fGg(a, b, c, d, x[13], s21, 0xa9e3e905);
    d = fGg(d, a, b, c, x[2], s22, 0xfcefa3f8);
    c = fGg(c, d, a, b, x[7], s23, 0x676f02d9);
    b = fGg(b, c, d, a, x[12], s24, 0x8d2a4c8a);

    // Round 3
    a = fHh(a, b, c, d, x[5], s31, 0xfffa3942);
    d = fHh(d, a, b, c, x[8], s32, 0x8771f681);
    c = fHh(c, d, a, b, x[11], s33, 0x6d9d6122);
    b = fHh(b, c, d, a, x[14], s34, 0xfde5380c);
    a = fHh(a, b, c, d, x[1], s31, 0xa4beea44);
    d = fHh(d, a, b, c, x[4], s32, 0x4bdecfa9);
    c = fHh(c, d, a, b, x[7], s33, 0xf6bb4b60);
    b = fHh(b, c, d, a, x[10], s34, 0xbebfbc70);
    a = fHh(a, b, c, d, x[13], s31, 0x289b7ec6);
    d = fHh(d, a, b, c, x[0], s32, 0xeaa127fa);
    c = fHh(c, d, a, b, x[3], s33, 0xd4ef3085);
    b = fHh(b, c, d, a, x[6], s34,  0x4881d05);
    a = fHh(a, b, c, d, x[9], s31, 0xd9d4d039);
    d = fHh(d, a, b, c, x[12], s32, 0xe6db99e5);
    c = fHh(c, d, a, b, x[15], s33, 0x1fa27cf8);
    b = fHh(b, c, d, a, x[2], s34, 0xc4ac5665);

    // Round 4
    a = fIi(a, b, c, d, x[0], s41, 0xf4292244);
    d = fIi(d, a, b, c, x[7], s42, 0x432aff97);
    c = fIi(c, d, a, b, x[14], s43, 0xab9423a7);
    b = fIi(b, c, d, a, x[5], s44, 0xfc93a039);
    a = fIi(a, b, c, d, x[12], s41, 0x655b59c3);
    d = fIi(d, a, b, c, x[3], s42, 0x8f0ccc92);
    c = fIi(c, d, a, b, x[10], s43, 0xffeff47d);
    b = fIi(b, c, d, a, x[1], s44, 0x85845dd1);
    a = fIi(a, b, c, d, x[8], s41, 0x6fa87e4f);
    d = fIi(d, a, b, c, x[15], s42, 0xfe2ce6e0);
    c = fIi(c, d, a, b, x[6], s43, 0xa3014314);
    b = fIi(b, c, d, a, x[13], s44, 0x4e0811a1);
    a = fIi(a, b, c, d, x[4], s41, 0xf7537e82);
    d = fIi(d, a, b, c, x[11], s42, 0xbd3af235);
    c = fIi(c, d, a, b, x[2], s43, 0x2ad7d2bb);
    b = fIi(b, c, d, a, x[9], s44, 0xeb86d391);

    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;
  }

  /**
   * Notice:
   * With the initialization of Dobbertin...
   *
   * <code>
   * state[0] = 0x12ac2375;
   * state[1] = 0x3b341042;
   * state[2] = 0x5f62b97c;
   * state[3] = 0x4ba763ed;
   * </code>
   *
   * ... there will be a collision:
   * <code>
   * begin 644 Message1
   * M7MH=JO6_>MG!X?!51$)W,CXV!A"=(!AR71,<X`Y-IIT9^Z&8L$2N'Y*Y:R.;
   * 39GIK9>TF$W()/MEHR%C4:G1R:Q"=
   * `
   * end
   *
   * begin 644 Message2
   * M7MH=JO6_>MG!X?!51$)W,CXV!A"=(!AR71,<X`Y-IIT9^Z&8L$2N'Y*Y:R.;
   * 39GIK9>TF$W()/MEHREC4:G1R:Q"=
   * `
   * end
   * </code>
   */

   function init()
   {
     count[0] = 0;
     count[1] = 0;
     state[0] = 0x67452301;
     state[1] = 0xefcdab89;
     state[2] = 0x98badcfe;
     state[3] = 0x10325476;

     var i;
     for (i = 0; i < digestBits.length; i = i + 1)
     {
       digestBits[i] = 0;
     }
   }

   function update(b)
   {
     var index;

     index = and(shr(count[0], 3), 0x3f);

     if (count[0]<0xffffffff-7)
     {
       count[0] += 8;
     }
     else
     {
	     count[1] += 1;
	     count[0] -= 0xffffffff + 1;
	     count[0] += 8;
     }

     buffer[index] = and(b, 0xff);
     if (index >= 63)
     {
       transform(buffer, 0);
     }
   }

   function finish()
   {
     var bits = new Arr(8),
         padding,
         i = 0,
         j = 0,
         index = 0,
         padLen = 0;

     for (i = 0; i < 4; i = i + 1)
     {
       bits[i] = and(shr(count[0], (i * 8)), 0xff);
     }

     for (i = 0; i < 4; i =  i + 1)
     {
       bits[i + 4] = and(shr(count[1], (i * 8)), 0xff);
     }

     index = and(shr(count[0], 3), 0x3f);
     padLen = (index < 56) ? (56 - index) : (120 - index);
     padding = new Arr(64);
     padding[0] = 0x80;

     for (i = 0;i < padLen; i = i + 1)
     {
       update(padding[i]);
     }

     for (i = 0; i < 8; i = i + 1)
     {
       update(bits[i]);
     }

     for (i = 0; i < 4; i = i + 1)
     {
       for (j = 0; j < 4; j = j + 1)
       {
         digestBits[i * 4 + j] = and(shr(state[i], (j * 8)), 0xff);
       }
     }
   }

  // End of the algorithm.

  function hexa(n)
  {
    var hexa_h = "0123456789abcdef",
        hexa_c = "",
        hexa_m = n,
        hexa_i;

    for (hexa_i = 0; hexa_i < 8; hexa_i = hexa_i + 1)
    {
      hexa_c = hexa_h.charAt(Math.abs(hexa_m) % 16) + hexa_c;
      hexa_m = Math.floor(hexa_m / 16);
    }

    return hexa_c;
  }

  /**
   * Creates a md5-hash from a string-message.
   *
   * @access public
   * @method YTILS.Hash.md5()
   * @return String
   */
  function md5(message)
  {
   var l,
       s,
       k,
       ka,
       kb,
       kc,
       kd,
       i;

   init();

   for (k = 0; k < message.length; k = k + 1)
   {
     l = message.charAt(k);
     update(ascii.lastIndexOf(l));
   }

   finish();
   ka = 0;
   kb = 0;
   kc = 0;
   kd = 0;

   for (i = 0; i < 4; i = i + 1)
   {
     ka += shl(digestBits[15 - i], (i * 8));
   }

   for (i = 4; i < 8; i = i + 1)
   {
     kb += shl(digestBits[15 - i], ((i - 4) * 8));
   }

   for (i = 8; i < 12; i = i + 1)
   {
     kc += shl(digestBits[15 - i], ((i - 8) * 8));
   }

   for (i = 12; i < 16; i = i + 1)
   {
     kd += shl(digestBits[15 - i], ((i - 12) * 8));
   }

   s = hexa(kd) + hexa(kc) + hexa(kb) + hexa(ka);

   return s;
  }

  return md5(str);
};

}());
