/* eslint-disable */
// @ts-nocheck
// r152 jsm/MTLLoader.js
import {
    Color,
    DefaultLoadingManager,
    FileLoader,
    FrontSide,
    Loader,
    LoaderUtils,
    MeshPhongMaterial,
    RepeatWrapping,
    SRGBColorSpace,
    TextureLoader,
    Vector2,
} from 'three';

/**
 * Loads a Wavefront .mtl file specifying materials
 */

class MTLLoader2 extends Loader {

    constructor(manager) {

        super(manager);

    }

    /**
     * Loads and parses a MTL asset from a URL.
     *
     * @param {String} url - URL to the MTL file.
     * @param {Function} [onLoad] - Callback invoked with the loaded object.
     * @param {Function} [onProgress] - Callback for download progress.
     * @param {Function} [onError] - Callback for download errors.
     *
     * {@link setPath} {@link setResourcePath}
     *
     * @note In order for relative texture references to resolve correctly
     * you must call setResourcePath() explicitly prior to load.
     */
    load(url, onLoad, onProgress, onError) {

        const scope = this;

        const path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;

        const loader = new FileLoader(this.manager);
        loader.setPath(this.path);
        loader.setRequestHeader(this.requestHeader);
        loader.setWithCredentials(this.withCredentials);
        loader.load(url, function (text) {

            try {

                onLoad(scope.parse(text, path));

            } catch (e) {

                if (onError) {

                    onError(e);

                } else {

                    console.error(e);

                }

                scope.manager.itemError(url);

            }

        }, onProgress, onError);

    }

    setMaterialOptions(value) {

        this.materialOptions = value;
        return this;

    }

    /**
     * Parses a MTL file.
     *
     * @param {String} text - Content of MTL file
     * @return {MaterialCreator}
     *
     * {@link setPath} {@link setResourcePath}
     *
     * @note In order for relative texture references to resolve correctly
     * you must call setResourcePath() explicitly prior to parse.
     */
    parse(text, path) {

        const lines = text.split('\n');
        let info = {};
        const delimiter_pattern = /\s+/;
        const materialsInfo = {};

        for (let i = 0; i < lines.length; i++) {

            let line = lines[i];
            line = line.trim();

            if (line.length === 0 || line.charAt(0) === '#') {

                // Blank line or comment ignore
                continue;

            }

            const pos = line.indexOf(' ');

            let key = (pos >= 0) ? line.substring(0, pos) : line;
            key = key.toLowerCase();

            let value = (pos >= 0) ? line.substring(pos + 1) : '';
            value = value.trim();

            if (key === 'newmtl') {

                // New material

                info = {name: value};
                materialsInfo[value] = info;

            } else {

                if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {

                    const ss = value.split(delimiter_pattern, 3);
                    info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];

                } else {

                    info[key] = value;

                }

            }

        }

        const materialCreator = new MaterialCreator(this.resourcePath || path, this.materialOptions);
        materialCreator.setCrossOrigin(this.crossOrigin);
        materialCreator.setManager(this.manager);
        materialCreator.setMaterials(materialsInfo);
        return materialCreator;

    }

}

/**
 * Create a new MTLLoader2.MaterialCreator
 * @param baseUrl - Url relative to which textures are loaded
 * @param options - Set of options on how to construct the materials
 *                  side: Which side to apply the material
 *                        FrontSide (default), THREE.BackSide, THREE.DoubleSide
 *                  wrap: What type of wrapping to apply for textures
 *                        RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
 *                                Default: false, assumed to be already normalized
 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
 *                                  Default: false
 * @constructor
 */

class MaterialCreator {

    constructor(baseUrl = '', options = {}) {

        this.baseUrl = baseUrl;
        this.options = options;
        this.materialsInfo = {};
        this.materials = {};
        this.materialsArray = [];
        this.nameLookup = {};

        this.crossOrigin = 'anonymous';

        this.side = (this.options.side !== undefined) ? this.options.side : FrontSide;
        this.wrap = (this.options.wrap !== undefined) ? this.options.wrap : RepeatWrapping;

    }

    setCrossOrigin(value) {

        this.crossOrigin = value;
        return this;

    }

    setManager(value) {

        this.manager = value;

    }

    setMaterials(materialsInfo) {

        this.materialsInfo = this.convert(materialsInfo);
        this.materials = {};
        this.materialsArray = [];
        this.nameLookup = {};

    }

    convert(materialsInfo) {

        if (!this.options) return materialsInfo;

        const converted = {};

        for (const mn in materialsInfo) {

            // Convert materials info into normalized form based on options

            const mat = materialsInfo[mn];

            const covmat = {};

            converted[mn] = covmat;

            for (const prop in mat) {

                let save = true;
                let value = mat[prop];
                const lprop = prop.toLowerCase();

                switch (lprop) {

                    case 'kd':
                    case 'ka':
                    case 'ks':

                        // Diffuse color (color under white light) using RGB values

                        if (this.options && this.options.normalizeRGB) {

                            value = [value[0] / 255, value[1] / 255, value[2] / 255];

                        }

                        if (this.options && this.options.ignoreZeroRGBs) {

                            if (value[0] === 0 && value[1] === 0 && value[2] === 0) {

                                // ignore

                                save = false;

                            }

                        }

                        break;

                    default:

                        break;

                }

                if (save) {

                    covmat[lprop] = value;

                }

            }

        }

        return converted;

    }

