import { FetchAssetResolver } from "./TinyUSDZLoader";  

class TinyUSDZComposer {

    constructor() {

        this.usdLayer_ = null; // This will hold the USD layer after loading.
        this.assetMap_ = new Map();
        this.usdLoader_ = null; // TinyUSDZLoaderNative instance
        this.assetResolver_ = new FetchAssetResolver(); // 'fetch' Asset resolver 
      
        this.assetSearchPaths_ = ["./"];
        this.baseWorkingPath_ = "./";

    }

    static hasSublayer(usd_layer) {
        if (!usd_layer || !usd_layer.hasSublayer) {
            console.warn("TinyUSDZComposer: Invalid USD layer or hasSublayer not available.");
            return false;
        }
        return usd_layer.hasSublayer();
    }

    static extractSublayerAssetPaths(usd_layer) {
        if (!usd_layer || !usd_layer.extractSublayerAssetPaths) {
            console.warn("TinyUSDZComposer: Invalid USD layer or extractSublayerAssetPaths not available.");
            return [];
        }
        return usd_layer.extractSublayerAssetPaths();
    }

    static hasReferences(usd_layer) {
        if (!usd_layer || !usd_layer.hasReferences) {
            console.warn("TinyUSDZComposer: Invalid USD layer or hasReferences not available.");
            return false;
        }
        return usd_layer.hasReferences();
    }

    static extractReferencesAssetPaths(usd_layer) {
        if (!usd_layer || !usd_layer.extractReferencesAssetPaths) {
            console.warn("TinyUSDZComposer: Invalid USD layer or extractReferencesAssetPaths not available.");
            return [];
        }
        return usd_layer.extractReferencesAssetPaths();
    }

    static hasPayload(usd_layer) {
        if (!usd_layer || !usd_layer.hasPayload) {
            console.warn("TinyUSDZComposer: Invalid USD layer or hasPayload not available.");
            return false;
        }
        return usd_layer.hasPayload();
    }

    static extractPayloadAssetPaths(usd_layer) {
        if (!usd_layer || !usd_layer.extractPayloadAssetPaths) {
            console.warn("TinyUSDZComposer: Invalid USD layer or extractPayloadAssetPaths not available.");
            return [];
        }
        return usd_layer.extractPayloadAssetPaths();
    }

    static composeSublayer(usd_layer) {
        if (!usd_layer || !usd_layer.composeSublayer) {
            console.warn("TinyUSDZComposer: Invalid USD layer or composeSublayer not available.");
            return [];
        }
        return usd_layer.composeSublayer();
    }

    static composeReferences(usd_layer) {
        if (!usd_layer || !usd_layer.composeReferences) {
            console.warn("TinyUSDZComposer: Invalid USD layer or composeReferences not available.");
            return [];
        }
        return usd_layer.composeReferences();
    }

    static composePayload(usd_layer) {
        if (!usd_layer || !usd_layer.composePayload) {
            console.warn("TinyUSDZComposer: Invalid USD layer or composePayload not available.");
            return [];
        }
        return usd_layer.composePayload();
    }

    static hasInherits(usd_layer) {
        if (!usd_layer || !usd_layer.hasInherits) {
            console.warn("TinyUSDZComposer: Invalid USD layer or hasInherits not available.");
            return false;
        }
        return usd_layer.hasInherits();
    }

    static composeInherits(usd_layer) {
        if (!usd_layer || !usd_layer.composeInherits) {
            console.warn("TinyUSDZComposer: Invalid USD layer or composeInherits not available.");
            return [];
        }
        return usd_layer.composeInherits();
    }

    static hasVariants(usd_layer) {
        if (!usd_layer || !usd_layer.hasVariants) {
            console.warn("TinyUSDZComposer: Invalid USD layer or hasVariants not available.");
            return false;
        }
        return usd_layer.hasVariants();
    }

    static composeVariants(usd_layer) {
        if (!usd_layer || !usd_layer.composeVariants) {
            console.warn("TinyUSDZComposer: Invalid USD layer or composeVariants not available.");
            return [];
        }
        return usd_layer.composeVariants();
    }

    clearAssetMap() {
        this.assetMap_.clear();
    }

    getAssetMap() {
        return this.assetMap_;
    }

    setLayer(usdLayer) {
        this.usdLayer_ = usdLayer;
    }

    getLayer() {
        if (!this.usdLayer_) {
            throw new Error("TinyUSDZComposer: Layer is not set. Call setLayer() first.");
        }
        return this.usdLayer_;
    }

    setUSDLoader(usd_loader) {
        this.usdLoader_ = usd_loader;
    }

    getUSDLoader() {
        if (!this.usdloader_) {
            throw new Error("TinyUSDZComposer: USD loader is not set. Call setUSDLoader() first.");
        }
        return this.usdLoader_;
    }

    setAssetSearchPaths(paths) {
        this.assetSearchPaths_ = paths;
    }

    getAssetSearchPaths() {
        return this.assetSearchPaths_;
    }

    setBaseWorkingPath(path) {
        this.baseWorkingPath_ = path;
    }

    getBaseWorkingPath() {
        return this.baseWorkingPath_;
    }

