import { AnimationMixer, Group, PCFSoftShadowMap, sRGBEncoding } from 'three';

import Vector2 from '../../core/geom/Vector2.js';
import { computeFileExtension } from "../../core/path/computeFileExtension.js";
import { advanceAnimation } from "../../engine/ecs/systems/AnimationSystem.js";
import { WebGLRendererPool } from "../../engine/graphics/render/RendererPool.js";
import { makeMeshPreviewScene } from "../../engine/graphics/util/makeMeshPreviewScene.js";
import { PointerDevice } from '../../engine/input/devices/PointerDevice.js';

import dom from "../DOM.js";
import View from "../View.js";

const mapExtension2Mime = {
    gltf: "model/gltf+json",
    glb: "model/gltf",
    /**
     * @deprecated no longer supported, please don't use
     */
    json: "three.js"
};

class MeshPreview extends View {
    /**
     *
     * @param {string} url
     * @param {AssetManager} assetManager
     * @param hooks
     * @param {boolean} [allowRotationY = true]
     * @param {boolean} [allowRotationX = true]
     * @param {string|null} [animation = 'Idle'] Animation to play
     * @constructor
     */
    constructor({
                    url,
                    assetManager,
                    hooks,
                    allowRotationY = true,
                    allowRotationX = true,
                    animation = 'Idle'
                }) {
        super();

        this.renderer = null;
        this.scene = null;
        this.camera = null;


        if (typeof url !== "string") {
            let s;

            try {
                s = JSON.stringify(url);
            } catch (e) {
                s = "$serialization error$";
            }

            throw new Error(`Mesh URL was expected to be a string, instead was ${typeof url}: ${s}`);
        }

        const extension = computeFileExtension(url);

        if (extension.length === 0) {
            throw new Error(`No file extension on url '${url}'`);
        }

        if (!mapExtension2Mime.hasOwnProperty(extension)) {
            throw new Error(`No known mime-type for file extension '${extension}'`);
        }

        const mimeType = mapExtension2Mime[extension];


        this.el = dom('div').addClass('ui-mesh-preview').el;

        const size = this.size;

        function addInteraction(el, mesh) {
            const pointer = new PointerDevice(el);
            pointer.start();
            const origin = new Vector2();
            const prevPosition = new Vector2();
            pointer.on.dragStart.add(function (p, event) {
                event.stopPropagation();
                origin.copy(p);
                prevPosition.copy(p);
            });
            pointer.on.drag.add(function (p, o, last, event) {
                event.stopPropagation();
                const delta = p.clone().sub(prevPosition);
                prevPosition.copy(p);
                //
                const PI2 = (2 * Math.PI);
                const angleDelta = new Vector2(PI2, PI2).divide(size).multiply(delta);
                if (allowRotationY !== false) {
                    // noinspection JSSuspiciousNameCombination
                    mesh.rotation.y += angleDelta.x;
                }
                if (allowRotationX !== false) {
                    // noinspection JSSuspiciousNameCombination
                    mesh.rotation.x += angleDelta.y;
                }
            });
        }

        this.__addInteraction = addInteraction;

        const self = this;

        this.pContents = new Promise(function (resolve, reject) {
            assetManager.get({
                path: url, type: mimeType, callback: function (asset) {
                    const mesh = asset.create();
                    const preview = makeMeshPreviewScene(mesh, size, {
                        x0: 0,
                        y0: 0,
                        x1: 1,
                        y1: 1
                    });
                    const camera = preview.camera;
                    const scene = preview.scene;


                    //wrap mesh so it stays centered relative to bb
                    const group = new Group();

                    const wrapAll = false;
                    if (wrapAll) {
                        const objects = scene.children.slice();
                        scene.children = [];

                        objects.forEach(function (o) {
                            group.add(o);
                        });
                    } else {
                        scene.remove(mesh);
                        group.add(mesh);
                    }


                    scene.add(group);

                    self.camera = camera;
                    self.scene = scene;


                    if (hooks !== undefined) {
                        if (typeof hooks.meshAdded === "function") {
                            hooks.meshAdded(mesh);
                        }
                        if (typeof hooks.sceneConstructed === "function") {
                            hooks.sceneConstructed(scene, mesh, camera, group);
                        }
                    }

                    mesh.rotation.y = -Math.PI / 6;

                    //

                    function playAnimation(animationName) {

                        const mixer = new AnimationMixer(mesh);

                        let clipAction = null;

                        const animations = asset.animations;
                        if (animations !== undefined) {
                            self.__animationMixer = mixer;
                            for (let i = 0; i < animations.length; i++) {
                                const animation = animations[i];
                                if (animation.name === animationName) {
                                    const root = null;
                                    clipAction = mixer.clipAction(animation, root);
                                    //bail, correct animation found
                                    break;
                                }
                            }
                        }

                        if (clipAction !== null) {
                            clipAction.play();
                        }
                    }


                    if (animation !== null) {
                        playAnimation(animation);
                    }

                    resolve({
                        group
                    });

                }, failure: function () {
                    console.error(arguments);
                    reject(arguments);
                }
            });
        });

        this.on.linked.add(this.__update_renderer_size, this);
        this.bindSignal(this.size.onChanged, this.__update_renderer_size, this);
    }

    __update_renderer_size() {
        if (this.renderer !== null) {
            this.renderer.setSize(this.size.x, this.size.y);
            this.camera.aspect = this.size.x / this.size.y;
            this.camera.updateProjectionMatrix();
        }
    }

    link() {
        super.link();

        this.isRendering = true;
        const self = this;

        const size = self.size;

        const renderer = WebGLRendererPool.global.get();
        renderer.outputEncoding = sRGBEncoding;
        renderer.setClearColor(0xffffff, 0);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(size.x, size.y);
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = PCFSoftShadowMap;

        self.renderer = renderer;

        self.pContents.then(function (contents) {
            if (renderer.domElement !== null) {
                self.__addInteraction(renderer.domElement, contents.group);
            }
        });

        self.el.appendChild(renderer.domElement);

        let t = Date.now();

        function update() {
            const now = Date.now();
            const timeDelta = (now - t) / 1000;
            t = now;

            if (self.isRendering) {
                requestAnimationFrame(update);
                self.render();
                if (self.__animationMixer !== undefined) {
                    advanceAnimation(self.__animationMixer, timeDelta);
                }
            }
        }

        update();
    }

    unlink() {
        super.unlink();

        this.isRendering = false;

        const renderer = this.renderer;
        if (renderer !== null) {
            try {
                this.el.removeChild(renderer.domElement);
            } catch (e) {
                console.error("Failed to remove renderer's dom element", e);
            }
            WebGLRendererPool.global.release(this.renderer);
            this.renderer = null;
        }
    }

    render() {
        if (this.renderer !== null && this.scene !== null && this.camera !== null) {
            this.renderer.render(this.scene, this.camera);
        }
    }
}


export default MeshPreview;
