import { GLTexture } from '../../../glCore';
import { WRAP_MODES, SCALE_MODES } from '../../const';
import RenderTarget from '../renderTarget/RenderTarget';
import { removeItems } from '../../utils';
import BaseTexture from '../../texture/BaseTexture';
import { WebglRenderer } from '../WebglRenderer';

/**
 * Helper class to create a webGL Texture
 *
 */
export default class TextureManager {
    boundTextures;
    renderer: WebglRenderer;
    emptyTextures;
    _nextTextureLocation: number = 0;
    currentLocation: number;
    gl: WebGLRenderingContext;
    /**
     * Track textures in the renderer so we can no longer listen to them on destruction.
     */
    _managedTextures: Array<any>;
    /**
     * @param {WebGLRenderer} renderer - A reference to the current renderer
     */
    constructor(renderer: WebglRenderer) {

        this.renderer = renderer;

        this.gl = renderer.gl;


        this._managedTextures = [];

        this.renderer.addEventListener('onContextChange', this.onContextChange, this);
    }

    onContextChange() {
        const gl = this.gl;
        const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);

        this.boundTextures = new Array(maxTextures);
        this.emptyTextures = new Array(maxTextures);

        // now lets fill up the textures with empty ones!
        const emptyGLTexture = GLTexture.fromData(gl, null, 1, 1);

        const tempObj = { _glTextures: {} };

        tempObj._glTextures[this.renderer.CONTEXT_UID] = {};