    async preload() {

        for (const mn in this.materialsInfo) {

            await this.create(mn);

        }

    }

    getIndex(materialName) {

        return this.nameLookup[materialName];

    }

    async getAsArray() {

        let index = 0;

        for (const mn in this.materialsInfo) {

            this.materialsArray[index] = await this.create(mn);
            this.nameLookup[mn] = index;
            index++;

        }

        return this.materialsArray;

    }

    async create(materialName) {

        if (this.materials[materialName] === undefined) {

            await this.createMaterial_(materialName);

        }

        return this.materials[materialName];

    }

    async createMaterial_(materialName) {

        // Create material

        const scope = this;
        const mat = this.materialsInfo[materialName];
        const params = {

            name: materialName,
            side: this.side

        };

        function resolveURL(baseUrl, url) {

            if (typeof url !== 'string' || url === '')
                return '';

            // Absolute URL
            if (/^https?:\/\//i.test(url)) return url;

            return baseUrl + url;

        }

        async function setMapForType(mapType, value) {

            if (params[mapType]) return; // Keep the first encountered texture

            const texParams = scope.getTextureParams(value, params);
            return new Promise((resolve, reject) => {
                let resolved = false;
                let res = ()=> (!resolved && (resolved = true) && resolve())
                const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url), undefined, (map)=>{
                    params[mapType] = map;
                    res()
                }, undefined, res);
                setTimeout(res, 50); // timeout.

                map.repeat.copy(texParams.scale);
                map.offset.copy(texParams.offset);

                map.wrapS = scope.wrap;
                map.wrapT = scope.wrap;

                if ( mapType === 'map' || mapType === 'emissiveMap' ) {

                    map.colorSpace = SRGBColorSpace;

                }

            })

        }

        /**
         *
         * @type {string[]}
         */
        const propList = Array.from(Object.keys(mat?mat:{}));

        let hasOpacity = propList.includes('d') || propList.includes('D');

        for (const prop of propList) {
            const value = mat[prop];
            let n;

            if (value === '') continue;

            switch (prop.toLowerCase()) {

                // Ns is material specular exponent

                case 'kd':

                    // Diffuse color (color under white light) using RGB values

                    params.color = new Color().fromArray( value ).convertSRGBToLinear();

                    break;

                case 'ks':

                    // Specular color (color when light is reflected from shiny surface) using RGB values
                    params.specular = new Color().fromArray( value ).convertSRGBToLinear();

                    break;

                case 'ke':

                    // Emissive using RGB values
                    params.emissive = new Color().fromArray( value ).convertSRGBToLinear();

                    break;

                case 'map_kd':

                    // Diffuse texture map

                    await setMapForType('map', value);

                    break;

                case 'map_ks':

                    // Specular map

                    await setMapForType('specularMap', value);

                    break;

                case 'map_ke':

                    // Emissive map

                    await setMapForType('emissiveMap', value);

                    break;

                case 'norm':

                    await setMapForType('normalMap', value);

                    break;

                case 'map_bump':
                case 'bump':

                    // Bump texture map

                    await setMapForType('bumpMap', value);

                    break;

                case 'map_d':

                    // Alpha map

                    await setMapForType('alphaMap', value);
                    params.transparent = true;

                    break;

                case 'ns':

                    // The specular exponent (defines the focus of the specular highlight)
                    // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.

                    params.shininess = parseFloat(value);

                    break;

                case 'd':
                    n = parseFloat(value);

                    if (n < 1) {

                        params.opacity = n;
                        params.transparent = true;

                    }

                    break;

                case 'tr': // is this translucency?
                    if (hasOpacity) break; // ignore transparency if opacity is present

                    n = parseFloat(value);

                    if (this.options && this.options.invertTrProperty) n = 1 - n;

                    if (n > 0) {

                        params.opacity = 1 - n;
                        params.transparent = true;

                    }

                    break;

                default:
                    break;

            }

        }

        this.materials[materialName] = new MeshPhongMaterial(params);
        return this.materials[materialName];

    }

    getTextureParams(value, matParams) {

        const texParams = {

            scale: new Vector2(1, 1),
            offset: new Vector2(0, 0)

        };

        const items = value.split(/\s+/);
        let pos;

        pos = items.indexOf('-bm');

        if (pos >= 0) {

            matParams.bumpScale = parseFloat(items[pos + 1]);
            items.splice(pos, 2);

        }

        pos = items.indexOf('-s');

        if (pos >= 0) {

            texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
            items.splice(pos, 4); // we expect 3 parameters here!

        }

        pos = items.indexOf('-o');

        if (pos >= 0) {

            texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
            items.splice(pos, 4); // we expect 3 parameters here!

        }

        texParams.url = items.join(' ').trim();
        return texParams;

    }

    loadTexture(url, mapping, onLoad, onProgress, onError) {

        const manager = (this.manager !== undefined) ? this.manager : DefaultLoadingManager;
        let loader = manager.getHandler(url);

        if (loader === null) {

            loader = new TextureLoader(manager);

        }

        if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);

        const texture = loader.load(url, onLoad, onProgress, onError);

        if (mapping !== undefined) texture.mapping = mapping;

        return texture;

    }

}

export {MTLLoader2, type MaterialCreator};
