/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const Utils = require("./../../../unisharp/Utils");
const Stream = require("./../../../unisharp/Stream");
const FileStream = require("./../../../unisharp/FileStream");
const MemoryStream = require("./../../../unisharp/MemoryStream");

const CompoundFileObjectType = require("./CompoundFileObjectType");
const FATSectorIds = require("./FATSectorIds");
const ReaderUtils = require("./ReaderUtils");
const CompoundFileStorage = require("./CompoundFileStorage");

// File System that base on Compound File structure. The class
// provides methods read the structure and the data of the
// Compound File.
// See Microsoft's [MS-CFB] "Compound File Binary File Format"
// reference for more details.
class CompoundFileSystem {
    
    get baseStream() {
        return this.m_BaseStream;
    }
    
    get header() {
        return this.m_Header;
    }
    
    get isVersion3() {
        return Utils.compareUnsigned(this.header.majorVersion, 3) == 0;
    }
    
    get created() {
        return (Utils.isNullOrEmpty(this.m_Filename) ? Utils.MIN_DATE : Utils.MIN_DATE);
    }
    
    get modified() {
        return (Utils.isNullOrEmpty(this.m_Filename) ? Utils.MIN_DATE : Utils.MIN_DATE);
    }
    
    constructor(filename, data = null, stream = null) {
        this.m_Filename = null;
        this.m_BaseStream = null;
        this.m_Disposed = false;
        this.m_RootStorage = null;
        this.m_Header = null;
        if (stream !== null) 
            this.m_BaseStream = stream;
        else if (data !== null) 
            this.m_BaseStream = new MemoryStream(data);
        else {
            this.m_Filename = filename;
            this.m_BaseStream = new FileStream(filename, "r", false);
        }
        this.initialize();
    }
    
    finalize() {
        this.dispose(false);
    }
    
    initialize() {
        this.m_Header = ReaderUtils.readHeader(this.baseStream);
        ReaderUtils.validateHeader(this.m_Header);
    }
    
    close() {
        try {
            this.dispose(true);
        } catch (ex) {
        }
        ;
    }
    
    dispose(disposing) {
        if (this.m_Disposed) 
            return;
        this.m_Disposed = true;
        this.m_BaseStream.close();
    }
    
    getRootStorage() {
        if (this.m_RootStorage === null) {
            let rootDirectoryStreamID = 0;
            this.m_RootStorage = new CompoundFileStorage(this, rootDirectoryStreamID, Array(0).fill(0));
            this.validateRootStorage();
        }
        return this.m_RootStorage;
    }
    
    validateRootStorage() {
        if (this.m_RootStorage.objectType !== CompoundFileObjectType.ROOT) {
            this.m_RootStorage = null;
            throw new Error("Invalid root storage");
        }
    }
    
    getSectorOffset(sectorNumber) {
        return (((sectorNumber) + 1)) << this.m_Header.sectorShift;
    }
    
    getSectorSize() {
        return 1 << this.m_Header.sectorShift;
    }
    
    getMiniSectorSize() {
        return 1 << this.m_Header.miniSectorShift;
    }
    
    getMiniSectorOffset(sectorNumber) {
        return this.toPhysicalStreamOffset(this.getRootStorage().entry.startingSectorLocation, (sectorNumber) << this.m_Header.miniSectorShift);
    }
    
    toPhysicalStreamOffset(firstSector, offsetWithinLogicalStream) {
        let sectorIndex = (offsetWithinLogicalStream >> this.m_Header.sectorShift);
        if (sectorIndex === 0) 
            return this.getSectorOffset(firstSector) + offsetWithinLogicalStream;
        let sector = this.getStreamNextSector(firstSector, sectorIndex);
        let mask = (1 << this.header.sectorShift) - 1;
        return this.getSectorOffset(sector) + ((offsetWithinLogicalStream & mask));
    }
    
    getStreamNextSector(sector, iterations) {
        for (let i = 0; i < iterations; i++) {
            let fatSectorIndex = (((sector) >>> (((this.header.sectorShift) - 2))));
            let mask = (1 << (((this.header.sectorShift) - 2))) - 1;
            let entryIndex = ((sector) & mask);
            let fatSector = this.getFATSector(fatSectorIndex);
            sector = ReaderUtils.readUInt32(this.baseStream, this.getSectorOffset(fatSector) + (entryIndex << 2));
            if (Utils.compareUnsigned(sector, FATSectorIds.mAXREGSECT) > 0) 
                throw new Error("Short chain");
        }
        return sector;
    }
    
    getMiniStreamNextSector(sector, iterations) {
        for (let i = 0; i < iterations; i++) {
            let entryOffset = (sector) << 2;
            if ((entryOffset >> this.header.sectorShift) >= (this.header.miniFATSectorsCount)) 
                throw new Error("Mini FAT sector index out of range");
            let offset = this.toPhysicalStreamOffset(this.header.firstMiniFATSectorLocation, entryOffset);
            sector = ReaderUtils.readUInt32(this.baseStream, offset);
            if (Utils.compareUnsigned(sector, FATSectorIds.mAXREGSECT) > 0) 
                throw new Error("Short chain");
        }
        return sector;
    }
    
    getFATSector(fatSectorIndex) {
        if (fatSectorIndex < ReaderUtils.headerDIFATSectorsCount) 
            return this.header.dIFAT[fatSectorIndex];
        else {
            let itemsPerPage = ((this.getSectorSize() >> 2)) - 1;
            let pageIndex = Utils.intDiv((fatSectorIndex - ReaderUtils.headerDIFATSectorsCount), itemsPerPage);
            if (pageIndex >= (this.header.fATSectorsCount)) 
                throw new Error("FAT sector index out of range");
            let currentSector = this.header.firstDIFATSectorLocation;
            for (let i = 0; i < pageIndex; i++) {
                let offset = this.getSectorOffset(currentSector) + (itemsPerPage << 2);
                currentSector = ReaderUtils.readUInt32(this.baseStream, offset);
            }
            let entryIndex = ((fatSectorIndex - ReaderUtils.headerDIFATSectorsCount)) % itemsPerPage;
            return ReaderUtils.readUInt32(this.baseStream, this.getSectorOffset(currentSector) + (entryIndex << 2));
        }
    }
}


module.exports = CompoundFileSystem