
// 1. TextDecoder -
// 2. TextDecoder's stream option (Firefox 38 + - how to detect ?) -
// 3. Promise.prototype.finally -
// 4. fetch +
// 5. Response.prototype.body +
// 6. ReadableStream.prototype.getReader (no Firefox support yet) +
// 7. AbortController (Firefox 57) +

var s = "";
if (window.Uint8Array != null && window.TextDecoder != null) {
  var b = function (byte) {
    var x = new Uint8Array(1);
    x[0] = byte;
    return x;
  };
  var textDecoder = new TextDecoder();
  var chunk = textDecoder.decode(b(208), {stream: true}) + textDecoder.decode(b(176), {stream: true});
  s = chunk;
};

console.log('TextDecoder', typeof window.TextDecoder);
console.log('TextDecoder+stream', chunk === 'а');
console.log('Promise#finally', window.Promise != null ? typeof window.Promise.prototype.finally : typeof undefined);
console.log('fetch', typeof window.fetch);
console.log('Response#body', window.Response != null && 'body' in window.Response.prototype);
console.log('ReadableStream#getReader', window.ReadableStream != null ? typeof window.ReadableStream.prototype.getReader : typeof undefined);
console.log('AbortController', typeof window.AbortController);

// an example from https://fetch.spec.whatwg.org/
var url = 'https://matrixcalc.org/jstest/tests.php?events';

function consume(reader) {
  var responseText = '';
  var pump = function () {
    return reader.read().then(function (result) {
      if (result.done) {
        console.log('responseText', responseText);
        return;
      }
      responseText += String.fromCharCode.apply(undefined, result.value);
      return pump();
    });
  };
  return pump();
}

if (window.fetch != null && window.TextDecoder != null) {
  fetch(url).then(function (response) {
    var body = response.body;
    console.log('response.body', typeof body);
    if (body != null) {
      console.log('response.body.getReader', typeof body.getReader);
      if (typeof body.getReader == 'function') {
        consume(body.getReader());
      }
    }
  });
}

setTimeout(function () {
  console.log('done');
}, 1000);




  // Firefox < 38 (no "stream" option), IE, Edge
  function TextDecoderPolyfill() {
  }

  //TODO: streaming
  TextDecoderPolyfill.prototype.decode = function (octets) {
    var string = "";
    var i = 0;
    while (i < octets.length) {
      var octet = octets[i];
      var bytesNeeded = 0;
      var codePoint = 0;
      if (octet <= 0x7F) {
        bytesNeeded = 0;
        codePoint = octet & 0xFF;
      } else if (octet <= 0xDF) {
        bytesNeeded = 1;
        codePoint = octet & 0x1F;
      } else if (octet <= 0xEF) {
        bytesNeeded = 2;
        codePoint = octet & 0x0F;
      } else if (octet <= 0xF4) {
        bytesNeeded = 3;
        codePoint = octet & 0x07;
      }
      if (octets.length - i - bytesNeeded > 0) {
        var k = 0;
        while (k < bytesNeeded) {
          octet = octets[i + k + 1];
          codePoint = (codePoint << 6) | (octet & 0x3F);
          k += 1;
        }
      } else {
        codePoint = 0xFFFD;
        bytesNeeded = octets.length - i;
      }
      string += String.fromCodePoint(codePoint);
      i += bytesNeeded + 1;
    }
    return string
  };

  // ?
  if (Promise != undefined && Promise.prototype["finally"] == undefined) {
    Promise.prototype["finally"] = function (callback) {
      return this.then(function (result) {
        return Promise.resolve(callback()).then(function () {
          return result;
        });
      }, function (error) {
        return Promise.resolve(callback()).then(function () {
          throw error;
        });
      });
    };
  }

  var open = function (onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers, timeout) {
    // cache: "no-store"
    // https://bugs.chromium.org/p/chromium/issues/detail?id=453190
    var textDecoder = new TextDecoder();
    fetch(url, {
      headers: headers,
      credentials: withCredentials ? "include" : "same-origin"
    }).then(function (response) {
      var reader = response.body.getReader();
      onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"));
      return new Promise(function (resolve, reject) {
        var readNextChunk = function () {
          var id = setTimeout(function () {
            reader.cancel();
            resolve(undefined);
          }, timeout);
          reader.read().then(function (result) {
            clearTimeout(id);
            if (result.done) {
              //Note: bytes in textDecoder are ignored
              resolve(undefined);
            } else {
              var chunk = textDecoder.decode(result.value, {stream: true});
              //var chunk = String.fromCharCode.apply(undefined, result.value);
              onProgressCallback(chunk);
              readNextChunk();
            }
          })["catch"](function (error) {
            clearTimeout(id);
            reject(error);
          });
        };
        readNextChunk();
      });
    })["finally"](function () {
      onFinishCallback();
    });
  };


