import BatchDrawCall from '../webgl/BatchDrawCall';
import { osType, BLEND_MODES } from "../../const"
// import State from '../state/State';
import ObjectRenderer from '../webgl/ObjectRenderer';
import { checkMaxIfStatementsInShader } from '../../../glCore/checkMaxIfStatementsInShader';

import { BatchBuffer } from '../webgl/BatchBuffer';
import { generateMultiTextureShader } from '../webgl/generateMultiTextureShader';
import { WebglRenderer } from '../WebglRenderer';
import { GLShader, GLBuffer, VertexArrayObject } from '../../../glCore';
import { nextPow2, log2, premultiplyTint, premultiplyBlendMode } from '../../utils';
import { DisplayObject } from '../../display/DisplayObject';
import Texture from '../../texture/Texture';
import BaseTexture from '../../texture/BaseTexture';

let TICK = 0;

/**
 * 批处理核心渲染插件
 *
 * @class
 * @extends ObjectRenderer
 */
export default class BatchRenderer extends ObjectRenderer {
    vertSize: number;
    vertByteSize: number;
    size: number;
    /**
     * 当前顶点数量
     */
    currentSize: number;
    /**
     * 当前索引数量
     */
    currentIndexSize: number;
    /**
     * 顶点数据
     */
    aBuffers: {};
    /**
     * 索引数据
     */
    iBuffers: {};
    /**
     * The default shaders that is used if a sprite doesn't have a more specific one.
     * there is a shader for each number of textures that can be rendered.
     * These shaders will also be generated on the fly as required.
     */
    shader: GLShader;
    /**
     * 当前累计的绘制对象数量
     */
    currentIndex: number;
    groups: BatchDrawCall[];
    /**
     * 绘制对象数组
     * 包括图片的和矢量图的
     */
    elements: batchElementInterface[];
    vaos: VertexArrayObject[];
    vaoMax: number;
    vertexCount: number;
    MAX_TEXTURES: number;

    vertexBuffers: GLBuffer[];
    indexBuffers: GLBuffer[];


    constructor(renderer: WebglRenderer) {
        super(renderer);

        /**
         * 每个点
         * Number of values sent in the vertex buffer.
         * aVertexPosition(2), aTextureCoord(2), aColor(1), aTextureId(1) = 6
         *
         * @member {number}
         */
        this.vertSize = 6;

        /**
         * The size of the vertex information in bytes.
         *
         * @member {number}
         */
        this.vertByteSize = this.vertSize * 4;

        /**
         * The number of images in the SpriteRenderer before it flushes.
         *
         * @member {number}
         */
        this.size = 2000 * 4;// settings.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop

        this.currentSize = 0;
        this.currentIndexSize = 0;

        // the total number of bytes in our batch
        // let numVerts = this.size * 4 * this.vertByteSize;

        this.aBuffers = {};
        this.iBuffers = {};


        this.shader = null;


        this.currentIndex = 0;
        this.groups = [];

        for (let k = 0; k < this.size / 4; k++) {
            this.groups[k] = new BatchDrawCall();
        }

        this.elements = [];
        this.vertexBuffers = [];
        this.indexBuffers = [];
        this.vaos = [];

        this.vaoMax = 2;
        this.vertexCount = 0;

        this.renderer.addEventListener('onPreRender', this.onPreRender, this);

        // this.state = State.for2d();深度检测去掉

    }

    /**
     * Sets up the renderer context and necessary buffers.
     */
    onContextChange() {
        const gl = this.renderer.gl;

        if (false /*传统方式*/) {
            this.MAX_TEXTURES = 1;
        }
        else {
            // step 1: first check max textures the GPU can handle.
            this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), 16);

