"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IsoView = void 0;
const ContainerBoxes_js_1 = require("./ContainerBoxes.js");
const DATA_js_1 = require("./fields/DATA.js");
const INT_js_1 = require("./fields/INT.js");
const STRING_js_1 = require("./fields/STRING.js");
const TEMPLATE_js_1 = require("./fields/TEMPLATE.js");
const UINT_js_1 = require("./fields/UINT.js");
const UTF8_js_1 = require("./fields/UTF8.js");
const readData_js_1 = require("./readers/readData.js");
const readInt_js_1 = require("./readers/readInt.js");
const readString_js_1 = require("./readers/readString.js");
const readTemplate_js_1 = require("./readers/readTemplate.js");
const readTerminatedString_js_1 = require("./readers/readTerminatedString.js");
const readUint_js_1 = require("./readers/readUint.js");
const readUTF8String_js_1 = require("./readers/readUTF8String.js");
const readUTF8TerminatedString_js_1 = require("./readers/readUTF8TerminatedString.js");
/**
 * ISO BMFF data view. Similar to DataView, but with additional methods for reading ISO BMFF data.
 * It implements the iterator protocol, so it can be used in a for...of loop.
 *
 * @group ISOBMFF
 *
 * @beta
 */
class IsoView {
    constructor(raw, config) {
        this.truncated = false;
        this.slice = (size) => {
            const dataView = new DataView(this.dataView.buffer, this.offset, size);
            this.offset += size;
            return new IsoView(dataView, this.config);
        };
        this.read = (type, size = 0) => {
            // TODO: Change all sizes from bits to bytes
            const { dataView, offset } = this;
            let result;
            let cursor = size;
            switch (type) {
                case UINT_js_1.UINT:
                    result = (0, readUint_js_1.readUint)(dataView, offset, size);
                    break;
                case INT_js_1.INT:
                    result = (0, readInt_js_1.readInt)(dataView, offset, size);
                    break;
                case TEMPLATE_js_1.TEMPLATE:
                    result = (0, readTemplate_js_1.readTemplate)(dataView, offset, size);
                    break;
                case STRING_js_1.STRING:
                    if (size === -1) {
                        result = (0, readTerminatedString_js_1.readTerminatedString)(dataView, offset);
                        cursor = result.length + 1;
                    }
                    else {
                        result = (0, readString_js_1.readString)(dataView, offset, size);
                    }
                    break;
                case DATA_js_1.DATA:
                    result = (0, readData_js_1.readData)(dataView, offset, size);
                    cursor = result.length;
                    break;
                case UTF8_js_1.UTF8:
                    if (size === -1) {
                        result = (0, readUTF8TerminatedString_js_1.readUTF8TerminatedString)(dataView, offset);
                        cursor = result.length + 1;
                    }
                    else {
                        result = (0, readUTF8String_js_1.readUTF8String)(dataView, offset);
                    }
                    break;
                default:
                    result = -1;
            }
            this.offset += cursor;
            return result;
        };
        this.readUint = (size) => {
            return this.read(UINT_js_1.UINT, size);
        };
        this.readInt = (size) => {
            return this.read(INT_js_1.INT, size);
        };
        this.readString = (size) => {
            return this.read(STRING_js_1.STRING, size);
        };
        this.readTemplate = (size) => {
            return this.read(TEMPLATE_js_1.TEMPLATE, size);
        };
        this.readData = (size) => {
            return this.read(DATA_js_1.DATA, size);
        };
        this.readUtf8 = (size) => {
            return this.read(UTF8_js_1.UTF8, size);
        };
        this.readFullBox = () => {
            return {
                version: this.readUint(1),
                flags: this.readUint(3),
            };
        };
        this.readArray = (type, size, length) => {
            const value = [];
            for (let i = 0; i < length; i++) {
                value.push(this.read(type, size));
            }
            return value;
        };
        this.readBox = () => {
            const { dataView, offset } = this;
            // read box size and type without advancing the cursor in case the box is truncated
            let cursor = 0;
            const box = {
                size: (0, readUint_js_1.readUint)(dataView, offset, 4),
                type: (0, readString_js_1.readString)(dataView, offset + 4, 4),
            };
            cursor += 8;
            if (box.size === 1) {
                box.largesize = (0, readUint_js_1.readUint)(dataView, offset + cursor, 8);
                cursor += 8;
            }
            const actualSize = box.largesize || box.size;
            if (this.cursor + actualSize > dataView.byteLength) {
                this.truncated = true;
                throw new Error('Truncated box');
            }
            this.offset += cursor;
            if (box.type === 'uuid') {
                box.usertype = this.readArray('uint', 1, 16);
            }
            const viewSize = box.size === 0 ? this.bytesRemaining : actualSize - cursor;
            box.data = this.slice(viewSize);
            return box;
        };
        this.readBoxes = (length) => {
            const result = [];
            for (const box of this) {
                result.push(box);
                if (length > 0 && result.length >= length) {
                    break;
                }
            }
            return result;
        };
        this.readEntries = (length, map) => {
            const result = [];
            for (let i = 0; i < length; i++) {
                result.push(map());
            }
            return result;
        };
        this.dataView = (raw instanceof ArrayBuffer) ? new DataView(raw) : (raw instanceof Uint8Array) ? new DataView(raw.buffer, raw.byteOffset, raw.byteLength) : raw;
        this.offset = this.dataView.byteOffset;
        this.config = config || { recursive: false, parsers: {} };
    }
    get cursor() {
        return this.offset - this.dataView.byteOffset;
    }
    get done() {
        return this.cursor >= this.dataView.byteLength || this.truncated;
    }
    get bytesRemaining() {
        return this.dataView.byteLength - this.cursor;
    }
    *[Symbol.iterator]() {
        const { parsers = {}, recursive = false } = this.config;
        while (!this.done) {
            try {
                const { type, data, ...rest } = this.readBox();
                const box = { type, ...rest };
                const parser = parsers[type] || parsers[type.trim()]; // url and urn boxes have a trailing space in their type field
                if (parser) {
                    Object.assign(box, parser(data, this.config));
                }
                if (ContainerBoxes_js_1.ContainerBoxes.includes(type)) {
                    const boxes = [];
                    for (const child of data) {
                        if (recursive) {
                            yield child;
                        }
                        boxes.push(child);
                    }
                    box.boxes = boxes;
                }
                yield box;
            }
            catch (error) {
                break;
            }
        }
    }
}
exports.IsoView = IsoView;
//# sourceMappingURL=IsoView.js.map