Source: package/package.js

import { Reporter } from '../reporter/reporter.js';

import { buildMateMap } from '../lib.js';

import { Block } from './block/block.js';
import { ConnectorType } from './connector/connector_type.js';
import { Connector } from './connector/connector.js';
import { ThemeGroup } from './theme/theme_group.js';
import { Theme } from './theme/theme.js';
import { ConnectionType } from './connector/connection_type.js';
import { BlockCategory } from './block/block_category.js';
import { WrappedMaterial } from './material/wrapped_material.js';
import { WrappedMesh } from './mesh/wrapped_mesh.js';
import { MeshGroup } from './mesh/mesh_group.js';
import { WrappedImage } from './image/wrapped_image.js';
import { MaterialSet } from './material/material_set.js';
import { WrappedTexture } from './wrapped_texture.js';
import { MaterialCategoryType } from './material/material_category_type.js';
import { MaterialCategory } from './material/material_category.js';
import { ComponentTree } from '../component/component_tree.js';
import { BuildableComponent } from './component/buildable_component.js';
import { GeometryFile } from './geometry/geometry_file.js';
import { Geometry } from './geometry/geometry.js';
import { GLTF } from './mesh/gltf.js';
import { EXR } from './image/exr.js';
import { EnvironmentMap } from './environment_map.js';
import { PositionedMesh } from '../package/mesh/positioned_mesh.js'
import { PositionedMeshGroup } from '../package/mesh/positioned_mesh_group.js'
import { BlockCategoryType } from './block/block_category_type.js';
import { BlockSet } from './block/block_set.js';
import { Configuration } from '../configurator/configuration.js';

import { Project } from '../project.js';

connectors: Connector
/** 
 * @todo
 * Add jsdoc constructor
 * Analysis functions: 
 * - loadable component with missing sizes
 * - total loading size
 */

/** Loads, caches and augments product package data. */
class Package extends ComponentTree {

    /**
     * @param {Reporter} reporter
     */

    constructor(reporter, settings, instructions = {}) {

        // console.log(settings)

        super(
            reporter,
            settings,
            {
                import: Package.importInstructions,
                ...instructions
            }
        );

        
        this.addEvent( 'mate-map-update' );
        this.addEvent( 'connectors-by-type-update' );
        this.addEvent( 'material-sets-by-material-update' );

        // console.log(this.label, this._events);

        if ( instructions.loader ) {
            this.loader = instructions.loader;
        }

        this.on('change', ({cls}) => {
            // console.log( 'Change event, classes:', cls)
            if (cls.includes(Connector) || cls.includes(ConnectorType) || cls.includes( ConnectionType)) {
                this.updateConnectorTypeMateMap();
            }
            else if ([WrappedMaterial, MaterialSet, MaterialCategory].includes(cls)) {
                this.updateMaterialSetsByMaterial();
            }
        });

        this.updateConnectorTypeMateMap();

        this.updateMaterialSetsByMaterial();
    }

    static _exportName = {
        singular: 'package',
        plural: 'packages'
    };

    static importInstructions = [
        // {
        //     cls: PackageInfo
        // },
        // {
        //     cls: ConfigurationSettings
        // },
        // {
        //     cls: NavigationSettings
        // },
        {
            cls: WrappedImage
        },
        {
            cls: EXR
        },
        {
            cls: WrappedTexture
        },
        {
            cls: EnvironmentMap
        },
        {
            cls: MaterialCategoryType
        },
        {
            cls: MaterialCategory
        },
        {
            cls: WrappedMaterial
        },
        {
            cls: MaterialSet
        },
        {
            cls: GeometryFile
        },
        {
            cls: GLTF
        },
        {
            cls: Geometry
        },
        {
            cls: WrappedMesh
        },
        {
            cls: PositionedMesh
        },
        {
            cls: MeshGroup
        },
        {
            cls: PositionedMeshGroup
        },
        {
            cls: ThemeGroup
        },
        {
            cls: Theme
        },
        {
            cls: ConnectorType
        },
        {
            cls: Connector
        },
        {
            cls: BlockCategoryType
        },
        {
            cls: BlockCategory
        },
        {
            cls: Block
        },
        // {
        //     cls: PositionedBlock
        // },
        {
            cls: BlockSet
        },
        {
            cls: ConnectionType
        },
        {
            cls: Configuration
        }
    ];

    // link only the blocks, and the components that are not dependencies 
    // not all the "settings"

    _autoLink() {

        const dependUpon = ['blocks', 'connectionTypes'];

        const blockSettings = {};
        
        for ( let [ setting, val ] of Object.entries(this._settings)) {
            if ( dependUpon.includes(setting)) {
                blockSettings[setting]  = val;
            }
            else if ( Array.isArray(val) ) {
                // console.log('export only dep', setting, val)
                this._exportOnlyDependencies[ setting ] = val;
            }
        }

        // console.info(blockSettings);

        super._autoLink(
            blockSettings,
            BuildableComponent.autogenIntegralInstruction(blockSettings)
        );
    }

    /** 
     * @typedef {Object} ConnectorTypeMates
     * @property {Array<ConnectionType>} connectionTypes - ConnectionTypes in which this connector type is a mate
     * @property {Array<ConnectorType>} mateConnectorTypes - ConnectorTypes that can mate with this connector type
     * @property {Array<Connector>} mateConnectors - Connectors that can mate with this connector type
     */

    /**
     * Object that has a list of potential mate type per connector types
     * and a reference to all relevant AllowedConnectionTypes per potential
     * mate type
     * @type {Map.<ConnectorType,ConnectorTypeMates>}
     */

    connectorTypeMateMap = new Map();

    updateConnectorTypeMateMap() {
        this.report({ msg: 'Updating mate map' });
        // console.warn( 'Update mate map', this.settings.connectorTypes)
        this.connectorTypeMateMap = buildMateMap(this._settings);
        // console.log( 'emit mate-map-update', Object.keys( this._events[ 'mate-map-update']), this.label);
        this.emit('mate-map-update', this.connectorTypeMateMap);
        // console.log('emit', this.label, this._events, this._events['mate-map-update'])
        return this.connectorTypeMateMap;
    }


    /**
     * @type {Object<ConnectorType,Array<Connector>>}
     */

    connectorsByType = {};

    updateConnectorsByType() {
        for (let type of this.connectorTypes) {
            this.connectorsByType = this.connectors.filter(con => con.settings.type === type);
        }
        this.emit('connectors-by-type-update', this.connectorsByType);
    }


    /**
     * The material sets in which each material is present
     * @type {Object<WrappedMaterial,Array<MaterialSet>>}
     */

    materialSetsByMaterial = {};

    updateMaterialSetsByMaterial() {
        const index = {};
        for (let material of this.materials) {
            index[material] = [];
            for (let materialSet of this.materialSets) {
                if (materialSet.has(material)) {
                    index[material].push(materialSet);
                }
            }
        }
        this.materialSetsByMaterial = index;
        this.emit('material-sets-by-material-update', this.materialSetsByMaterial);
    }





    async _build(part, quality, dependencies) {

        // console.log(dependencies)

        this._status[part][quality].setState('loading');

        switch (part) {

            case 'UI':

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

            case 'main':
                // console.log(dependencies)
                this._setContent(part, quality, dependencies);
                break;

            default:

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

        return this;
    }
}

export { Package }