/*
 * Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1,
 * as defined in FIPS PUB 180-1, tuned for high performance with large inputs.
 * (http://github.com/srijs/rusha)
 *
 * Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5).
 *
 * Copyright (c) 2013 Sam Rijs (http://awesam.de).
 * Released under the terms of the MIT license as follows:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

(function () {

  var util = {
    getDataType: function (data) {
      if (typeof data === 'string') {
        return 'string';
      }
      if (data instanceof Array) {
        return 'array';
      }
      if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) {
        return 'buffer';
      }
      if (data instanceof ArrayBuffer) {
        return 'arraybuffer';
      }
      if (data.buffer instanceof ArrayBuffer) {
        return 'view';
      }
      if (data instanceof Blob) {
        return 'blob';
      }
      throw new Error('Unsupported data type.');
    }
  };

  // The Rusha object is a wrapper around the low-level RushaCore.
  // It provides means of converting different inputs to the
  // format accepted by RushaCore as well as other utility methods.
  function Rusha (chunkSize) {
    "use strict";

    // Private object structure.
    var self = {fill: 0};

    // Calculate the length of buffer that the sha1 routine uses
    // including the padding.
    var padlen = function (len) {
      for (len += 9; len % 64 > 0; len += 1);
      return len;
    };

    var padZeroes = function (bin, len) {
      for (var i = len >> 2; i < bin.length; i++) bin[i] = 0;
    };

    var padData = function (bin, chunkLen, msgLen) {
      bin[chunkLen>>2] |= 0x80 << (24 - (chunkLen % 4 << 3));
      bin[(((chunkLen >> 2) + 2) & ~0x0f) + 14] = msgLen >> 29;
      bin[(((chunkLen >> 2) + 2) & ~0x0f) + 15] = msgLen << 3;
    };

    // Convert a binary string and write it to the heap.
    // A binary string is expected to only contain char codes < 256.
    var convStr = function (H8, H32, start, len, off) {
      var str = this, i, om = off % 4, lm = len % 4, j = len - lm;
      if (j > 0) {
        switch (om) {
          case 0: H8[off+3|0] = str.charCodeAt(start);
          case 1: H8[off+2|0] = str.charCodeAt(start+1);
          case 2: H8[off+1|0] = str.charCodeAt(start+2);
          case 3: H8[off|0]   = str.charCodeAt(start+3);
        }
      }
      for (i = om; i < j; i = i + 4 |0) {
        H32[off+i>>2] = str.charCodeAt(start+i)   << 24 |
                        str.charCodeAt(start+i+1) << 16 |
                        str.charCodeAt(start+i+2) <<  8 |
                        str.charCodeAt(start+i+3);
      }
      switch (lm) {
        case 3: H8[off+j+1|0] = str.charCodeAt(start+j+2);
        case 2: H8[off+j+2|0] = str.charCodeAt(start+j+1);
        case 1: H8[off+j+3|0] = str.charCodeAt(start+j);
      }
    };

    // Convert a buffer or array and write it to the heap.
    // The buffer or array is expected to only contain elements < 256.
    var convBuf = function (H8, H32, start, len, off) {
      var buf = this, i, om = off % 4, lm = len % 4, j = len - lm;
      if (j > 0) {
        switch (om) {
          case 0: H8[off+3|0] = buf[start];
          case 1: H8[off+2|0] = buf[start+1];
          case 2: H8[off+1|0] = buf[start+2];
          case 3: H8[off|0]   = buf[start+3];
        }
      }
      for (i = 4 - om; i < j; i = i += 4 |0) {
        H32[off+i>>2] = buf[start+i]   << 24 |
                        buf[start+i+1] << 16 |
                        buf[start+i+2] <<  8 |
                        buf[start+i+3];
      }
      switch (lm) {
        case 3: H8[off+j+1|0] = buf[start+j+2];
        case 2: H8[off+j+2|0] = buf[start+j+1];
        case 1: H8[off+j+3|0] = buf[start+j];
      }
    };

    var convBlob = function (H8, H32, start, len, off) {
      var blob = this, i, om = off % 4, lm = len % 4, j = len - lm;
      var buf = new Uint8Array(
        reader.readAsArrayBuffer(blob.slice(start, start+len))
      );
      if (j > 0) {
        switch (om) {
          case 0: H8[off + 3 | 0] = buf[0];
          case 1: H8[off + 2 | 0] = buf[1];
          case 2: H8[off + 1 | 0] = buf[2];
          case 3: H8[off | 0] = buf[3];
        }
      }
      for (i = 4 - om; i < j; i = i += 4 | 0) {
        H32[off + i >> 2] = buf[i] << 24 |
                            buf[i + 1] << 16 |
                            buf[i + 2] << 8 |
                            buf[i + 3];
      }
      switch (lm) {
        case 3: H8[off + j + 1 | 0] = buf[j + 2];
        case 2: H8[off + j + 2 | 0] = buf[j + 1];
        case 1: H8[off + j + 3 | 0] = buf[j];
      }
    };

    var convFn = function (data) {
      switch (util.getDataType(data)) {
        case 'string': return convStr.bind(data);
        case 'array': return convBuf.bind(data);
        case 'buffer': return convBuf.bind(data);
        case 'arraybuffer': return convBuf.bind(new Uint8Array(data));
        case 'view': return convBuf.bind(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
        case 'blob': return convBlob.bind(data);
      }
    };

    var slice = function (data, offset) {
      switch (util.getDataType(data)) {
        case 'string': return data.slice(offset);
        case 'array': return data.slice(offset);
        case 'buffer': return data.slice(offset);
        case 'arraybuffer': return data.slice(offset);
        case 'view': return data.buffer.slice(offset);
      }
    };

    // Convert an ArrayBuffer into its hexadecimal string representation.
    var hex = function (arrayBuffer) {
      var i, x, hex_tab = "0123456789abcdef", res = [], binarray = new Uint8Array(arrayBuffer);
      for (i = 0; i < binarray.length; i++) {
        x = binarray[i];
        res[i] = hex_tab.charAt((x >>  4) & 0xF) +
                 hex_tab.charAt((x >>  0) & 0xF);
      }
      return res.join('');
    };

    var ceilHeapSize = function (v) {
      // The asm.js spec says:
      // The heap object's byteLength must be either
      // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
      // Also, byteLengths smaller than 2^16 are deprecated.
      var p;
      // If v is smaller than 2^16, the smallest possible solution
      // is 2^16.
      if (v <= 65536) return 65536;
      // If v < 2^24, we round up to 2^n,
      // otherwise we round up to 2^24 * n.
      if (v < 16777216) {
        for (p = 1; p < v; p = p << 1);
      } else {
        for (p = 16777216; p < v; p += 16777216);
      }
      return p;
    };

    // Initialize the internal data structures to a new capacity.
    var init = function (size) {
      if (size % 64 > 0) {
        throw new Error('Chunk size must be a multiple of 128 bit');
      }
      self.maxChunkLen = size;
      self.padMaxChunkLen = padlen(size);
      // The size of the heap is the sum of:
      // 1. The padded input message size
      // 2. The extended space the algorithm needs (320 byte)
      // 3. The 160 bit state the algoritm uses
      self.heap     = new ArrayBuffer(ceilHeapSize(self.padMaxChunkLen + 320 + 20));
      self.h32      = new Int32Array(self.heap);
      self.h8       = new Int8Array(self.heap);
      self.core     = new Rusha._core({Int32Array: Int32Array, DataView: DataView}, {}, self.heap);
      self.buffer   = null;
    };

    // Iinitializethe datastructures according
    // to a chunk siyze.
    init(chunkSize || 64 * 1024);

    var initState = function (heap, padMsgLen) {
      var io  = new Int32Array(heap, padMsgLen + 320, 5);
      io[0] =  1732584193;
      io[1] =  -271733879;
      io[2] = -1732584194;
      io[3] =   271733878;
      io[4] = -1009589776;
    };

    var padChunk = function (chunkLen, msgLen) {
      var padChunkLen = padlen(chunkLen);
      var view = new Int32Array(self.heap, 0, padChunkLen >> 2);
      padZeroes(view, chunkLen);
      padData(view, chunkLen, msgLen);
      return padChunkLen;
    };

    // Write data to the heap.
    var write = function (data, chunkOffset, chunkLen) {
      convFn(data)(self.h8, self.h32, chunkOffset, chunkLen, 0);
    };

    // Initialize and call the RushaCore,
    // assuming an input buffer of length len * 4.
    var coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) {
      var padChunkLen = chunkLen;
      if (finalize) {
        padChunkLen = padChunk(chunkLen, msgLen);
      }
      write(data, chunkOffset, chunkLen);
      self.core.hash(padChunkLen, self.padMaxChunkLen);
    };

    var getRawDigest = function (heap, padMaxChunkLen) {
      var io  = new Int32Array(heap, padMaxChunkLen + 320, 5);
      var out = new Int32Array(5);
      var arr = new DataView(out.buffer);
      arr.setInt32(0,  io[0], false);
      arr.setInt32(4,  io[1], false);
      arr.setInt32(8,  io[2], false);
      arr.setInt32(12, io[3], false);
      arr.setInt32(16, io[4], false);
      return out;
    };

    // Calculate the hash digest as an array of 5 32bit integers.
    var rawDigest = this.rawDigest = function (str) {
      var msgLen = str.byteLength || str.length || str.size || 0;
      initState(self.heap, self.padMaxChunkLen);
      var chunkOffset = 0, chunkLen = self.maxChunkLen, last;
      for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) {
        coreCall(str, chunkOffset, chunkLen, msgLen, false);
      }
      coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true);
      return getRawDigest(self.heap, self.padMaxChunkLen);
    };

    // The digest and digestFrom* interface returns the hash digest
    // as a hex string.
    this.digest = this.digestFromString =
    this.digestFromBuffer = this.digestFromArrayBuffer =
    function (str) {
      return hex(rawDigest(str).buffer);
    };

  };

  macro rol1  { rule { ($v:expr) } => { ($v <<  1 | $v >>> 31) } }
  macro rol5  { rule { ($v:expr) } => { ($v <<  5 | $v >>> 27) } }
  macro rol30 { rule { ($v:expr) } => { ($v << 30 | $v >>>  2) } }

  macro extended {
    rule { ($H, $j:expr) } => {
      rol1($H[$j-12>>2] ^ $H[$j-32>>2] ^ $H[$j-56>>2] ^ $H[$j-64>>2])
    }
  }

  macro F0 { rule { ($b,$c,$d) } => { ($b & $c | ~$b & $d) } }
  macro F1 { rule { ($b,$c,$d) } => { ($b ^ $c ^ $d) }}
  macro F2 { rule { ($b,$c,$d) } => { ($b & $c | $b & $d | $c & $d) }}

  macro swap {
    rule { ($y0, $y1, $y2, $y3, $y4, $t0) } => {
      $y4 = $y3;
      $y3 = $y2;
      $y2 = rol30($y1);
      $y1 = $y0;
      $y0 = $t0;
    }
  }

  macro roundL { rule { ($y0, $f:expr) } => { (rol5($y0) + $f |0) } }
  macro roundR { rule { ($y4, $t1) }     => { ($t1 + $y4 |0) } }

  // The low-level RushCore module provides the heart of Rusha,
  // a high-speed sha1 implementation working on an Int32Array heap.
  // At first glance, the implementation seems complicated, however
  // with the SHA1 spec at hand, it is obvious this almost a textbook
  // implementation that has a few functions hand-inlined and a few loops
  // hand-unrolled.
  Rusha._core = function RushaCore (stdlib, foreign, heap) {
    "use asm";

    var H = new stdlib.Int32Array(heap);

    function hash (k, x) { // k in bytes

      k = k|0;
      x = x|0;
      var i = 0, j = 0,
          y0 = 0, z0 = 0, y1 = 0, z1 = 0,
          y2 = 0, z2 = 0, y3 = 0, z3 = 0,
          y4 = 0, z4 = 0, t0 = 0, t1 = 0;

      y0 = H[x+320>>2]|0;
      y1 = H[x+324>>2]|0;
      y2 = H[x+328>>2]|0;
      y3 = H[x+332>>2]|0;
      y4 = H[x+336>>2]|0;

      for (i = 0; (i|0) < (k|0); i = i + 64 |0) {

        z0 = y0;
        z1 = y1;
        z2 = y2;
        z3 = y3;
        z4 = y4;

        for (j = 0; (j|0) < 64; j = j + 4 |0) {
          t1 = H[i+j>>2]|0;
          t0 = roundL(y0, F0(y1, y2, y3)) + (roundR(y4, t1) + 1518500249 |0) |0;
          swap(y0, y1, y2, y3, y4, t0)
          H[k+j>>2] = t1;
        }

        for (j = k + 64 |0; (j|0) < (k + 80 |0); j = j + 4 |0) {
          t1 = extended(H, j);
          t0 = roundL(y0, F0(y1, y2, y3)) + (roundR(y4, t1) + 1518500249 |0) |0;
          swap(y0, y1, y2, y3, y4, t0)
          H[j>>2] = t1;
        }

        for (j = k + 80 |0; (j|0) < (k + 160 |0); j = j + 4 |0) {
          t1 = extended(H, j);
          t0 = roundL(y0, F1(y1, y2, y3)) + (roundR(y4, t1) + 1859775393 |0) |0;
          swap(y0, y1, y2, y3, y4, t0)
          H[j>>2] = t1;
        }

        for (j = k + 160 |0; (j|0) < (k + 240 |0); j = j + 4 |0) {
          t1 = extended(H, j);
          t0 = roundL(y0, F2(y1, y2, y3)) + (roundR(y4, t1) - 1894007588 |0) |0;
          swap(y0, y1, y2, y3, y4, t0)
          H[j>>2] = t1;
        }

        for (j = k + 240 |0; (j|0) < (k + 320 |0); j = j + 4 |0) {
          t1 = extended(H, j);
          t0 = roundL(y0, F1(y1, y2, y3)) + (roundR(y4, t1) - 899497514 |0) |0;
          swap(y0, y1, y2, y3, y4, t0)
          H[j>>2] = t1;
        }

        y0 = y0 + z0 |0;
        y1 = y1 + z1 |0;
        y2 = y2 + z2 |0;
        y3 = y3 + z3 |0;
        y4 = y4 + z4 |0;

      }

      H[x+320>>2] = y0;
      H[x+324>>2] = y1;
      H[x+328>>2] = y2;
      H[x+332>>2] = y3;
      H[x+336>>2] = y4;

    }

    return {hash: hash};

  }

  // If we'e running in Node.JS, export a module.
  if (typeof module !== 'undefined') {
    module.exports = Rusha;
  }

  // If we're running in a DOM context, export
  // the Rusha object to toplevel.
  else if (typeof window !== 'undefined') {
    window.Rusha = Rusha;
  }

  // If we're running in a webworker, accept
  // messages containing a jobid and a buffer
  // or blob object, and return the hash result.
  if (typeof FileReaderSync !== 'undefined') {
    var reader = new FileReaderSync(),
        hasher = new Rusha(4 * 1024 * 1024);
    self.onmessage = function onMessage (event) {
      var hash, data = event.data.data;
      try {
        hash = hasher.digest(data);
        self.postMessage({id: event.data.id, hash: hash});
      } catch (e) {
        self.postMessage({id: event.data.id, error: e.name});
      }
    };
  }

})();