    // Recursively resolve sublayer assets.
    async resolveSublayerAssets(depth, usdLayer) {

        if (depth > 16) {
            console.warn("TinyUSDZComposer: Maximum recursion depth reached while resolving sublayer assets.");
            return;
        }
        const sublayerAssetPaths = TinyUSDZComposer.extractSublayerAssetPaths(usdLayer);
        //console.log("extractSublayer", sublayerAssetPaths);

        await Promise.all(sublayerAssetPaths.map(async (sublayerPath) => {
            const [uri, binary] = await this.assetResolver_.resolveAsync(sublayerPath);
            //console.log("sublayerPath:", sublayerPath, "binary:", binary.byteLength, "bytes");

            //console.log("Loading sublayer:", sublayerPath);
            const sublayer = await this.usdLoader_.loadAsLayerAsync(sublayerPath);

            //console.log("sublayer:", sublayer);
            await this.resolveSublayerAssets(depth + 1, sublayer);

            this.assetMap_.set(sublayerPath, binary);
        }));
    }

    async progressiveComposition() {

        if (!this.usdLayer_) {
            throw new Error("TinyUSDZComposer: setLayer() is not called.");
        }

        if (!this.usdLoader_) {
            throw new Error("TinyUSDZComposer: setUSDLoader() is not called.");
        }

        this.usdLayer_.clearAssetSearchPaths();
        for (const path of this.assetSearchPaths_) {
            this.usdLayer_.addAssetSearchPath(path);
        }

        this.usdLayer_.setBaseWorkingPath(this.baseWorkingPath_);

        // LIVRPS
        // [x] local(subLayer)
        // [x] inherits
        // [x] variants
        // [x] references
        // [x ] payload
        // [ ] specializes

        // Resolving subLayer is recursive.
        await this.resolveSublayerAssets(/* depth */0, this.usdLayer_);

        for (const [uri, binary] of this.assetMap_.entries()) {
            //console.log("setAsset:", uri, "binary:", binary.byteLength, "bytes");
            this.usdLayer_.setAsset(uri, binary);
        }

        if (!this.usdLayer_.composeSublayers()) {
            throw new Error("Failed to compose sublayers:", this.usdLayer_.error());
        }


        // others are iterative.
        const kMaxIter = 16;

        for (let i = 0; i < kMaxIter; i++) {

            // In each composition operation, usd_layer may be modified(merged with sublayers, etc).
            // And we iterate until no more composition is needed.

            //console.log("iter", i);
            //console.log("hasReferences:", TinyUSDZComposer.hasReferences(this.usdLayer_));
            //console.log("hasPayload:", TinyUSDZComposer.hasPayload(this.usdLayer_));
            //console.log("hasInherits:", TinyUSDZComposer.hasInherits(this.usdLayer_));
            //console.log("hasVariants:", TinyUSDZComposer.hasVariants(this.usdLayer_));

            if (!TinyUSDZComposer.hasReferences(this.usdLayer_) &&
                !TinyUSDZComposer.hasPayload(this.usdLayer_) &&
                !TinyUSDZComposer.hasInherits(this.usdLayer_) &&
                !TinyUSDZComposer.hasVariants(this.usdLayer_)) {
                break;
            }

            // Inherits and variants does not involve asset loading.
            if (TinyUSDZComposer.hasInherits(this.usdLayer_)) {
                if (!this.usdLayer_.composeInherits()) {
                    throw new Error("Failed to compose inherits:", this.usdLayer_.error());
                }
            }

            if (TinyUSDZComposer.hasVariants(this.usdLayer_)) {
                if (!this.usdLayer_.composeVariants()) {
                    throw new Error("Failed to compose variants:", this.usdLayer_.error());
                }
            }

            if (TinyUSDZComposer.hasReferences(this.usdLayer_)) {
                const referencesAssetPaths = TinyUSDZComposer.extractReferencesAssetPaths(this.usdLayer_);

                await Promise.all(referencesAssetPaths.map(async (assetPath) => {
                    const [uri, binary] = await this.assetResolver_.resolveAsync(assetPath);
                    //console.log("referencesPath:", assetPath, "binary:", binary.byteLength, "bytes");

                    this.assetMap_.set(uri, binary);
                    this.usdLayer_.setAsset(uri, binary);
                }));

                //console.log("do composeReferences");
                if (!this.usdLayer_.composeReferences()) {
                    throw new Error("Failed to compose references:", this.usdLayer_.error());
                }
            }

            if (TinyUSDZComposer.hasPayload(this.usdLayer_)) {
                const payloadAssetPaths = TinyUSDZComposer.extractPayloadAssetPaths(this.usdLayer_);

                await Promise.all(payloadAssetPaths.map(async (assetPath) => {
                    const [uri, binary] = await this.assetResolver_.resolveAsync(assetPath);
                    //console.log("payloadAssetPath:", assetPath, "binary:", binary.byteLength, "bytes");

                    this.assetMap_.set(uri, binary);
                    this.usdLayer_.setAsset(uri, binary);
                }));

                if (!this.usdLayer_.composePayload()) {
                    throw new Error("Failed to compose payload:", usd_layer.error());
                }
            }
        }
    }



}



export { TinyUSDZComposer };
