import { CubeTextureLoader, LinearFilter, PMREMGenerator, sRGBEncoding, TextureLoader, UnsignedByteType } from "three";
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
import { HDRCubeTextureLoader } from "three/examples/jsm/loaders/HDRCubeTextureLoader.js";
import { noop } from "../../../../core/function/noop.js";
import { computeFileExtension } from "../../../../core/path/computeFileExtension.js";

/**
 *
 * @param {string[]|string} paths
 * @return {Promise<THREE.CubeTexture>}
 */
function load_cube_texture(paths) {
    // piece-wise cubemap, a collection of images for each face
    const cubeTextureLoader = new CubeTextureLoader();
    return new Promise((resolve, reject) => {
        cubeTextureLoader.load(
            paths,
            texture => {
                texture.magFilter = LinearFilter;
                texture.encoding = sRGBEncoding;
                resolve(texture);
            },
            noop,
            reject
        );
    });
}

/**
 *
 * @param {string[]|string} paths
 * @return {Promise<THREE.CubeTexture>}
 */
function load_hdr_cube_texture(paths) {
    // piece-wise cubemap, a collection of images for each face
    const cubeTextureLoader = new HDRCubeTextureLoader();
    return new Promise((resolve, reject) => {
        cubeTextureLoader.load(
            paths,
            texture => {
                resolve(texture);
            },
            noop,
            reject
        );
    });
}

/**
 *
 * @param {string} path
 * @return {Promise<THREE.Texture>}
 */
function load_exr_texture(path) {
    return new Promise((resolve, reject) => {
        new EXRLoader()
            .setDataType(UnsignedByteType)
            .load(path, texture => {
                resolve(texture);
            }, noop, reject);
    })
}

/**
 *
 * @param {string} path
 * @return {Promise<THREE.Texture>}
 */
function load_standard_texture(path) {
    return new Promise((resolve, reject) => {
        new TextureLoader()
            .load(path, texture => {
                texture.encoding = sRGBEncoding;
                resolve(texture);
            }, noop, reject);
    })
}

/**
 *
 * @param {string|string[]} path
 * @param {THREE.WebGLRenderer} [renderer]
 * @param {boolean} [original]
 * @param {boolean} [filtered]
 * @returns {{original?:THREE.CubeTexture, filtered?: THREE.CubeTexture}}
 */
export async function load_environment_map({ path, renderer, original = false, filtered = true }) {
    /**
     * @type {string} 'e' or 'c'
     */
    let type;

    /**
     * @type {THREE.WebGLCubeRenderTarget}
     */
    let cube_rt;

    /**
     * @type {THREE.Texture|THREE.CubeTexture}
     */
    let texture;

    if (Array.isArray(path)) {
        const ext = computeFileExtension(path[0]);

        if (ext === 'hdr') {
            texture = await load_hdr_cube_texture(path);
        } else {
            texture = await load_cube_texture(path);
        }

        type = 'c';

    } else if (typeof path === 'string') {
        // single cubemap
        const extension = computeFileExtension(path).toLowerCase();
        if (extension === 'exr') {
            texture = await load_exr_texture(path);
        } else if (extension === 'jpg') {
            texture = await load_standard_texture(path);
        } else {
            // unsupported extension
            throw new Error(`Unsupported cube map texture extension '${extension}'`);
        }

        type = 'e';

    } else {
        throw new Error(`Unsupported parameter format: '${path}'`);
    }

    const result = {};

    if (filtered) {

        if (renderer === undefined) {
            throw new Error('renderer argument not supplied, expected a WebGLRenderer');
        }

        const pmremGenerator = new PMREMGenerator(renderer);

        if (type === 'c') {
            cube_rt = pmremGenerator.fromCubemap(texture);
        } else if (type === 'e') {
            cube_rt = pmremGenerator.fromEquirectangular(texture);
        } else {
            throw new Error(`Unsupported cubemap type '${type}'`);
        }

        pmremGenerator.dispose();

        result.filtered = cube_rt.texture;
    }

    if (original) {
        result.original = texture;
    } else {
        // cleanup
        texture.dispose();
    }

    return result;
}
