"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var buffer_1 = require("buffer");
var KaitaiStream = /** @class */ (function () {
    /**
      KaitaiStream is an implementation of Kaitai Struct API for JavaScript.
      Based on DataStream - https://github.com/kig/DataStream.js
      @param {ArrayBuffer} arrayBuffer ArrayBuffer to read from.
      @param {?Number} byteOffset Offset from arrayBuffer beginning for the KaitaiStream.
      */
    function KaitaiStream(arrayBuffer, byteOffset, isBigEndian) {
        if (byteOffset === void 0) { byteOffset = 0; }
        if (isBigEndian === void 0) { isBigEndian = true; }
        /**
          Virtual byte length of the KaitaiStream backing buffer.
          Updated to be max of original buffer size and last written size.
          If dynamicSize is false is set to buffer size.
          @type {number}
          */
        this._byteLength = 0;
        this._byteOffset = byteOffset;
        if (arrayBuffer instanceof ArrayBuffer || (arrayBuffer && arrayBuffer.constructor.name === "SharedArrayBuffer")) {
            this.buffer = arrayBuffer; // TODO check SharedArrayBuffer can be associated as ArrayBuffer
        }
        else if (typeof arrayBuffer == "object") {
            this.dataView = arrayBuffer;
            if (byteOffset) {
                this._byteOffset += byteOffset;
            }
        }
        else {
            this.buffer = new ArrayBuffer(arrayBuffer || 1);
        }
        this._isBigEndian = isBigEndian;
        this._pos = 0;
        this.bits = 0;
        this.bitsLeft = 0;
    }
    ;
    Object.defineProperty(KaitaiStream.prototype, "byteLeft", {
        get: function () {
            return this.size - this._pos;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "pos", {
        get: function () {
            return this._pos;
        },
        set: function (v) {
            this._pos = v;
            this.alignToByte();
        },
        enumerable: true,
        configurable: true
    });
    KaitaiStream.prototype.getStreamSize = function () {
        return this._buffer.byteLength;
    };
    KaitaiStream.prototype.alignToByte = function () {
        this.bits = 0;
        this.bitsLeft = 0;
    };
    Object.defineProperty(KaitaiStream.prototype, "buffer", {
        /**
            Set/get the backing ArrayBuffer of the KaitaiStream object.
            The setter updates the DataView to point to the new buffer.
            @type {Object}
            */
        get: function () {
            this._trimAlloc();
            return this._buffer;
        },
        set: function (v) {
            this._buffer = v;
            this._dataView = new DataView(this._buffer, this._byteOffset);
            this._byteLength = this._buffer.byteLength;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "toBytes", {
        get: function () {
            var a = new Uint8Array(this._dataView.byteLength);
            // console.debug(this._dataView)
            for (var i = 0; i < this._dataView.byteLength; i++)
                a[i] = this._dataView.getUint8(i);
            return a;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "isBigEndian", {
        get: function () {
            return this._isBigEndian;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "isLittleEndian", {
        get: function () {
            return !this._isBigEndian;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "byteOffset", {
        /**
          Set/get the byteOffset of the KaitaiStream object.
          The setter updates the DataView to point to the new byteOffset.
          @type {number}
          */
        get: function () {
            return this._byteOffset;
        },
        set: function (v) {
            this._byteOffset = v;
            this._dataView = new DataView(this._buffer, this._byteOffset);
            this._byteLength = this._buffer.byteLength;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(KaitaiStream.prototype, "dataView", {
        /**
          Set/get the backing DataView of the KaitaiStream object.
          The setter updates the buffer and byteOffset to point to the DataView values.
          @type {Object}
          */
        get: function () {
            return this._dataView;
        },
        set: function (v) {
            this._byteOffset = v.byteOffset;
            this._buffer = v.buffer;
            this._dataView = new DataView(this._buffer, this._byteOffset);
            this._byteLength = this._byteOffset + v.byteLength;
        },
        enumerable: true,
        configurable: true
    });
    /**
      Internal function to trim the KaitaiStream buffer when required.
      Used for stripping out the extra bytes from the backing buffer when
      the virtual byteLength is smaller than the buffer byteLength (happens after
      growing the buffer with writes and not filling the extra space completely).
      @return {null}
      */
    KaitaiStream.prototype._trimAlloc = function () {
        if (this._byteLength == this._buffer.byteLength) {
            return;
        }
        var buf = new ArrayBuffer(this._byteLength);
        var dst = new Uint8Array(buf);
        var src = new Uint8Array(this._buffer, 0, dst.length);
        dst.set(src);
        this.buffer = buf;
    };
    ;
    KaitaiStream.bytesStripRight = function (data, padByte) {
        var newLen = data.length;
        while (data[newLen - 1] == padByte)
            newLen--;
        return data.slice(0, newLen);
    };
    KaitaiStream.bytesTerminate = function (data, term, include) {
        if (include === void 0) { include = false; }
        var newLen = 0;
        var maxLen = data.length;
        while (newLen < maxLen && data[newLen] != term)
            newLen++;
        if (include && newLen < maxLen)
            newLen++;
        return data.slice(0, newLen);
    };
    KaitaiStream.bytesToStr = function (arr, encoding) {
        if (encoding == null || encoding.toLowerCase() == "ascii") {
            return KaitaiStream.createStringFromArray(arr);
        }
        else {
            if (typeof TextDecoder === 'function') {
                // we're in the browser that supports TextDecoder
                return (new TextDecoder(encoding)).decode(arr);
            }
            else {
                // probably we're in node.js
                // check if it's supported natively by node.js Buffer
                // see https://github.com/nodejs/node/blob/master/lib/buffer.js#L187 for details
                switch (encoding.toLowerCase()) {
                    case 'utf8':
                    case 'utf-8':
                    case 'ucs2':
                    case 'ucs-2':
                    case 'utf16le':
                    case 'utf-16le':
                        return new buffer_1.Buffer(arr).toString(encoding);
                        break;
                    default:
                        // unsupported encoding, we'll have to resort to iconv-lite
                        // if (typeof KaitaiStream.iconvlite === 'undefined')
                        //     KaitaiStream.iconvlite = require('iconv-lite');
                        // return KaitaiStream.iconvlite.decode(arr, encoding);
                        throw new Error("Unsupported encoding: " + encoding.toLowerCase());
                }
            }
        }
    };
    // ========================================================================
    // Stream positioning
    // ========================================================================
    /**
      Returns true if the KaitaiStream seek pointer is at the end of buffer and
      there's no more data to read.
      @return {boolean} True if the seek pointer is at the end of the buffer.
      */
    KaitaiStream.prototype.isEof = function () {
        return (this.pos >= this.size);
    };
    ;
    KaitaiStream.prototype.forward = function (relativePosition) {
        this.seek(this.pos + relativePosition);
        return this;
    };
    /**
      Sets the KaitaiStream read/write position to given position.
      Clamps between 0 and KaitaiStream length.
      @param {number} pos Position to seek to.
      @return {null}
      */
    KaitaiStream.prototype.seek = function (pos) {
        var npos = Math.max(0, Math.min(this.size, pos));
        this.pos = (isNaN(npos) || !isFinite(npos)) ? 0 : npos;
    };
    ;
    Object.defineProperty(KaitaiStream.prototype, "size", {
        /**
          Returns the byte length of the KaitaiStream object.
          @type {number}
          */
        get: function () {
            return this._byteLength - this._byteOffset;
        },
        enumerable: true,
        configurable: true
    });
    // ========================================================================
    // Misc runtime operations
    // ========================================================================
    KaitaiStream.mod = function (a, b) {
        if (b <= 0)
            throw "mod divisor <= 0";
        var r = a % b;
        if (r < 0)
            r += b;
        return r;
    };
    KaitaiStream.arrayMin = function (arr) {
        var min = arr[0];
        var x;
        for (var i = 1, n = arr.length; i < n; ++i) {
            x = arr[i];
            if (x < min)
                min = x;
        }
        return min;
    };
    KaitaiStream.arrayMax = function (arr) {
        var max = arr[0];
        var x;
        for (var i = 1, n = arr.length; i < n; ++i) {
            x = arr[i];
            if (x > max)
                max = x;
        }
        return max;
    };
    KaitaiStream.byteArrayCompare = function (a, b) {
        if (a === b)
            return 0;
        var al = a.length;
        var bl = b.length;
        var minLen = al < bl ? al : bl;
        for (var i = 0; i < minLen; i++) {
            var cmp = a[i] - b[i];
            if (cmp != 0)
                return cmp;
        }
        // Reached the end of at least one of the arrays
        if (al == bl) {
            return 0;
        }
        else {
            return al - bl;
        }
    };
    /**
      Creates an array from an array of character codes.
      Uses String.fromCharCode in chunks for memory efficiency and then concatenates
      the resulting string chunks.
      @param {array} array Array of character codes.
      @return {string} String created from the character codes.
    **/
    KaitaiStream.createStringFromArray = function (array) {
        var chunk_size = 0x8000;
        var chunks = [];
        for (var i = 0; i < array.length; i += chunk_size) {
            chunks.push(String.fromCharCode.apply(null, array.subarray(i, i + chunk_size)));
        }
        return chunks.join("");
    };
    ;
    // ========================================================================
    // Byte array processing
    // ========================================================================
    KaitaiStream.processXorOne = function (data, key) {
        var r = new Uint8Array(data.length);
        var dl = data.length;
        for (var i = 0; i < dl; i++)
            r[i] = data[i] ^ key;
        return r;
    };
    KaitaiStream.processXorMany = function (data, key) {
        var r = new Uint8Array(data.length);
        var dl = data.length;
        var kl = key.length;
        var ki = 0;
        for (var i = 0; i < data.length; i++) {
            r[i] = data[i] ^ key[ki];
            ki++;
            if (ki >= kl)
                ki = 0;
        }
        return r;
    };
    KaitaiStream.processRotateLeft = function (data, amount, groupSize) {
        if (groupSize != 1)
            throw ("unable to rotate group of " + groupSize + " bytes yet");
        var mask = groupSize * 8 - 1;
        var antiAmount = -amount & mask;
        var r = new Uint8Array(data.length);
        for (var i = 0; i < data.length; i++)
            r[i] = (data[i] << amount) & 0xff | (data[i] >> antiAmount);
        return r;
    };
    // public static processZlib(buf) {
    //     if (typeof require !== 'undefined') {
    //         // require is available - we're running under node
    //         if (typeof KaitaiStream.zlib === 'undefined')
    //             KaitaiStream.zlib = require('zlib');
    //         let b;
    //         if (buf instanceof Uint8Array) {
    //             b = new Buffer(buf.buffer);
    //         } else {
    //             b = buf;
    //         }
    //         // use node's zlib module API
    //         var r = KaitaiStream.zlib.inflateSync(b);
    //         return r;
    //     } else {
    //         // no require() - assume we're running as a web worker in browser.
    //         // user should have configured KaitaiStream.depUrls.zlib, if not
    //         // we'll throw.
    //         if (typeof KaitaiStream.zlib === 'undefined'
    //             && typeof KaitaiStream.depUrls.zlib !== 'undefined') {
    //             importScripts(KaitaiStream.depUrls.zlib);
    //             KaitaiStream.zlib = pako;
    //         }
    //         // use pako API
    //         r = KaitaiStream.zlib.inflate(buf);
    //         return r;
    //     }
    // }
    // ========================================================================
    // Internal implementation details
    // ========================================================================
    /**
      Maps a Uint8Array into the KaitaiStream buffer.
      Nice for quickly reading in data.
      @param {number} length Number of elements to map.
      @return {Object} Uint8Array to the KaitaiStream backing buffer.
      */
    KaitaiStream.prototype.mapUint8Array = function (length) {
        length |= 0;
        if (this.pos + length > this.size) {
            throw new EOFError(length, this.size - this.pos);
        }
        var arr = new Uint8Array(this._buffer, this.byteOffset + this.pos, length);
        this._pos += length;
        return arr;
    };
    ;
    /**
      Dependency configuration data. Holds urls for (optional) dynamic loading
      of code dependencies from a remote server. For use by (static) processing functions.
      Caller should the supported keys to the asset urls as needed.
      NOTE: `depUrls` is a static property of KaitaiStream (the factory),like the various
            processing functions. It is NOT part of the prototype of instances.
      @type {Object}
      */
    KaitaiStream.depUrls = {
        // processZlib uses this and expected a link to a copy of pako.
        // specifically the pako_inflate.min.js script at:
        // https://raw.githubusercontent.com/nodeca/pako/master/dist/pako_inflate.min.js
        zlib: undefined
    };
    /**
      Native endianness. Either KaitaiStream.BIG_ENDIAN or KaitaiStream.LITTLE_ENDIAN
      depending on the platform endianness.
      @type {boolean}
     */
    KaitaiStream.endianness = new Int8Array(new Int16Array([1]).buffer)[0] > 0;
    return KaitaiStream;
}());
exports.KaitaiStream = KaitaiStream;
var EOFError = /** @class */ (function (_super) {
    __extends(EOFError, _super);
    function EOFError(bytesReq, bytesAvail) {
        var _this = _super.call(this, "requested " + bytesReq + " bytes, but only " + bytesAvail + " bytes available") || this;
        _this.bytesReq = bytesReq;
        _this.bytesAvail = bytesAvail;
        return _this;
    }
    return EOFError;
}(Error));
exports.EOFError = EOFError;
var UnexpectedDataError = /** @class */ (function (_super) {
    __extends(UnexpectedDataError, _super);
    function UnexpectedDataError(expected, actual) {
        var _this = _super.call(this, "expected [" + expected + "], but got [" + actual + "]") || this;
        _this.expected = expected;
        _this.actual = actual;
        return _this;
    }
    return UnexpectedDataError;
}(Error));
exports.UnexpectedDataError = UnexpectedDataError;
var UndecidedEndiannessError = /** @class */ (function (_super) {
    __extends(UndecidedEndiannessError, _super);
    function UndecidedEndiannessError() {
        return _super.call(this, "UndecidedEndiannessError") || this;
    }
    return UndecidedEndiannessError;
}(Error));
exports.UndecidedEndiannessError = UndecidedEndiannessError;
