import { THREE } from "threebox-plugin";
import merge from "lodash.merge";
import vignettingVert from "./shaders/vignetting.vertex.glsl";
import vignettingFrag from "./shaders/vignetting.fragment.glsl";
import * as defaultValue from "../values";

export default class Effect {
    constructor(map, renderTarget, options) {
        this.map = map;
        this.canvas = map._canvas;
        this.hasFocused = false; // 当前是否为聚焦状态
        this.vignettingOptions = merge(
            defaultValue.vignetting,
            options.vignetting
        );
        this.SCENE_WIDTH = this.map.transform.width;
        this.SCENE_HEIGHT = this.map.transform.height;
        this.renderTarget = renderTarget;
        this.prevTime = 0;
        this.delta = 0;
        this.frameId = null;

        this.lightHeightParams = {
            animating: false,
            enable: defaultValue.lightFocus.enable,
            duration: defaultValue.lightFocus.duration,
            step: 0,
            to: this.vignettingOptions.lightHeight,
            cur: this.vignettingOptions.lightHeight,
            from: this.vignettingOptions.lightHeight,
            prev: this.vignettingOptions.lightHeight,
        };
        this.initScene();
        this.initCamera();
        this.initRenderer();
        this.initObject();
        this.render();

        this.map.on("resize", this.onResize.bind(this));
    }

    onResize() {
        cancelAnimationFrame(this.frameId);
        this.SCENE_WIDTH = this.map.transform.width;
        this.SCENE_HEIGHT = this.map.transform.height;

        this.renderer.setSize(this.SCENE_WIDTH, this.SCENE_HEIGHT);
        this.camera.left = this.SCENE_WIDTH / -2;
        this.camera.right = this.SCENE_WIDTH / 2;
        this.camera.top = this.SCENE_HEIGHT / 2;
        this.camera.bottom = this.SCENE_HEIGHT / -2;
        this.camera.updateProjectionMatrix();

        this.mesh.geometry.dispose();
        this.mesh.material.dispose();
        this.scene.remove(this.mesh);
        this.initObject();
        this.render();
    }

    focus(options) {
        if (this.hasFocused) return;
        const enable = options.enable;
        this.lightHeightParams.enable = enable;
        if (!enable) return;
        const duration = options.duration;
        this.lightHeightParams.animating = true;
        this.lightHeightParams.duration = duration;
        this.lightHeightParams.from = this.lightHeightParams.cur;
        this.lightHeightParams.prev = this.lightHeightParams.from;
        this.lightHeightParams.to = options.height;
        const heightDiff = Math.abs(
            this.lightHeightParams.from - this.lightHeightParams.to
        );
        this.lightHeightParams.step = heightDiff / duration;
        this.hasFocused = true;
    }

    unfocus() {
        if (!this.hasFocused) return;
        this.hasFocused = false;
        this.lightHeightParams.animating = true;
        this.lightHeightParams.from = this.lightHeightParams.cur;
        this.lightHeightParams.to = this.lightHeightParams.prev;
        const heightDiff = Math.abs(
            this.lightHeightParams.from - this.lightHeightParams.to
        );
        this.lightHeightParams.step =
            heightDiff / this.lightHeightParams.duration;
    }

    initDevHelpers() {
        this.axesHelper = new THREE.AxesHelper(200);
        this.scene.add(this.axesHelper);
    }

    initScene() {
        this.scene = new THREE.Scene();
    }

    initRenderer() {
        this.renderer = new THREE.WebGLRenderer({
            antialias: true,
            canvas: this.canvas,
        });
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setClearColor(0x000000, 1);
        this.renderer.setSize(this.SCENE_WIDTH, this.SCENE_HEIGHT);
    }

    initCamera() {
        this.camera = new THREE.OrthographicCamera(
            this.SCENE_WIDTH / -2,
            this.SCENE_WIDTH / 2,
            this.SCENE_HEIGHT / 2,
            this.SCENE_HEIGHT / -2,
            1,
            1000
        );
        this.camera.setViewOffset(
            this.SCENE_WIDTH,
            this.SCENE_HEIGHT,
            0,
            0,
            this.SCENE_WIDTH,
            this.SCENE_HEIGHT
        );
        this.camera.position.set(0, 100, 0);
        this.camera.lookAt(new THREE.Vector3(0, 0, 0));
        this.scene.add(this.camera);
    }

    render(time) {
        this.frameId = requestAnimationFrame(this.render.bind(this));
        if (!this.prevTime) {
            this.updateDelta(time);
            return;
        }
        this.updateDelta(time);
        this.updateLightHeight();
        this.canvasTexture.needsUpdate = true;
        this.renderer.render(this.scene, this.camera);
    }

    updateLightHeight() {
        if (!this.lightHeightParams.animating || !this.lightHeightParams.enable)
            return;
        let symbol =
            this.lightHeightParams.from > this.lightHeightParams.to ? -1 : 1;

        if (
            symbol === -1 &&
            this.lightHeightParams.cur <= this.lightHeightParams.to
        ) {
            this.lightHeightParams.animating = false;
            this.lightHeightParams.cur = this.lightHeightParams.to;
            this.mat.uniforms.u_lightPosition.value = new THREE.Vector3(
                0,
                this.lightHeightParams.cur,
                0
            );
            return;
        }

        if (
            symbol === 1 &&
            this.lightHeightParams.cur >= this.lightHeightParams.to
        ) {
            this.lightHeightParams.animating = false;
            this.lightHeightParams.cur = this.lightHeightParams.to;
            this.mat.uniforms.u_lightPosition.value = new THREE.Vector3(
                0,
                this.lightHeightParams.cur,
                0
            );
            return;
        }

        this.lightHeightParams.cur +=
            this.delta * this.lightHeightParams.step * symbol;
        this.mat.uniforms.u_lightPosition.value = new THREE.Vector3(
            0,
            this.lightHeightParams.cur,
            0
        );
    }

    updateDelta(time) {
        this.delta = time - this.prevTime;
        this.prevTime = time;
    }

    initObject() {
        this.canvasTexture = new THREE.CanvasTexture(this.renderTarget);

        const geo = new THREE.PlaneGeometry(
            this.SCENE_WIDTH,
            this.SCENE_HEIGHT
        );
        this.mat = new THREE.ShaderMaterial({
            uniforms: {
                u_mapbox: { value: this.canvasTexture },
                u_lightColor: {
                    value: new THREE.Color(this.vignettingOptions.lightColor),
                },
                u_lightPosition: {
                    value: new THREE.Vector3(0, this.lightHeightParams.from, 0),
                },
                u_strength: {
                    value: this.vignettingOptions.strength,
                },
                u_vignettingEnable: {
                    value: this.vignettingOptions.enable ? 1 : 0,
                },
            },
            fragmentShader: vignettingFrag,
            vertexShader: vignettingVert,
        });
        this.mesh = new THREE.Mesh(geo, this.mat);
        this.mesh.rotateX(-Math.PI / 2);
        this.scene.add(this.mesh);
    }

    destroy() {
        this.renderer.dispose();
        this.mesh.geometry.dispose();
        this.mesh.material.dispose();
        this.scene.remove(this.mesh);
        this.scene.remove(this.camera);
        cancelAnimationFrame(this.frameId);
    }
}
