/* cputils.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/*jshint newcap: false */
(function(root, factory){
  "use strict";
  if(typeof cptable === "undefined") {
    if(typeof require !== "undefined"){
      var cpt = require('./cptable');
      if (typeof module !== 'undefined' && module.exports) module.exports = factory(cpt);
      else root.cptable = factory(cpt);
    } else throw new Error("cptable not found");
  } else cptable = factory(cptable);
}(this, function(cpt){
  "use strict";
  var magic = {
    "1200":"utf16le",
    "1201":"utf16be",
    "12000":"utf32le",
    "12001":"utf32be",
    "16969":"utf64le",
    "20127":"ascii",
    "65000":"utf7",
    "65001":"utf8"
  };

  var sbcs_cache = [874,1250,1251,1252,1253,1254,1255,1256,10000];
  var dbcs_cache = [932,936,949,950];
  var magic_cache = [65001];
  var magic_decode = {};
  var magic_encode = {};
  var cpecache = {};
  var cpdcache = {};

  if(typeof Buffer !== 'undefined') {
    var sbcs_encode = function(cp) {
      var E = cpt[cp].enc;
      var EE = new Buffer(65536);
      for(var i = 0; i != 65536;++i) EE[i] = 0;
      var keys = Object.keys(E), len = keys.length;
      for(var ee = 0, e = keys[ee]; ee != len; ++ee) {
        if(!(e = keys[ee])) continue;
        EE[e.charCodeAt(0)] = E[e];
      };
      return function(data, ofmt) {
        if(data instanceof Buffer) data = data.toString('utf8');
        var out = new Buffer(data.length), i;
        if(typeof data === 'string') {
          for(i = 0; i != data.length; ++i) out[i] = EE[data.charCodeAt(i)];
        } else {
          for(i = 0; i != data.length; ++i) out[i] = EE[data[i].charCodeAt(0)];
        }
        if(!ofmt) return out;
        if(ofmt === 'buf') return out;
        var arr = [].slice.call(out);
        if(ofmt === 'arr') return arr;
        return arr.map(function(x) { return String.fromCharCode(x); }).join("");
      };
    };
    var sbcs_decode = function(cp) {
      var D = cpt[cp].dec;
      var DD = new Buffer(131072), d=0, c;
      for(d=0;d!=D.length;++d) {
        if(!(c=D[d])) continue;
        var w = c.charCodeAt(0);
        DD[2*d] = w%256; DD[2*d+1] = w>>8;
      }
      return function(data) {
        var out = new Buffer(2*data.length), w, i=0, j;
        if(data instanceof Buffer) {
          for(i = 0; i < data.length; i++) {
            j = 2*data[i];
            out[2*i] = DD[j]; out[2*i+1] = DD[j+1];
          }
        } else if(typeof data === "string") {
          for(i = 0; i < data.length; i++) {
            j = 2*data.charCodeAt(i);
            out[2*i] = DD[j]; out[2*i+1] = DD[j+1];
          }
        } else {
          for(i = 0; i < data.length; i++) {
            j = 2*data[i];
            out[2*i] = DD[j]; out[2*i+1] = DD[j+1];
          }
        }
        return out.toString('ucs2');
      };
    };
    var dbcs_encode = function(cp) {
      var E = cpt[cp].enc;
      var EE = new Buffer(131072);
      for(var i = 0; i != 131072; ++i) EE[i] = 0;
      var keys = Object.keys(E);
      for(var ee = 0, e = keys[ee]; ee != keys.length; ++ee) {
        if(!(e = keys[ee])) continue;
        var f = e.charCodeAt(0);
        EE[2*f] = E[e] & 255; EE[2*f+1] = E[e]>>8;
      };
      return function(data, ofmt) {
        if(data instanceof Buffer) data = data.toString('utf8');
        var out = new Buffer(2*data.length), i, j, k;
        if(typeof data === 'string') {
          for(i = k = 0; i != data.length; ++i) {
            j = data.charCodeAt(i)*2;
            out[k++] = EE[j+1] || EE[j]; if(EE[j+1] > 0) out[k++] = EE[j];
          }
          out.length = k;
        } else {
          for(i = k = 0; i != data.length; i++) {
            j = data[i].charCodeAt(0)*2;
            out[k++] = EE[j+1] || EE[j]; if(EE[j+1] > 0) out[k++] = EE[j];
          }
        }
        if(!ofmt) return out;
        if(ofmt === 'buf') return out;
        var arr = [].slice.call(out);
        if(ofmt === 'arr') return arr;
        return arr.map(function(x) { return String.fromCharCode(x); }).join("");
      };
    };
    var dbcs_decode = function(cp) {
      var D = cpt[cp].dec;
      var DD = new Buffer(131072), d=0, c, w=0, j=0, dd=0, i=0;
      for(i = 0; i != 65536; ++i) { DD[2*i] = 0xFF; DD[2*i+1] = 0xFD;}
      for(d=0;d!=D.length;++d) {
        if(!(c=D[d])) continue;
        w = c.charCodeAt(0);
        j = 2*d;
        DD[j] = w%256; DD[j+1] = w>>8;
      };
      return function(data) {
        var out = new Buffer(2*data.length), w, i, j, k=0;
        if(data instanceof Buffer) {
          for(i = 0; i < data.length; i++) {
            j = 2*data[i];
            if(DD[j]===0xFF && DD[j+1]===0xFD) { j=2*(256*data[i]+data[i+1]); ++i; }
            out[k++] = DD[j]; out[k++] = DD[j+1];
          }
        } else if(typeof data === "string") {
          for(i = 0; i < data.length; i++) {
            j = 2*data.charCodeAt(i);
            if(DD[j]===0xFF && DD[j+1]===0xFD) { j=2*(256*data.charCodeAt(i)+data.charCodeAt(i+1)); ++i; }
            out[k++] = DD[j]; out[k++] = DD[j+1];
          }
        } else {
          for(i = 0; i < data.length; i++) {
            j = 2*data[i];
            if(DD[j]===0xFF && DD[j+1]===0xFD) { j=2*(256*data[i]+data[i+1]); ++i; }
            out[k++] = DD[j]; out[k++] = DD[j+1];
          }
        }
        out.length = k;
        return out.toString('ucs2');
      };
    };
    magic_decode[65001] = function(data) {
      var out = new Buffer(2*data.length), w, i, j = 1, k = 0, ww;
      for(i = 0; i < data.length; i+=j) {
        j = 1;
        if(data[i] < 128) w = data[i];
        else if(data[i] < 224) { w = (data[i]&31)*64+(data[i+1]&63); j=2; }
        else if(data[i] < 240) { w=(data[i]&15)*4096+(data[i+1]&63)*64+(data[i+2]&63); j=3; }
        else { w=(data[i]&7)*262144+(data[i+1]&63)*4096+(data[i+2]&63)*64+(data[i+3]&63); j=4; }
        if(w < 65536) { out[k++] = w&255; out[k++] = w>>8; }
        else {
          w -= 65536; ww = 0xD800 + ((w>>10)&1023); w = 0xDC00 + (w&1023);
          out[k++] = ww&255; out[k++] = ww>>8; out[k++] = w&255; out[k++] = w>>8;
        }
      }
      out.length = k;
      return out.toString('ucs2');
    };
  }

  var encache = function(cp) {
    if(typeof Buffer !== 'undefined') {
      if(cpdcache[sbcs_cache[0]]) return;
      sbcs_cache.forEach(function(s) {
        if(!cpt[s]) return;
        cpdcache[s] = sbcs_decode(s);
        cpecache[s] = sbcs_encode(s);
      });
      dbcs_cache.forEach(function(s) {
        if(!cpt[s]) return;
        cpdcache[s] = dbcs_decode(s);
        cpecache[s] = dbcs_encode(s);
      });
      magic_cache.forEach(function(s) {
        if(magic_decode[s]) cpdcache[s] = magic_decode[s];
        if(magic_encode[s]) cpecache[s] = magic_encode[s];
      });
    }
  };
  var cp_decache = function(cp) { delete cpdcache[cp]; delete cpecache[cp]; };
  var decache = function() {
    if(typeof Buffer !== 'undefined') {
      if(!cpdcache[sbcs_cache[0]]) return;
      sbcs_cache.forEach(cp_decache);
      dbcs_cache.forEach(cp_decache);
      magic_cache.forEach(cp_decache);
    }
  };
  var cache = {
    encache: encache,
    decache: decache,
    sbcs: sbcs_cache,
    dbcs: dbcs_cache
  };

  encache();

  var BM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var SetD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'(),-./:?";
  var encode = function(cp, data, ofmt) {
    var F; if((F=cpecache[cp])) return F(data, ofmt);
    if(typeof Buffer !== 'undefined' && data instanceof Buffer) data = data.toString('utf8');
    var out = typeof Buffer !== 'undefined' ? new Buffer(4*data.length) : [], w, i, j = 0, c, tt;
    var C = cpt[cp], E, M;
    if(C && (E=C.enc)) for(i = 0; i != data.length; ++i, ++j) {
      w = E[data[i]];
      out[j] = w % 256;
      if(w > 255) {
        out[j] = (w/256)|0;
        out[++j] = w%256;
      }
    }
    else if((M=magic[cp])) switch(M) {
      case "utf8":
        if(typeof Buffer !== 'undefined' && typeof data === "string") { out = new Buffer(data, M); j = out.length; break; }
        for(i = 0; i != data.length; ++i, ++j) {
          w = data[i].charCodeAt(0);
          if(w <= 0x007F) out[j] = w;
          else if(w <= 0x07FF) {
            out[j]   = 192 + (w >> 6);
            out[++j] = 128 + (w % 64);
          } else {
            out[j]   = 224 + (w >> 12);
            out[++j] = 128 + ((w >> 6) % 64);
            out[++j] = 128 + (w % 64);
          }
        }
        break;
      case "ascii":
        if(typeof Buffer !== 'undefined' && typeof data === "string") { out = new Buffer(data, M); j = out.length; break; }
        for(i = 0; i != data.length; ++i, ++j) {
          w = data[i].charCodeAt(0);
          if(w <= 0x007F) out[j] = w;
          else throw new Error("bad ascii " + w);
        }
        break;
      case "utf16le": /* TODO: surrogate pairs */
        if(typeof Buffer !== 'undefined' && typeof data === "string") { out = new Buffer(data, M); j = out.length; break; }
        for(i = 0; i != data.length; ++i) {
          w = data[i].charCodeAt(0);
          out[j++] = w % 256;
          out[j++] = w>>8;
        }
        break;
      case "utf16be":
        for(i = 0; i != data.length; ++i) {
          w = data[i].charCodeAt(0);
          out[j++] = w>>8;
          out[j++] = w % 256;
        }
        break;
      case "utf32le":
        for(i = 0; i != data.length; ++i) {
          w = data[i].charCodeAt(0);
          out[j++] = w % 256; w >>= 8;
          out[j++] = w % 256; w >>= 8;
          out[j++] = w % 256; w >>= 8;
          out[j++] = w % 256;
        }
        break;
      case "utf32be":
        for(i = 0; i != data.length; ++i) {
          w = data[i].charCodeAt(0);
          out[j+3] = w % 256; w >>= 8;
          out[j+2] = w % 256; w >>= 8;
          out[j+1] = w % 256; w >>= 8;
          out[j] = w % 256; w >>= 8;
          j+=4;
        }
        break;
      case "utf7":
        for(i = 0; i != data.length; i++) {
          c = data[i];
          if(c === "+") { out[j++] = 0x2b; out[j++] = 0x2d; continue; }
          if(SetD.indexOf(c) > -1) { out[j++] = c.charCodeAt(0); continue; }
          tt = encode(1201, c);
          out[j++] = 0x2b;
          out[j++] = BM.charCodeAt(tt[0]>>2);
          out[j++] = BM.charCodeAt(((tt[0]&0x03)<<4) + ((tt[1]||0)>>4));
          out[j++] = BM.charCodeAt(((tt[1]&0x0F)<<2) + ((tt[2]||0)>>6));
          out[j++] = 0x2d;
        }
        break;
      default: throw new Error("Unsupported magic: " + cp + " " + magic[cp]);
    }
    else throw new Error("Unrecognized CP: " + cp);
    out.length = j;
    if(typeof Buffer === 'undefined') return (ofmt == 'str') ? out.map(function(x) { return String.fromCharCode(x); }).join("") : out;
    if(!ofmt) return out;
    if(ofmt === 'buf') return out;
    var arr = [].slice.call(out);
    if(ofmt === 'arr') return arr;
    return arr.map(function(x) { return String.fromCharCode(x); }).join("");
  };
  var decode = function(cp, data) {
    var F; if((F=cpdcache[cp])) return F(data);
    var out = new Array(data.length), w, i, j = 1, k = 0;
    var C = cpt[cp], D, M;
    if(C && (D=C.dec)) {
      if(typeof data === "string") data = data.split("").map(function(x){ return x.charCodeAt(0); });
      for(i = 0; i < data.length; i+=j) {
        j = 2;
        w = D[data[i]*256 + data[i+1]];
        if(!w) {
          j = 1;
          w = D[data[i]];
        }
        if(!w) throw new Error('Unrecognized code: ' + data[i] + ' ' + data[i+j-1] + ' ' + i + ' ' + j + ' ' + D[data[i]]);
        out[k++] = w;
      }
    }
    else if((M=magic[cp])) switch(M) {
      case "utf8":
        for(i = 0; i < data.length; i+=j) {
          j = 1;
          if(data[i] < 128) w = data[i];
          else if(data[i] < 224) { w = (data[i]&31)*64+(data[i+1]&63); j=2; }
          else { w=(data[i]&15)*4096+(data[i+1]&63)*64+(data[i+2]&63); j=3; }
          out[k++] = String.fromCharCode(w);
        }
        break;
      case "ascii":
        if(typeof Buffer !== 'undefined' && data instanceof Buffer) return data.toString(M);
        for(i = 0; i < data.length; i++) out[i] = String.fromCharCode(data[i]);
        k = data.length; break;
      case "utf16le":
        if(typeof Buffer !== 'undefined' && data instanceof Buffer) return data.toString(M);
        j = 2;
        for(i = 0; i < data.length; i+=j) {
          out[k++] = String.fromCharCode(256*data[i+1] + data[i]);
        }
        break;
      case "utf16be":
        j = 2;
        for(i = 0; i < data.length; i+=j) {
          out[k++] = String.fromCharCode(256*data[i] + data[i+1]);
        }
        break;
      case "utf32le":
        j = 4;
        for(i = 0; i < data.length; i+=j) {
          out[k++] = String.fromCharCode((data[i+3]<<24) + (data[i+2]<<16) + (data[i+1]<<8) + (data[i]));
        }
        break;
      case "utf32be":
        j = 4;
        for(i = 0; i < data.length; i+=j) {
          out[k++] = String.fromCharCode((data[i]<<24) + (data[i+1]<<16) + (data[i+2]<<8) + (data[i+3]));
        }
        break;
      case "utf7":
        for(i = 0; i < data.length; i+=j) {
          if(data[i] !== 0x2b) { j=1; out[k++] = String.fromCharCode(data[i]); continue; }
          j=1;
          if(data[i+1] === 0x2d) { j = 2; out[k++] = "+"; continue; }
          while(String.fromCharCode(data[i+j]).match(/[A-Za-z0-9+\/]/)) j++;
          var dash = 0;
          if(data[i+j] === 0x2d) { ++j; dash=1; }
          var tt = [];
          var o64;
          var c1, c2, c3;
          var e1, e2, e3, e4;
          for(var l = 1; l < j - dash;) {
            e1 = BM.indexOf(String.fromCharCode(data[i+l++]));
            e2 = BM.indexOf(String.fromCharCode(data[i+l++]));
            c1 = e1 << 2 | e2 >> 4;
            tt.push(c1);
            e3 = BM.indexOf(String.fromCharCode(data[i+l++]));
            if(e3 === -1) break;
            c2 = (e2 & 15) << 4 | e3 >> 2;
            tt.push(c2);
            e4 = BM.indexOf(String.fromCharCode(data[i+l++]));
            if(e4 === -1) break;
            c3 = (e3 & 3) << 6 | e4;
            if(e4 != 64) tt.push(c3);
          }
          if(tt.length % 2 == 1) tt.length--;
          o64 = decode(1201, tt);
          for(l = 0; l < o64.length; ++l) out[k++] = o64[l];
        }
        break;
      default: throw new Error("Unsupported magic: " + cp + " " + magic[cp]);
    }
    else throw new Error("Unrecognized CP: " + cp);
    out.length = k;
    return out.join("");
  };
  var hascp = function(cp) { return cpt[cp] || magic[cp]; };
  cpt.utils = { decode: decode, encode: encode, hascp: hascp, magic: magic, cache:cache };
  return cpt;
}));