        for (let i = 0; i < maxTextures; i++) {
            const empty = new BaseTexture();

            empty._glTextures[this.renderer.CONTEXT_UID] = emptyGLTexture;

            this.boundTextures[i] = tempObj;
            this.emptyTextures[i] = empty;
            this.bindTexture(null, i);
        }
    }



    /**
     * Binds the texture. This will return the location of the bound texture.
     * It may not be the same as the one you pass in. This is due to optimisation that prevents
     * needless binding of textures. For example if the texture is already bound it will return the
     * current location of the texture instead of the one provided. To bypass this use force location
     *
     * @param {Texture} texture - the new texture
     * @param {number} location - the suggested texture location
     * @param {boolean} forceLocation - force the location
     * @return {number} bound texture location
     */
    bindTexture(texture: any, location?: number, forceLocation?: boolean): number {
        texture = texture || this.emptyTextures[location];
        texture = texture.baseTexture || texture;
        texture._touchedId = this.renderer.textureGC.count;

        //从原先绑定过的纹理里找
        if (!forceLocation) {
            // TODO - maybe look into adding boundIds.. save us the loop?
            for (let i = 0; i < this.boundTextures.length; i++) {
                if (this.boundTextures[i] === texture) {
                    return i;
                }
            }

            if (location === undefined) {
                this._nextTextureLocation++;
                this._nextTextureLocation %= this.boundTextures.length;
                location = this.boundTextures.length - this._nextTextureLocation - 1;
            }
        }
        else {
            location = location || 0;
        }

        const gl = this.gl;
        const glTexture = texture._glTextures[this.renderer.CONTEXT_UID];

        //考虑到时用dirty更新的方法，不然有些属性更新可能无效，mipmap等
        if (!glTexture) {
            // this will also bind the texture..
            this.updateTexture(texture, location);
        }
        else {
            // bind the current texture
            if (this.currentLocation !== location) {
                this.currentLocation = location;
                gl.activeTexture(gl.TEXTURE0 + location);
            }

            if (this.boundTextures[location] !== texture) {
                gl.bindTexture(gl.TEXTURE_2D, glTexture.texture);
            }
            this.boundTextures[location] = texture;
        }
        return location;
    }
    /**
     * Gets a texture.
     *
     */
    getTexture() {
        // empty
    }

    /**
     * Updates and/or Creates a WebGL texture for the renderer's context.
     *
     * @param {BaseTexture|Texture} texture - the texture to update
     * @param {number} location - the location the texture will be bound to.
     * @return {GLTexture} The gl texture.
     */
    updateTexture(texture: any, location?: number): GLTexture {
        //如果是事件返回的
        if (texture.instanceType === "Event") {
            //选择它的target
            texture = texture.target.baseTexture || texture.target
        } else {
            texture = texture.baseTexture || texture;
        }


        const gl = this.gl;

        const isRenderTexture = !!texture["_glRenderTargets"];

        if (!texture.hasLoaded) {
            return null;
        }

        const boundTextures = this.boundTextures;

        // if the location is undefined then this may have been called by n event.
        // this being the case the texture may already be bound to a slot. As a texture can only be bound once
        // we need to find its current location if it exists.
        if (location === undefined) {
            location = 0;

            // TODO maybe we can use texture bound ids later on...
            // check if texture is already bound..
            for (let i = 0; i < boundTextures.length; ++i) {
                if (boundTextures[i] === texture) {
                    location = i;
                    break;
                }
            }
        }

        boundTextures[location] = texture;

        if (this.currentLocation !== location) {
            this.currentLocation = location;
            gl.activeTexture(gl.TEXTURE0 + location);
        }

        let glTexture: GLTexture = texture._glTextures[this.renderer.CONTEXT_UID];

        if (!glTexture) {
            if (isRenderTexture) {
                const renderTarget = new RenderTarget(
                    this.gl,
                    texture.width,
                    texture.height,
                    texture.scaleMode,
                    false
                );
                renderTarget.resize(texture.width, texture.height);
                texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget;
                glTexture = renderTarget.texture;
            }
            else {
                glTexture = new GLTexture(this.gl, null, null, null, null);
                //之前已经active过了，upload里也有bind，这里也许不用执行
                // glTexture.bind(location);
                glTexture.premultiplyAlpha = true;
                glTexture.upload(texture.source);
            }

            texture._glTextures[this.renderer.CONTEXT_UID] = glTexture;

            texture.addEventListener('update', this.updateTexture, this);
            texture.addEventListener('dispose', this.destroyTexture, this);

            this._managedTextures.push(texture);

            if (texture.isPowerOfTwo) {
                if (texture.mipmap) {
                    glTexture.enableMipmap();
                }
                if (texture.wrapMode === WRAP_MODES.CLAMP) {
                    glTexture.enableWrapClamp();
                }
                else if (texture.wrapMode === WRAP_MODES.REPEAT) {
                    glTexture.enableWrapRepeat();
                }
                else {
                    glTexture.enableWrapMirrorRepeat();
                }
            }
            else {
                glTexture.enableWrapClamp();
            }

            if (texture.scaleMode === SCALE_MODES.NEAREST) {
                glTexture.enableNearestScaling();
            }
            else {
                glTexture.enableLinearScaling();
            }
        }
        // the texture already exists so we only need to update it..
        else if (isRenderTexture) {
            texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height);
        }
        else {
            glTexture.upload(texture.source);
        }

        return glTexture;
    }

    /**
     * unbinds the texture ...
     *
     * @param {Texture} texture - the texture to unbind
     */
    unbindTexture(texture) {
        const gl = this.gl;

        texture = texture.baseTexture || texture;

        for (let i = 0; i < this.boundTextures.length; i++) {
            if (this.boundTextures[i] === texture) {
                this.boundTextures[i] = this.emptyTextures[i];
                if (this.currentLocation !== i) {
                    gl.activeTexture(gl.TEXTURE0 + i);
                    this.currentLocation = i;
                }
                gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.renderer.CONTEXT_UID].texture);
            }
        }
    }

    /**
     * Deletes the texture from WebGL
     *
     * @param {BaseTexture|Texture} texture - the texture to destroy
     * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager.
     */
    destroyTexture(texture, skipRemove) {
        texture = texture.baseTexture || texture;

        if (!texture.hasLoaded) {
            return;
        }

        const uid = this.renderer.CONTEXT_UID;
        const glTextures = texture._glTextures;
        const glRenderTargets = texture._glRenderTargets;

        if (glTextures[uid]) {
            this.unbindTexture(texture);

            glTextures[uid].destroy();
            texture.removeEventListener('update', this.updateTexture, this);
            texture.removeEventListener('dispose', this.destroyTexture, this);

            delete glTextures[uid];

            if (!skipRemove) {
                const i = this._managedTextures.indexOf(texture);

                if (i !== -1) {
                    removeItems(this._managedTextures, i, 1);
                }
            }
        }

        if (glRenderTargets && glRenderTargets[uid]) {
            glRenderTargets[uid].destroy();
            delete glRenderTargets[uid];
        }
    }

    /**
     * Deletes all the textures from WebGL
     */
    removeAll() {
        // empty all the old gl textures as they are useless now
        for (let i = 0; i < this._managedTextures.length; ++i) {
            const texture = this._managedTextures[i];

            if (texture._glTextures[this.renderer.CONTEXT_UID]) {
                delete texture._glTextures[this.renderer.CONTEXT_UID];
            }
        }
    }

    /**
     * Destroys this manager and removes all its textures
     */
    destroy() {
        // destroy managed textures
        for (let i = 0; i < this._managedTextures.length; ++i) {
            const texture = this._managedTextures[i];

            this.destroyTexture(texture, true);
            //上面已经移除了
            // texture.removeEventListener('update', this.updateTexture, this);
            // texture.removeEventListener('dispose', this.destroyTexture, this);
        }
        //移除绑定事件
        this.renderer.removeEventListener('onContextChange', this.onContextChange, this);
        this.renderer = null;
        this._managedTextures = null;
    }
}
