import { BatchRenderer } from "./plugins/BatchRenderer";
import { SystemRenderer } from "./SystemRenderer";
import { RENDERER_TYPE, BLEND_MODES } from "../const";
import { createContext, GLShader, VertexArrayObject } from "../../glCore";
import ObjectRenderer from "./webgl/ObjectRenderer";
import RenderTarget from "./renderTarget/RenderTarget";
import TextureManager from "./managers/TextureManager";
import TextureGarbageCollector from "./managers/TextureGarbageCollector";
import { SCALE_MODES, /*devicePixelRatio*/ } from "../const";
// import RenderTexture from "../texture/RenderTexture";
import { Matrix, Transform } from "../math";
import WebGLState from "./webgl/WebGLState";
import BatchManager from "./managers/BatchManager";
import MaskManager from "./managers/MaskManager";
import StencilManager from "./managers/StencilManager";
import { DisplayObject } from "../display/DisplayObject";
import { hex2string, hex2rgb } from "../utils";
import { Container } from "../display";
import RenderTexture from "../texture/RenderTexture";
import BaseRenderTexture from "../texture/BaseRenderTexture";
import FilterManager from "./managers/FilterManager";

let CONTEXT_UID = 0;

//渲染方式基本用了pixi的方式
export class WebglRenderer extends SystemRenderer {
    /**
     * 所有插件列表，目前只有batch
     */
    plugins = {};
    /**
     * webgl上下文
     */
    gl: WebGLRenderingContext
    /**
     * 渲染器位移标识
     */
    CONTEXT_UID: number;
    /**
     * 遮罩管理
     */
    maskManager: MaskManager;
    /**
     * 模板管理
     */
    stencilManager: StencilManager;
    /**
     * 滤镜管理器
     */
    filterManager: FilterManager;
    /**
     * 批处理管理
     */
    batchManager: BatchManager
    /**
     * 纹理管理
     */
    textureManager: TextureManager;
    /**
     * 纹理回收器，对于长期未使用的纹理，从gpu移除
     */
    textureGC: TextureGarbageCollector;
    /**
     * 状态机，暂时不需要切换，以后有3d再说
     */
    state: WebGLState;
    /**
     * 是否渲染到主画布
     */
    renderingToScreen: boolean;
    /**
     * 当前着色器对象
     */
    _activeShader: GLShader;
    /**
     * 当前vao
     */
    _activeVao: VertexArrayObject;
    /**
     * 当前渲染对象，不是帧缓存时就是root
     */
    _activeRenderTarget: RenderTarget;
    /**
     * 主画布
     */
    rootRenderTarget: RenderTarget;

    /**
     * 只传入上下文和尺寸，canvas标签在stage上处理
     * @param gl 
     * @param width 
     * @param height 
     */
    constructor(gl: WebGLRenderingContext, width: number, height: number) {
        super();
        this._instanceType = "WebglRenderer";

        this.type = RENDERER_TYPE.WEBGL;
        //修改下函数this指向，stage里直接用
        this.handleContextLost = this.handleContextLost.bind(this);
        this.handleContextRestored = this.handleContextRestored.bind(this);
        //在stage里处理
        // this.htmlElement.addEventListener('webglcontextlost', this.handleContextLost, false);
        // this.htmlElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);

        //stage上处理了
        // var contextOptions = {
        //     alpha: true,
        //     antialias: false,
        //     premultipliedAlpha: true,  //一般png图片都不会预乘alpha，所以必为true,除非有些图集工具选择了premultipliedAlpha
        //     stencil: true,
        //     preserveDrawingBuffer: false,
        //     // powerPreference: this.options.powerPreference,
        // };
        this._backgroundColorRgba[3] = this.transparent ? 0 : 1;

        // this.gl = createContext(this.htmlElement, contextOptions);
        this.gl = gl

        this.CONTEXT_UID = CONTEXT_UID++;

        this.maskManager = new MaskManager(this);

        this.stencilManager = new StencilManager(this);

        this.batchManager = new BatchManager(this)

        this.textureManager = null;

        //初始化插件
        this.initPlugins(WebglRenderer.__plugins)

        this.state = new WebGLState(this.gl);

        this.renderingToScreen = true;

        this._activeShader = null;

        this._activeVao = null;

        this._activeRenderTarget = null;

        this._initContext();

        // this.state.setBlendMode(0);
        this.resize(width, height);
    }

    _initContext() {
        const gl = this.gl;
        // restore a context if it was previously lost
        if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) {
            gl.getExtension('WEBGL_lose_context').restoreContext();
        }

        this._activeShader = null;
        this._activeVao = null;
        // create a texture manager...
        this.textureManager = new TextureManager(this);
        this.filterManager = new FilterManager(this);
        this.textureGC = new TextureGarbageCollector(this);
        this.state.resetToDefault();
        this.rootRenderTarget = new RenderTarget(gl, 1, 1, SCALE_MODES.LINEAR, true);
        this.rootRenderTarget.clearColor = this._backgroundColorRgba;

