// ASF Objects
import * as Token from 'token-types';
import { Buffer } from 'node:buffer';
import * as util from '../common/Util.js';
import GUID from './GUID.js';
import { AsfUtil } from './AsfUtil.js';
import { AttachedPictureType } from '../id3v2/ID3v2Token.js';
/**
 * Data Type: Specifies the type of information being stored. The following values are recognized.
 */
export var DataType;
(function (DataType) {
    /**
     * Unicode string. The data consists of a sequence of Unicode characters.
     */
    DataType[DataType["UnicodeString"] = 0] = "UnicodeString";
    /**
     * BYTE array. The type of data is implementation-specific.
     */
    DataType[DataType["ByteArray"] = 1] = "ByteArray";
    /**
     * BOOL. The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values.
     */
    DataType[DataType["Bool"] = 2] = "Bool";
    /**
     * DWORD. The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer.
     */
    DataType[DataType["DWord"] = 3] = "DWord";
    /**
     * QWORD. The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer.
     */
    DataType[DataType["QWord"] = 4] = "QWord";
    /**
     * WORD. The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer.
     */
    DataType[DataType["Word"] = 5] = "Word";
})(DataType || (DataType = {}));
/**
 * Token for: 3. ASF top-level Header Object
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3
 */
export const TopLevelHeaderObjectToken = {
    len: 30,
    get: (buf, off) => {
        return {
            objectId: GUID.fromBin(new Token.BufferType(16).get(buf, off)),
            objectSize: Number(Token.UINT64_LE.get(buf, off + 16)),
            numberOfHeaderObjects: Token.UINT32_LE.get(buf, off + 24)
            // Reserved: 2 bytes
        };
    }
};
/**
 * Token for: 3.1 Header Object (mandatory, one only)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_1
 */
export const HeaderObjectToken = {
    len: 24,
    get: (buf, off) => {
        return {
            objectId: GUID.fromBin(new Token.BufferType(16).get(buf, off)),
            objectSize: Number(Token.UINT64_LE.get(buf, off + 16))
        };
    }
};
export class State {
    constructor(header) {
        this.len = Number(header.objectSize) - HeaderObjectToken.len;
    }
    postProcessTag(tags, name, valueType, data) {
        if (name === 'WM/Picture') {
            tags.push({ id: name, value: WmPictureToken.fromBuffer(data) });
        }
        else {
            const parseAttr = AsfUtil.getParserForAttr(valueType);
            if (!parseAttr) {
                throw new Error('unexpected value headerType: ' + valueType);
            }
            tags.push({ id: name, value: parseAttr(data) });
        }
    }
}
// ToDo: use ignore type
export class IgnoreObjectState extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        return null;
    }
}
/**
 * Token for: 3.2: File Properties Object (mandatory, one only)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
 */
export class FilePropertiesObject extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        return {
            fileId: GUID.fromBin(buf, off),
            fileSize: Token.UINT64_LE.get(buf, off + 16),
            creationDate: Token.UINT64_LE.get(buf, off + 24),
            dataPacketsCount: Token.UINT64_LE.get(buf, off + 32),
            playDuration: Token.UINT64_LE.get(buf, off + 40),
            sendDuration: Token.UINT64_LE.get(buf, off + 48),
            preroll: Token.UINT64_LE.get(buf, off + 56),
            flags: {
                broadcast: util.getBit(buf, off + 64, 24),
                seekable: util.getBit(buf, off + 64, 25)
            },
            // flagsNumeric: Token.UINT32_LE.get(buf, off + 64),
            minimumDataPacketSize: Token.UINT32_LE.get(buf, off + 68),
            maximumDataPacketSize: Token.UINT32_LE.get(buf, off + 72),
            maximumBitrate: Token.UINT32_LE.get(buf, off + 76)
        };
    }
}
FilePropertiesObject.guid = GUID.FilePropertiesObject;
/**
 * Token for: 3.3 Stream Properties Object (mandatory, one per stream)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
 */
