Source: package/material/material_set.js

import { BuildableComponent } from '../component/buildable_component.js';
import { Reporter } from '../../reporter/reporter.js';
import { UUIDRegex, checkPropTypes } from '../../lib.js';
import { MaterialCategory } from './material_category.js';
import { WrappedMaterial } from './wrapped_material.js';
import { WrappedImage } from '../image/wrapped_image.js';
import { Project } from '../../project.js';


/**
 * Material set
 * Component is not auto-linked and uses the Package to formulate the set of materials
 * from material categories and material references. As "MaterialSet" is loaded after
 * "Material" in the Package, this isn't a problem. Otherwise, building depends
 * on Package change events.
 */

class MaterialSet extends BuildableComponent {


    /**
     * @param {Reporter} reporter
     * @param {Object} settings
     * @param {UUID} [settings.id]
     * @param {string} settings.name
     * @param {WrappedImage} [settings.thumbnail]
     * @param {Array<MaterialCategory>} [settings.categories]
     * @param {Array<WrappedMaterial>} [settings.materials]
     */
    

    constructor(reporter, settings) {

        // this component should not be auto linked in parent class

        super(reporter, settings, { autoLinkDependencies: false });


        checkPropTypes(
            settings,
            {
                name: 'string',
            },
            {
                categories: val => {
                    return Array.isArray(val) && val.every(entry => entry instanceof MaterialCategory)
                },
                materials: val => {
                    return Array.isArray(val) && val.every(entry => entry instanceof WrappedMaterial)
                },
                id: UUIDRegex,
            }
        );


        // this.exportName = 'materialSets';

        this._dependencies.main = {};

        // material set should not have dependencies
        // or the number of alternative loading paths
        // will explode (as ComponentLoader will "see"
        // this as alternatives)

        // thinking further about this, it is obvious
        // that building the set should not lead to 
        // building every material in it. It is almost
        // always undesirable to build "every" material

        for ( let material of settings.materials || [] ) {
            this.addExportDependency( `material-${material.id}`, material );
        }

        for ( let category of settings.categories || [] ) {
            this.addExportDependency( `category-${category.name}`, category );
        }

        // no!
        // copyProps({
        //     from: settings,
        //     into: this._dependencies.main,
        //     optional: [ 'categories', 'materials' ]
        // });

        if ( settings.thumbnail ) {
            this._dependencies.UI = [ settings.thumbnail ];
        }

        this.collectMaterials();


        // immediately build the "first version" with only the 
        // directly referenced materials (so, without the categories)

        // this._build('main');
    }


    static _exportName = {
        singular: 'materialSet',
        plural: 'materialSets'
    };


    _onTreeSet() {
        const component = this;

        this._tree.on(
            'change',
            ({ cls = [] }) => {
                if ( cls.includes( WrappedMaterial ) || cls.includes( MaterialCategory ) ) {
                    component.report({ msg: 'Materials or material categories changed in package. Rebuilding.' });
                    component._rebuild();
                }
            },
            this.id
        );
        
        this._rebuild();
    }

    _onTreeUnset() {
        if (this._tree.hasListener('change', this.id)) {
            this._tree.removeListener('change', this.id);
        }
    }


    /**
     * All materials that are in this set. (Available after building)
     * @type {Array<WrappedMaterial>} 
     */

    allMaterials = [];


    allMaterialsIndexed = {};


    /**
     * Check whether a material is in the set
     * @param {WrappedMaterial} material
     * @returns {boolean}
     */

    has(material) {
        return !! this.allMaterialsIndexed[material.id];
    }


    /**
     * Number of materials in set
     * @returns {number}
     */

    size(quality = 'medium') {
        return this.allMaterials.length;
    }


    collectMaterials() {
        const materialsInSet = this._tree
            ? this._tree.materials
                .filter(mat =>
                    (mat.settings.categories || []).some(cat => (this.settings.categories || []).includes(cat))
                    ||
                    this._settings.materials.indexOf( mat ) > -1
                )
            : this._settings.materials || []
            ;

        this.allMaterials = materialsInSet;

        this.allMaterialsIndexed = {};

        for ( let material of materialsInSet ) {
            this.allMaterialsIndexed[ material.id ] = material;
        }

        this.report({ msg: `size ${materialsInSet.length} materials` });
    }


    /**
     * @protected 
     */

    async _build(part, quality) {

        switch (part) {
            
            case 'UI':
                if (this._settings.thumbnail) {
                    this._setContent('UI', quality, this._settings.thumbnail.content.main[quality]);
                }
                else {
                    this._setContent('UI', quality, Project.defaultImages.missing.cloneNode(true));
                }
                break;

            case 'main':

                this.collectMaterials();

                this._setContent('main', quality, this.allMaterials);

                break;
                
            default:

                this._setContent(part, quality, null);
                
                break;
        }

        return this;
    }
}

export { MaterialSet };