        this.bindRenderTarget(this.rootRenderTarget);

        this.dispatchEvent('onContextChange');
    }

    render(displayObject: DisplayObject, renderTexture?: RenderTexture, transform?: Matrix) {
        //触发onPreRender
        this.dispatchEvent('onPreRender');
        //是否渲染到屏幕root
        this.renderingToScreen = !renderTexture;
        //如果上下文丢失
        if (!this.gl || this.gl.isContextLost()) {
            return;
        }

        if (!renderTexture) {
            this._lastObjectRendered = displayObject;
        }

        //update更新属性
        displayObject.update()

        //更新矩阵
        const cacheParent = displayObject.parent;
        displayObject.parent = this._tempDisplayObjectParent;
        displayObject.updateTransform();
        displayObject.parent = cacheParent;

        //绑定渲染对象，没有则是默认root
        this.bindRenderTexture(renderTexture, transform);

        //当前插件start
        this.batchManager.currentRenderer.start();

        //渲染对象清空画布
        this._activeRenderTarget.clear();

        //开始渲染所有的显示对象
        displayObject.renderWebGL(this);

        //一次性刷一次
        this.batchManager.currentRenderer.flush();

        if (renderTexture) {
            //有bug，到时考虑具体更新什么
            // renderTexture.baseTexture.update();
        }

        //回收纹理,回收长时间不用的纹理,从gpu移除,对于很多场景,很多图片的时候很有用,否则gpu容易崩溃
        this.textureGC.update();//20210601打开注释，忘了为啥之前一直注释

        //触发onPostRender
        this.dispatchEvent('onPostRender');

    }


    /**
     * Erases the active render target and fills the drawing area with a colour
     *
     * @param {number[]} [clearColor] - The colour
     */
    clear(clearColor?: number[]) {
        this._activeRenderTarget.clear(clearColor);
    }

    /**
     * Sets the transform of the active render target to the given matrix
     *
     * @param {Matrix} matrix - The transformation matrix
     */
    setTransform(matrix: Matrix) {
        this._activeRenderTarget.transform = matrix;
    }

    /**
     * Erases the render texture and fills the drawing area with a colour
     *
     * @param {RenderTexture} renderTexture - The render texture to clear
     * @param {number} [clearColor] - The colour
     * @return {WebGLRenderer} Returns itself.
     */
    clearRenderTexture(renderTexture: RenderTexture, clearColor: number[]): WebglRenderer {
        const baseTexture: BaseRenderTexture = renderTexture.baseTexture;
        const renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];
        if (renderTarget) {
            renderTarget.clear(clearColor);
        }
        return this;
    }

    /**
     * Changes the current shader to the one given in parameter
     *
     * @param {Shader} shader - the new shader
     * @param {boolean} [autoProject=true] - Whether automatically set the projection matrix
     * @return {WebGLRenderer} Returns itself.
     */
    bindShader(shader: GLShader, autoProject: boolean = true): WebglRenderer {
        // TODO cache
        if (this._activeShader !== shader) {
            this._activeShader = shader;
            shader.bind();
            // `autoProject` normally would be a default parameter set to true
            // but because of how Babel transpiles default parameters
            // it hinders the performance of this method.
            if (autoProject !== false) {
                // automatically set the projection matrix
                shader.uniforms["projectionMatrix"] = this._activeRenderTarget.projectionMatrix.toArray(true);
            }
        }
        return this;
    }

    /**
     * Creates a new VAO from this renderer's context and state.
     *
     * @return {VertexArrayObject} The new VAO.
     */
    createVao(): VertexArrayObject {
        return new VertexArrayObject(this.gl, this.state.attribState);
    }

    /**
     * Changes the current Vao to the one given in parameter
     *
     * @param {VertexArrayObject} vao - the new Vao
     * @return {WebGLRenderer} Returns itself.
     */
    bindVao(vao: VertexArrayObject, force: boolean = false): WebglRenderer {
        if (this._activeVao === vao) {
            //计算是同一个也要bind，因为有可能vao里的attr被修改了，但是vao用的同一个，会导致buff不生效
            if (force) vao.bind();//TODO，考虑是否用vao.dirty来确定要不要执行bind（讲道理dirty必须重新bind通道）
            return this;
        }

        if (vao) {
            vao.bind();
        }
        else if (this._activeVao) {
            // TODO this should always be true i think?
            this._activeVao.unbind();
        }

        this._activeVao = vao;

        return this;
    }

    /**
     * Resets the WebGL state so you can render things however you fancy!
     *
     * @return {WebGLRenderer} Returns itself.
     */
    reset(): WebglRenderer {
        this.batchManager.reset();

        this.bindVao(null);
        this._activeShader = null;
        this._activeRenderTarget = this.rootRenderTarget;

        for (let i = 0; i < this.textureManager.boundTextures.length; i++) {
            this.textureManager.boundTextures[i] = this.textureManager.emptyTextures[i];
        }

        // bind the main frame buffer (the screen);
        this.rootRenderTarget.activate();

        this.state.resetToDefault();

        return this;
    }

    /**
     * Binds a render texture for rendering
     *
     * @param {RenderTexture} renderTexture - The render texture to render
     * @param {Matrix} transform - The transform to be applied to the render texture
     * @return {WebGLRenderer} Returns itself.
     */
    bindRenderTexture(renderTexture: RenderTexture, transform: Matrix): WebglRenderer {
        let renderTarget;

        if (renderTexture) {
            const baseTexture = renderTexture.baseTexture;

            if (!baseTexture._glRenderTargets[this.CONTEXT_UID]) {
                // bind the current texture
                this.textureManager.updateTexture(baseTexture, 0);
            }
            this.textureManager.unbindTexture(baseTexture);

            renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];
            renderTarget.setFrame(renderTexture.frame);
        }
        else {
            renderTarget = this.rootRenderTarget;
        }

        renderTarget.transform = transform;
        this.bindRenderTarget(renderTarget);

        return this;
    }

    /**
     * 绑定当前渲染对象
     * @param {RenderTarget} renderTarget - the new render target
     * @return {WebglRenderer} Returns itself.
     */
    bindRenderTarget(renderTarget: RenderTarget): WebglRenderer {
        if (renderTarget !== this._activeRenderTarget) {
            this._activeRenderTarget = renderTarget;
            renderTarget.activate();
            if (this._activeShader) {
                this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true);
            }
            this.stencilManager.setMaskStack(renderTarget.stencilMaskStack);
        }
        return this;
    }

    /**
     * Resizes the webGL view to the specified width and height.
     *
     * @param {number} blendMode - the desired blend mode
     */
    setBlendMode(blendMode: BLEND_MODES) {
        this.state.setBlendMode(blendMode);
    }


    /**
     * 传入真实的canvas尺寸
     * @param screenWidth 
     * @param screenHeight 
     */
    resize(screenWidth: number, screenHeight: number) {
        super.resize(screenWidth, screenHeight);
        this.rootRenderTarget.resize(screenWidth /** devicePixelRatio*/, screenHeight /** devicePixelRatio*/);
        if (this._activeRenderTarget === this.rootRenderTarget) {
            this.rootRenderTarget.activate();
            if (this._activeShader) {
                this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true);
            }
        }
    }

    /**
     * 渲染器销毁方法
     */
    destroy() {
        //插件销毁
        this.destroyPlugins();
        // remove listeners，这部分逻辑放舞台里，注意舞台先移除监听，再销毁渲染器
        // this.htmlElement.removeEventListener('webglcontextlost', this.handleContextLost);
        // this.htmlElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
        //纹理管理器销毁
        this.textureManager.destroy();

        //父级销毁方法
        super.destroy();

        // 销毁所有管理器
        this.maskManager.destroy();
        this.stencilManager.destroy();
        this.filterManager.destroy();

        this.maskManager = null;
        this.filterManager = null;
        this.textureManager = null;

        this.handleContextLost = null;
        this.handleContextRestored = null;

        //淘宝小程序内会有问题，这样执行，暂时先注释，如果web环境出问题再修改
        // this.gl.useProgram(null);

        // if (this.gl.getExtension('WEBGL_lose_context')) {
        //   this.gl.getExtension('WEBGL_lose_context').loseContext();
        // }

        // this.gl = null;
    }



    /**
     * webgl上下文恢复时
     * @private
     */
    handleContextRestored() {
        this.textureManager.removeAll();
        this.filterManager.destroy(true);
        this._initContext();
        this.resize(this.width, this.height);//放在这吧,改动最小
    }

    /**
     * webgl上下文丢失时
     * @param {WebGLContextEvent} event - 事件
     */
    handleContextLost(event) {
        event.preventDefault();
    }

    /**
     * 初始化插件
     * @protected
     * @param {object} staticMap - 静态属性里存的插件列表
     */
    initPlugins(staticMap) {
        for (const o in staticMap) {
            this.plugins[o] = new (staticMap[o])(this);
        }
    }
    /**
     * 销毁插件
     */
    destroyPlugins() {
        for (const o in this.plugins) {
            this.plugins[o].destroy();
            this.plugins[o] = null;
        }
        this.plugins = null;
    }
    /**
     * 插件列表
     */
    static __plugins;
    /**
     * 添加插件，用于initPlugins时生成所有插件实例，webgl渲染器估计插件可扩展多，所以加静态方法
     * @method
     * @param {string} pluginName - 插件名，用于区分键值
     * @param {Function} ctor - 插件类
     */
    static registerPlugin(pluginName, ctor) {
        WebglRenderer.__plugins = WebglRenderer.__plugins || {};
        WebglRenderer.__plugins[pluginName] = ctor;
    }
}

//注册2d批处理插件，不放这里BatchRenderer文件里，因为不往外引，不会执行，
WebglRenderer.registerPlugin('batch', BatchRenderer);