export class StreamPropertiesObject extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        return {
            streamType: GUID.decodeMediaType(GUID.fromBin(buf, off)),
            errorCorrectionType: GUID.fromBin(buf, off + 8)
            // ToDo
        };
    }
}
StreamPropertiesObject.guid = GUID.StreamPropertiesObject;
/**
 * 3.4: Header Extension Object (mandatory, one only)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
 */
export class HeaderExtensionObject {
    constructor() {
        this.len = 22;
    }
    get(buf, off) {
        return {
            reserved1: GUID.fromBin(buf, off),
            reserved2: buf.readUInt16LE(off + 16),
            extensionDataSize: buf.readUInt32LE(off + 18)
        };
    }
}
HeaderExtensionObject.guid = GUID.HeaderExtensionObject;
/**
 * 3.5: The Codec List Object provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
 */
const CodecListObjectHeader = {
    len: 20,
    get: (buf, off) => {
        return {
            entryCount: buf.readUInt16LE(off + 16)
        };
    }
};
async function readString(tokenizer) {
    const length = await tokenizer.readNumber(Token.UINT16_LE);
    return (await tokenizer.readToken(new Token.StringType(length * 2, 'utf16le'))).replace('\0', '');
}
/**
 * 3.5: Read the Codec-List-Object, which provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
 */
export async function readCodecEntries(tokenizer) {
    const codecHeader = await tokenizer.readToken(CodecListObjectHeader);
    const entries = [];
    for (let i = 0; i < codecHeader.entryCount; ++i) {
        entries.push(await readCodecEntry(tokenizer));
    }
    return entries;
}
async function readInformation(tokenizer) {
    const length = await tokenizer.readNumber(Token.UINT16_LE);
    const buf = Buffer.alloc(length);
    await tokenizer.readBuffer(buf);
    return buf;
}
/**
 * Read Codec-Entries
 * @param tokenizer
 */
async function readCodecEntry(tokenizer) {
    const type = await tokenizer.readNumber(Token.UINT16_LE);
    return {
        type: {
            videoCodec: (type & 0x0001) === 0x0001,
            audioCodec: (type & 0x0002) === 0x0002
        },
        codecName: await readString(tokenizer),
        description: await readString(tokenizer),
        information: await readInformation(tokenizer)
    };
}
/**
 * 3.10 Content Description Object (optional, one only)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
 */
export class ContentDescriptionObjectState extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        const tags = [];
        let pos = off + 10;
        for (let i = 0; i < ContentDescriptionObjectState.contentDescTags.length; ++i) {
            const length = buf.readUInt16LE(off + i * 2);
            if (length > 0) {
                const tagName = ContentDescriptionObjectState.contentDescTags[i];
                const end = pos + length;
                tags.push({ id: tagName, value: AsfUtil.parseUnicodeAttr(buf.slice(pos, end)) });
                pos = end;
            }
        }
        return tags;
    }
}
ContentDescriptionObjectState.guid = GUID.ContentDescriptionObject;
ContentDescriptionObjectState.contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
/**
 * 3.11 Extended Content Description Object (optional, one only)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_11
 */
export class ExtendedContentDescriptionObjectState extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        const tags = [];
        const attrCount = buf.readUInt16LE(off);
        let pos = off + 2;
        for (let i = 0; i < attrCount; i += 1) {
            const nameLen = buf.readUInt16LE(pos);
            pos += 2;
            const name = AsfUtil.parseUnicodeAttr(buf.slice(pos, pos + nameLen));
            pos += nameLen;
            const valueType = buf.readUInt16LE(pos);
            pos += 2;
            const valueLen = buf.readUInt16LE(pos);
            pos += 2;
            const value = buf.slice(pos, pos + valueLen);
            pos += valueLen;
            this.postProcessTag(tags, name, valueType, value);
        }
        return tags;
    }
}
ExtendedContentDescriptionObjectState.guid = GUID.ExtendedContentDescriptionObject;
/**
 * 4.1 Extended Stream Properties Object (optional, 1 per media stream)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
 */