            // step 2: check the maximum number of if statements the shader can have too..
            this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl);
        }

        // generate generateMultiTextureProgram, may be a better move?
        this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES);

        // we use the second shader as the first one depending on your browser may omit aTextureId
        // as it is not used by the shader so is optimized out.


        this.renderer.bindVao(null);

        const attrs = this.shader.attributes;

        for (let i = 0; i < this.vaoMax; i++) {
            /* eslint-disable max-len */
            const vertexBuffer = this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
            const indexBuffer = this.indexBuffers[i] = GLBuffer.createIndexBuffer(gl, null, gl.STREAM_DRAW);

            // build the vao object that will render..
            const vao = this.renderer.createVao()
                .addIndex(indexBuffer)
                .addAttribute(vertexBuffer, attrs.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                // .addAttribute(vertexBuffer, attrs.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
                // .addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4);
                .addAttribute(vertexBuffer, attrs.aTextureCoord, gl.FLOAT, true, this.vertByteSize, 2 * 4)
                .addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 4 * 4)
                .addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 5 * 4);
            // if (attrs.aTextureId)
            // {
            //     vao.addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
            // }

            this.vaos[i] = vao;
        }
    }

    /**
     * Called before the renderer starts rendering.
     *
     */
    onPreRender() {
        this.vertexCount = 0;
    }

    /**
     * Renders the sprite object.
     * element必须的属性
     * 
     * _texture  里面有.BaseTexture
     * _vertexData  
     * _indices 
     * _worldAlpha
     * _tintRGB
     * _uvs
     * 
     * the sprite to render when using this spritebatch
     */
    render(element: batchElementInterface) {
        if (!element._texture || !element._texture.valid) {
            return;
        }

        if (this.currentSize + (element._vertexData.length / 2) > this.size) {
            this.flush();
        }

        this.elements[this.currentIndex++] = element;

        this.currentSize += element._vertexData.length / 2;
        this.currentIndexSize += element._indices.length;
    }

    /**
     * 获得索引buffer
     * @param size 
     */
    getIndexBuffer(size) {
        // 12 indices is enough for 2 quads
        const roundedP2 = nextPow2(Math.ceil(size / 12));
        const roundedSizeIndex = log2(roundedP2);
        const roundedSize = roundedP2 * 12;

        // if (this.iBuffers.length <= roundedSizeIndex) {
        //     this.iBuffers.length = roundedSizeIndex + 1;
        // }

        let buffer = this.iBuffers[roundedSizeIndex];

        if (!buffer) {
            this.iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize);
        }

        return buffer;
    }

    /**
     * 获取相应的顶点数据buffer
     * @param size 
     */
    getAttributeBuffer(size: number) {
        // 8 vertices is enough for 2 quads
        const roundedP2 = nextPow2(Math.ceil(size / 8));
        const roundedSizeIndex = log2(roundedP2);
        const roundedSize = roundedP2 * 8;

        // if (this.aBuffers.length <= roundedSizeIndex) {
        //     this.iBuffers.length = roundedSizeIndex + 1;
        // }

        let buffer = this.aBuffers[roundedSize];

        if (!buffer) {
            this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize);
        }

        return buffer;
    }

    /**
     * Renders the content and empties the current batch.
     *
     */
    flush() {
        if (this.currentSize === 0) {
            return;
        }

        const gl = this.renderer.gl;
        const MAX_TEXTURES = this.MAX_TEXTURES;

        const buffer = this.getAttributeBuffer(this.currentSize);
        const indexBuffer = this.getIndexBuffer(this.currentIndexSize);

        const elements = this.elements;
        const groups = this.groups;

        const float32View = buffer.float32View;
        const uint32View = buffer.uint32View;

        const touch = this.renderer.textureGC.count;

        let index = 0;
        let indexCount = 0;
        let nextTexture;
        let currentTexture;
        let groupCount = 1;

        let textureCount = 0;
        let currentGroup = groups[0];

        //先用第一个的混色模式
        let blendMode = premultiplyBlendMode[elements[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][elements[0]._blendMode];

        currentGroup.textureCount = 0;
        currentGroup.start = 0;
        // currentGroup.blend = blendMode;

        TICK++;

        let i;

        for (i = 0; i < this.currentIndex; ++i) {
            // upload the sprite elements...
            // they have all ready been calculated so we just need to push them into the buffer.

            const sprite = elements[i];

            elements[i] = null;

            nextTexture = sprite._texture.baseTexture;

            const spriteBlendMode = premultiplyBlendMode[nextTexture.premultipliedAlpha ? 1 : 0][sprite._blendMode];

            if (blendMode !== spriteBlendMode) {
                blendMode = spriteBlendMode;

                // force the batch to break!
                currentTexture = null;
                textureCount = MAX_TEXTURES;
                TICK++;
            }

            if (currentTexture !== nextTexture) {
                currentTexture = nextTexture;

                if (nextTexture._enabledId !== TICK) {
                    if (textureCount === MAX_TEXTURES) {
                        TICK++;

                        textureCount = 0;

                        currentGroup.size = indexCount - currentGroup.start;

                        currentGroup = groups[groupCount++];
                        currentGroup.textureCount = 0;
                        currentGroup.blend = blendMode;
                        currentGroup.start = indexCount;
                    }

                    nextTexture._touchedId = touch;
                    nextTexture._enabledId = TICK;
                    nextTexture._id = textureCount;

                    currentGroup.textures[currentGroup.textureCount++] = nextTexture;
                    textureCount++;
                }
            }
            // argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount);
            this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);

            // push a graphics..
            index += (sprite._vertexData.length / 2) * this.vertSize;
            indexCount += sprite._indices.length;
        }

        currentGroup.size = indexCount - currentGroup.start;

        //        this.indexBuffer.update();
        //暂时出现了bug，ios不做特殊处理先，以后有时间排查，暂时应该影响不大
        if (osType == "ios" && false) {
            //可能有一帧，在多个地方执行flush
            // this is still needed for IOS performance..
            // it really does not like uploading to the same buffer in a single frame!
            if (this.vaoMax <= this.vertexCount) {
                this.vaoMax++;

                const attrs = this.shader.attributes;

                /* eslint-disable max-len */
                const vertexBuffer = this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
                const indexBufferAdd = this.indexBuffers[this.vertexCount] = GLBuffer.createIndexBuffer(gl, null, gl.STREAM_DRAW);

                // build the vao object that will render..
                const vao = this.renderer.createVao()
                    .addIndex(indexBufferAdd)
                    .addAttribute(vertexBuffer, attrs.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                    .addAttribute(vertexBuffer, attrs.aTextureCoord, gl.FLOAT, true, this.vertByteSize, 2 * 4)
                    .addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 4 * 4)
                    .addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 5 * 4);
                this.vaos[this.vertexCount] = vao;

                // console.log(this.vertexCount)
            }

            this.renderer.bindVao(this.vaos[this.vertexCount]);

            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false);
            this.indexBuffers[this.vertexCount].upload(indexBuffer, 0, false);

            this.vertexCount++;

        }
        else {

            // lets use the faster option, always use buffer number 0
            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true);
            this.indexBuffers[this.vertexCount].upload(indexBuffer, 0, true);
        }

        //   this.renderer.state.set(this.state);

        const textureManager = this.renderer.textureManager;
        // const stateSystem = this.renderer.state;
        // e.log(groupCount);
        // / render the groups..

        for (i = 0; i < groupCount; i++) {
            const group = groups[i];
            const groupTextureCount = group.textureCount;

            for (let j = 0; j < groupTextureCount; j++) {
                if (group.textures[j] != textureManager.boundTextures[j]) {
                    //location已经和sampleId固定了，就算原先boundsTextures有，也要重新绑定位置
                    // console.log("bindTexture")
                    textureManager.bindTexture(group.textures[j], j, true);
                }
                group.textures[j] = null;
            }
            //混色模式暂不用
            // this.state.blendMode = group.blend;
            // this.state.blend = true;

            // this.renderer.state.setState(this.state);
            // set the blend mode..
            // stateSystem.setBlendMode(group.blend);
            this.renderer.setBlendMode(group.blend)

            gl.drawElements(group.type, group.size, gl.UNSIGNED_SHORT, group.start * 2);
        }

        // reset elements for the next flush
        this.currentIndex = 0;
        this.currentSize = 0;
        this.currentIndexSize = 0;
    }

    packGeometry(element: batchElementInterface, float32View, uint32View, indexBuffer, index, indexCount) {
        const p = index / this.vertSize;// float32View.length / 6 / 2;
        const uvs = element._uvs;
        const indicies = element._indices;// geometry.getIndex().data;// indicies;
        const vertexData = element._vertexData;
        const textureId = element._texture.baseTexture["_id"];

        var _tintRGB = element._tintRGB == undefined ? 16777215 : element._tintRGB

        const alpha = Math.min(element._worldAlpha, 1.0);
        // we dont call extra function if alpha is 1.0, that's faster
        const argb = alpha < 1.0 && element._texture.baseTexture.premultipliedAlpha ? premultiplyTint(_tintRGB, alpha)
            : _tintRGB + (alpha * 255 << 24);

        // lets not worry about tint! for now..
        for (let i = 0; i < vertexData.length; i += 2) {
            float32View[index++] = vertexData[i];
            float32View[index++] = vertexData[i + 1];
            float32View[index++] = uvs[i];
            float32View[index++] = uvs[i + 1];
            uint32View[index++] = argb;
            float32View[index++] = textureId;
        }

        for (let i = 0; i < indicies.length; i++) {
            indexBuffer[indexCount++] = p + indicies[i];
        }
    }

    /**
     * Starts a new sprite batch.
     */
    start() {
        //暂时不考虑2d专门的状态机属性
        // this.renderer.state.setState(this.state);

        this.renderer.bindShader(this.shader);

        // if (osType != "ios") {//暂时出现了bug，ios不做特殊处理先
        this.renderer.bindVao(this.vaos[this.vertexCount]);
        this.vertexBuffers[this.vertexCount].bind();
        this.indexBuffers[this.vertexCount].bind();
        // }
    }

    /**
     * Stops and flushes the current batch.
     *
     */
    stop() {
        this.flush();
    }

    /**
     * Destroys the SpriteRenderer.
     *
     */
    destroy() {
        for (let i = 0; i < this.vaoMax; i++) {
            // if (this.vertexBuffers[i])
            // {
            //     this.vertexBuffers[i].destroy();
            // }
            if (this.vaos[i]) {
                this.vaos[i].destroy();
            }
        }

        this.renderer.removeEventListener('onPreRender', this.onPreRender, this);

        if (this.shader) {
            this.shader.destroy();
            this.shader = null;
        }

        this.vaos = null;

        super.destroy();
    }
}

interface batchElementInterface {
    _blendMode: BLEND_MODES,
    _texture: Texture,
    _worldAlpha: number,
    _vertexData: Float32Array,
    _indices: Uint16Array,
    _tintRGB?: number,
    _uvs: Float32Array,
}