export class ExtendedStreamPropertiesObjectState extends State {
    constructor(header) {
        super(header);
    }
    get(buf, off) {
        return {
            startTime: Token.UINT64_LE.get(buf, off),
            endTime: Token.UINT64_LE.get(buf, off + 8),
            dataBitrate: buf.readInt32LE(off + 12),
            bufferSize: buf.readInt32LE(off + 16),
            initialBufferFullness: buf.readInt32LE(off + 20),
            alternateDataBitrate: buf.readInt32LE(off + 24),
            alternateBufferSize: buf.readInt32LE(off + 28),
            alternateInitialBufferFullness: buf.readInt32LE(off + 32),
            maximumObjectSize: buf.readInt32LE(off + 36),
            flags: {
                reliableFlag: util.getBit(buf, off + 40, 0),
                seekableFlag: util.getBit(buf, off + 40, 1),
                resendLiveCleanpointsFlag: util.getBit(buf, off + 40, 2)
            },
            // flagsNumeric: Token.UINT32_LE.get(buf, off + 64),
            streamNumber: buf.readInt16LE(off + 42),
            streamLanguageId: buf.readInt16LE(off + 44),
            averageTimePerFrame: buf.readInt32LE(off + 52),
            streamNameCount: buf.readInt32LE(off + 54),
            payloadExtensionSystems: buf.readInt32LE(off + 56),
            streamNames: [], // ToDo
            streamPropertiesObject: null
        };
    }
}
ExtendedStreamPropertiesObjectState.guid = GUID.ExtendedStreamPropertiesObject;
/**
 * 4.7  Metadata Object (optional, 0 or 1)
 * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
 */
export class MetadataObjectState extends State {
    constructor(header) {
        super(header);
    }
    get(uint8Array, off) {
        const tags = [];
        const buf = Buffer.from(uint8Array);
        const descriptionRecordsCount = buf.readUInt16LE(off);
        let pos = off + 2;
        for (let i = 0; i < descriptionRecordsCount; i += 1) {
            pos += 4;
            const nameLen = buf.readUInt16LE(pos);
            pos += 2;
            const dataType = buf.readUInt16LE(pos);
            pos += 2;
            const dataLen = buf.readUInt32LE(pos);
            pos += 4;
            const name = AsfUtil.parseUnicodeAttr(buf.slice(pos, pos + nameLen));
            pos += nameLen;
            const data = buf.slice(pos, pos + dataLen);
            pos += dataLen;
            this.postProcessTag(tags, name, dataType, data);
        }
        return tags;
    }
}
MetadataObjectState.guid = GUID.MetadataObject;
// 4.8	Metadata Library Object (optional, 0 or 1)
export class MetadataLibraryObjectState extends MetadataObjectState {
    constructor(header) {
        super(header);
    }
}
MetadataLibraryObjectState.guid = GUID.MetadataLibraryObject;
/**
 * Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
 */
export class WmPictureToken {
    static fromBase64(base64str) {
        return this.fromBuffer(Buffer.from(base64str, 'base64'));
    }
    static fromBuffer(buffer) {
        const pic = new WmPictureToken(buffer.length);
        return pic.get(buffer, 0);
    }
    constructor(len) {
        this.len = len;
    }
    get(buffer, offset) {
        const typeId = buffer.readUInt8(offset++);
        const size = buffer.readInt32LE(offset);
        let index = 5;
        while (buffer.readUInt16BE(index) !== 0) {
            index += 2;
        }
        const format = buffer.slice(5, index).toString('utf16le');
        while (buffer.readUInt16BE(index) !== 0) {
            index += 2;
        }
        const description = buffer.slice(5, index).toString('utf16le');
        return {
            type: AttachedPictureType[typeId],
            format,
            description,
            size,
            data: buffer.slice(index + 4)
        };
    }
}
