//const { ccclass, property } = cc._decorator;

/**
 * 单个粒子信息
 */
export class MyParticle {
    pos = cc.v2(0, 0);
    startPos = cc.v2(0, 0);
    color = cc.color(0, 0, 0, 255);
    deltaColor = { r: 0, g: 0, b: 0, a: 255 };
    size = 0;
    deltaSize = 0;
    rotation = 0;
    deltaRotation = 0;
    timeToLive = 0;
    drawPos = cc.v2(0, 0);
    aspectRatio = 1;
    // Mode A
    dir = cc.v2(0, 0);
    radialAccel = 0;
    tangentialAccel = 0;
    // Mode B
    angle = 0;
    degreesPerSecond = 0;
    radius = 0;
    deltaRadius = 0;

    frameindex = 0//当前帧
    lastFrameTime = 0;
    topLife = 0;       //当刷新帧率为-1时,自动按总生活周期

    init() {
        this.pos.set(cc.Vec2.ZERO)
        this.startPos.set(cc.Vec2.ZERO)
        this.color.fromHEX('0xFF000000');

        this.deltaColor.r = this.deltaColor.g = this.deltaColor.b = 0;
        this.deltaColor.a = 255;
        this.size = 0;
        this.deltaSize = 0;
        this.rotation = 0;
        this.deltaRotation = 0;
        this.timeToLive = 0;
        this.drawPos.set(cc.Vec2.ZERO)
        this.aspectRatio = 1;
        // Mode A
        this.dir.set(cc.Vec2.ZERO)
        this.radialAccel = 0;
        this.tangentialAccel = 0;
        // Mode B
        this.angle = 0;
        this.degreesPerSecond = 0;
        this.radius = 0;
        this.deltaRadius = 0;

        this.frameindex = 0//当前帧
        this.lastFrameTime = 0;
        this.topLife = 0;       //当刷新帧率为-1时,自动按总生活周期
    }
}

export default class MyParticleSimulator {
    pool: cc.js.Pool;
    // sys: MyParticleSysInfo;
    sys: cc.ParticleSystem;
    particles: MyParticle[] = null;
    active: boolean = false;
    readyToPlay: boolean = true;
    finished: boolean = false;
    elapsed: number = 0;
    emitCounter: number = 0;
    _uvFilled: number = 0;
    _worldRotation: number = 0;
    animationRate: number = 0;       //帧率
    animFrameNums: number = 0;       //图片有几帧
    _pos = new cc.Vec2(0, 0);
    _tpa = new cc.Vec2(0, 0);
    _tpb = new cc.Vec2(0, 0);
    _tpc = new cc.Vec2(0, 0);

    constructor(system, frameNums, animRate) {
        this.pool = new cc.js.Pool((par: MyParticle) => {
            par.pos.set(cc.Vec2.ZERO)
            par.startPos.set(cc.Vec2.ZERO)
            par.color.fromHEX('0xFF000000');

            par.deltaColor.r = par.deltaColor.g = par.deltaColor.b = 0;
            par.deltaColor.a = 255;
            par.size = 0;
            par.deltaSize = 0;
            par.rotation = 0;
            par.deltaRotation = 0;
            par.timeToLive = 0;
            par.drawPos.set(cc.Vec2.ZERO)
            par.aspectRatio = 1;
            // Mode A
            par.dir.set(cc.Vec2.ZERO)
            par.radialAccel = 0;
            par.tangentialAccel = 0;
            // Mode B
            par.angle = 0;
            par.degreesPerSecond = 0;
            par.radius = 0;
            par.deltaRadius = 0;

            par.frameindex = 0//当前帧
            par.lastFrameTime = 0;
            par.topLife = 0;       //当刷新帧率为-1时,自动按总生活周期
        }, 1024);

        this.sys = system;
        this.particles = [];
        this.active = false;
        this.readyToPlay = true;
        this.finished = false;
        this.elapsed = 0;
        this.emitCounter = 0;
        this._uvFilled = 0;
        this._worldRotation = 0;
        this.animationRate = animRate;
        this.animFrameNums = frameNums;

        this.pool.get = function () {
            return this._get() || new MyParticle();
        };
    }

    stop() {
        this.active = false;
        this.readyToPlay = false;
        this.elapsed = this.sys.duration;
        this.emitCounter = 0;
    }

    reset() {
        cc.js.Pool
        this.active = true;
        this.readyToPlay = true;
        this.elapsed = 0;
        this.emitCounter = 0;
        this.finished = false;
        let particles = this.particles;
        for (let id = 0; id < particles.length; ++id) {
            this.pool.put(particles[id]);
        }
        particles.length = 0;
    }

    /**
     * 开始喷射
     */
    emitParticle(pos) {
        let psys = this.sys;
        let clampf = cc.misc.clampf;
        let particle = this.pool.get();
        this.particles.push(particle);
        particle.timeToLive = psys.life + psys.lifeVar * (Math.random() - 0.5) * 2;
        // Init particle
        // timeToLive
        // no negative life. prevent division by 0
        particle.timeToLive = psys.life + psys.lifeVar * (Math.random() - 0.5) * 2;
        let timeToLive = particle.timeToLive = Math.max(0, particle.timeToLive);

        // position
        particle.pos.x = psys.sourcePos.x + psys.posVar.x * (Math.random() - 0.5) * 2;
        particle.pos.y = psys.sourcePos.y + psys.posVar.y * (Math.random() - 0.5) * 2;

        // Color
        let sr, sg, sb, sa;
        let startColor = psys.startColor, startColorVar = psys.startColorVar;
        let endColor = psys.endColor, endColorVar = psys.endColorVar;
        particle.color.r = sr = clampf(startColor.r + startColorVar.r * (Math.random() - 0.5) * 2, 0, 255);
        particle.color.g = sg = clampf(startColor.g + startColorVar.g * (Math.random() - 0.5) * 2, 0, 255);
        particle.color.b = sb = clampf(startColor.b + startColorVar.b * (Math.random() - 0.5) * 2, 0, 255);
        particle.color.a = sa = clampf(startColor.a + startColorVar.a * (Math.random() - 0.5) * 2, 0, 255);
        particle.deltaColor.r = (clampf(endColor.r + endColorVar.r * (Math.random() - 0.5) * 2, 0, 255) - sr) / timeToLive;
        particle.deltaColor.g = (clampf(endColor.g + endColorVar.g * (Math.random() - 0.5) * 2, 0, 255) - sg) / timeToLive;
        particle.deltaColor.b = (clampf(endColor.b + endColorVar.b * (Math.random() - 0.5) * 2, 0, 255) - sb) / timeToLive;
        particle.deltaColor.a = (clampf(endColor.a + endColorVar.a * (Math.random() - 0.5) * 2, 0, 255) - sa) / timeToLive;

        // size
        let startS = psys.startSize + psys.startSizeVar * (Math.random() - 0.5) * 2;
        startS = Math.max(0, startS); // No negative value
        particle.size = startS;
        if (psys.endSize === cc.ParticleSystem.START_SIZE_EQUAL_TO_END_SIZE) {
            particle.deltaSize = 0;
        } else {
            var endS = psys.endSize + psys.endSizeVar * (Math.random() - 0.5) * 2;
            endS = Math.max(0, endS); // No negative values
            particle.deltaSize = (endS - startS) / timeToLive;
        }

        // rotation
        var startA = psys.startSpin + psys.startSpinVar * (Math.random() - 0.5) * 2;
        var endA = psys.endSpin + psys.endSpinVar * (Math.random() - 0.5) * 2;
        particle.rotation = startA;
        particle.deltaRotation = (endA - startA) / timeToLive;

        // position
        particle.startPos.x = pos.x;
        particle.startPos.y = pos.y;

        // aspect ratio
        particle.aspectRatio = this.sys['_aspectRatio'] || 1;

        // direction
        let a = cc.misc.degreesToRadians(psys.angle + this._worldRotation + psys.angleVar * (Math.random() - 0.5) * 2);
        // Mode Gravity: A
        if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) {
            let s = psys.speed + psys.speedVar * (Math.random() - 0.5) * 2;
            // direction
            particle.dir.x = Math.cos(a);
            particle.dir.y = Math.sin(a);
            particle.dir.mulSelf(s);
            // radial accel
            particle.radialAccel = psys.radialAccel + psys.radialAccelVar * (Math.random() - 0.5) * 2;
            // tangential accel
            particle.tangentialAccel = psys.tangentialAccel + psys.tangentialAccelVar * (Math.random() - 0.5) * 2;
            // rotation is dir
            if (psys.rotationIsDir) {
                particle.rotation = -cc.misc.radiansToDegrees(Math.atan2(particle.dir.y, particle.dir.x));
            }
        }
        // Mode Radius: B
        else {
            // Set the default diameter of the particle from the source position
            var startRadius = psys.startRadius + psys.startRadiusVar * (Math.random() - 0.5) * 2;
            var endRadius = psys.endRadius + psys.endRadiusVar * (Math.random() - 0.5) * 2;
            particle.radius = startRadius;
            particle.deltaRadius = (psys.endRadius === cc.ParticleSystem.START_RADIUS_EQUAL_TO_END_RADIUS) ? 0 : (endRadius - startRadius) / timeToLive;
            particle.angle = a;
            particle.degreesPerSecond = cc.misc.degreesToRadians(psys.rotatePerS + psys.rotatePerSVar * (Math.random() - 0.5) * 2);
        }


        particle.frameindex = 0;
        particle.lastFrameTime = particle.timeToLive;
        particle.topLife = particle.timeToLive;
    }

    getWorldRotation(node) {
        let rotation = 0;
        let tempNode = node;
        while (tempNode) {
            rotation += tempNode.angle;
            tempNode = tempNode.parent;
        }
        return rotation;
    }


    updateUVs(force) {
        let assembler = this.sys['_assembler'];
        if (!assembler) {
            return;
        }
        let buffer = this.sys['_assembler'].getBuffer();
        if (buffer) {
            const FLOAT_PER_PARTICLE = 4 * this.sys['_assembler']._vfmt._bytes / 4;
            let vbuf = buffer._vData;
            let uv = this.sys['_renderSpriteFrame'].uv;

            let start = force ? 0 : this._uvFilled;
            let particleCount = this.particles.length;
            for (let i = start; i < particleCount; i++) {
                let offset = i * FLOAT_PER_PARTICLE;
                vbuf[offset + 2] = uv[0];
                vbuf[offset + 3] = uv[1];
                vbuf[offset + 7] = uv[2];
                vbuf[offset + 8] = uv[3];
                vbuf[offset + 12] = uv[4];
                vbuf[offset + 13] = uv[5];
                vbuf[offset + 17] = uv[6];
                vbuf[offset + 18] = uv[7];
            }
            this._uvFilled = particleCount;
        }
    }

    updateParticleBuffer(particle, pos, buffer, offset) {
        let vbuf = buffer._vData;
        let uintbuf = buffer._uintVData;

        let x = pos.x, y = pos.y;
        let width = particle.size;
        let height = width;
        let aspectRatio = particle.aspectRatio;
        aspectRatio > 1 ? (height = width / aspectRatio) : (width = height * aspectRatio);
        width = width / this.animFrameNums;
        let halfWidth = width / 2;
        let halfHeight = height / 2;
        // pos
        if (particle.rotation) {
            let x1 = -halfWidth, y1 = -halfHeight;
            let x2 = halfWidth, y2 = halfHeight;
            let rad = -cc.misc.degreesToRadians(particle.rotation);
            let cr = Math.cos(rad), sr = Math.sin(rad);
            // bl
            vbuf[offset] = x1 * cr - y1 * sr + x;
            vbuf[offset + 1] = x1 * sr + y1 * cr + y;
            // br
            vbuf[offset + 5] = x2 * cr - y1 * sr + x;
            vbuf[offset + 6] = x2 * sr + y1 * cr + y;
            // tl
            vbuf[offset + 10] = x1 * cr - y2 * sr + x;
            vbuf[offset + 11] = x1 * sr + y2 * cr + y;
            // tr
            vbuf[offset + 15] = x2 * cr - y2 * sr + x;
            vbuf[offset + 16] = x2 * sr + y2 * cr + y;
        }
        else {
            // bl
            vbuf[offset] = x - halfWidth;
            vbuf[offset + 1] = y - halfHeight;
            // br
            vbuf[offset + 5] = x + halfWidth;
            vbuf[offset + 6] = y - halfHeight;
            // tl
            vbuf[offset + 10] = x - halfWidth;
            vbuf[offset + 11] = y + halfHeight;
            // tr
            vbuf[offset + 15] = x + halfWidth;
            vbuf[offset + 16] = y + halfHeight;
        }


        if (this.animFrameNums > 1) {
            if (this.animationRate == -1) {
                //自动的
                var fix = 1 / this.animFrameNums;
                let fixTimes = particle.topLife / this.animFrameNums;
                let idx = Math.floor(particle.timeToLive / fixTimes);
                idx = this.animFrameNums - idx - 1;
                vbuf[offset + 2] = fix * idx;
                vbuf[offset + 12] = fix * idx;
                vbuf[offset + 7] = vbuf[offset + 2] + fix;
                vbuf[offset + 17] = vbuf[offset + 7];
            } else {
                let duration = 1 / this.animationRate;
                var fix = 1 / this.animFrameNums;


                vbuf[offset + 2] = fix * particle.frameindex;
                vbuf[offset + 12] = fix * particle.frameindex;
                vbuf[offset + 7] = vbuf[offset + 2] + fix;
                vbuf[offset + 17] = vbuf[offset + 7];

                if (particle.lastFrameTime - particle.timeToLive > duration) {
                    particle.frameindex += 1;
                    particle.lastFrameTime = particle.timeToLive;
                    if (particle.frameindex >= this.animFrameNums) {
                        particle.frameindex = 0;
                    }
                }
            }
        }

        // color
        uintbuf[offset + 4] = particle.color._val;
        uintbuf[offset + 9] = particle.color._val;
        uintbuf[offset + 14] = particle.color._val;
        uintbuf[offset + 19] = particle.color._val;
    }

    step(dt) {
        dt = dt > cc.director['_maxParticleDeltaTime'] ? cc.director['_maxParticleDeltaTime'] : dt;
        let psys = this.sys;
        let node = psys.node;
        let particles = this.particles;
        const FLOAT_PER_PARTICLE = 4 * this.sys['_assembler']._vfmt._bytes / 4;
        const PositionType = cc.ParticleSystem.PositionType;

        // Calculate pos
        node['_updateWorldMatrix']();
        if (psys.positionType === PositionType.FREE) {
            this._worldRotation = this.getWorldRotation(node);
            let m = node['_worldMatrix'].m;
            this._pos.x = m[12];
            this._pos.y = m[13];
        } else if (psys.positionType === PositionType.RELATIVE) {
            this._worldRotation = node.angle;
            this._pos.x = node.x;
            this._pos.y = node.y;
        } else {
            this._worldRotation = 0;
        }

        // Emission
        if (this.active && psys.emissionRate) {
            var rate = 1.0 / psys.emissionRate;
            //issue #1201, prevent bursts of particles, due to too high emitCounter
            if (particles.length < psys.totalParticles)
                this.emitCounter += dt;

            while ((particles.length < psys.totalParticles) && (this.emitCounter > rate)) {
                this.emitParticle(this._pos);
                this.emitCounter -= rate;
            }

            this.elapsed += dt;
            if (psys.duration !== -1 && psys.duration < this.elapsed) {
                psys.stopSystem();
            }
        }

        // Request buffer for particles
        let buffer = psys['_assembler'].getBuffer();
        let particleCount = particles.length;
        buffer.reset();
        buffer.request(particleCount * 4, particleCount * 6);

        // Fill up uvs
        if (particleCount > this._uvFilled) {
            this.updateUVs(false);
        }

        // Used to reduce memory allocation / creation within the loop
        let particleIdx = 0;
        while (particleIdx < particles.length) {
            // Reset temporary vectors
            this._tpa.x = this._tpa.y = this._tpb.x = this._tpb.y = this._tpc.x = this._tpc.y = 0;

            let particle = particles[particleIdx];

            // life
            particle.timeToLive -= dt;
            if (particle.timeToLive > 0) {
                // Mode A: gravity, direction, tangential accel & radial accel
                if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) {
                    let tmp = this._tpc, radial = this._tpa, tangential = this._tpb;

                    // radial acceleration
                    if (particle.pos.x || particle.pos.y) {
                        radial.set(particle.pos);
                        radial.normalizeSelf();
                    }
                    tangential.set(radial);
                    radial.mulSelf(particle.radialAccel);

                    // tangential acceleration
                    let newy = tangential.x;
                    tangential.x = -tangential.y;
                    tangential.y = newy;

                    tangential.mulSelf(particle.tangentialAccel);

                    tmp.set(radial);
                    tmp.addSelf(tangential);
                    tmp.addSelf(psys.gravity);
                    tmp.mulSelf(dt);
                    particle.dir.addSelf(tmp);

                    tmp.set(particle.dir);
                    tmp.mulSelf(dt);
                    particle.pos.addSelf(tmp);
                }
                // Mode B: radius movement
                else {
                    // Update the angle and radius of the particle.
                    particle.angle += particle.degreesPerSecond * dt;
                    particle.radius += particle.deltaRadius * dt;

                    particle.pos.x = -Math.cos(particle.angle) * particle.radius;
                    particle.pos.y = -Math.sin(particle.angle) * particle.radius;
                }

                // color
                particle.color.r += particle.deltaColor.r * dt;
                particle.color.g += particle.deltaColor.g * dt;
                particle.color.b += particle.deltaColor.b * dt;
                particle.color.a += particle.deltaColor.a * dt;

                // size
                particle.size += particle.deltaSize * dt;
                if (particle.size < 0) {
                    particle.size = 0;
                }

                // angle
                particle.rotation += particle.deltaRotation * dt;

                // update values in quad buffer
                let newPos = this._tpa;
                newPos.set(particle.pos);
                if (psys.positionType !== PositionType.GROUPED) {
                    newPos.addSelf(particle.startPos);
                }

                let offset = FLOAT_PER_PARTICLE * particleIdx;
                this.updateParticleBuffer(particle, newPos, buffer, offset);

                // update particle counter
                ++particleIdx;
            } else {
                // life < 0
                let deadParticle = particles[particleIdx];
                if (particleIdx !== particles.length - 1) {
                    particles[particleIdx] = particles[particles.length - 1];
                }
                this.pool.put(deadParticle);
                particles.length--;
            }
        }

        if (particles.length > 0) {
            buffer.uploadData();
            psys['_assembler']._ia._count = particles.length * 6;
        }
        else if (!this.active && !this.readyToPlay) {
            this.finished = true;
            psys['_finishedSimulation']();
        }
    }

    // Simulator.prototype.step = function (dt) {
    // dt = dt > cc.director._maxParticleDeltaTime ? cc.director._maxParticleDeltaTime : dt;
    // let psys = this.sys;
    // let node = psys.node;
    // let particles = this.particles;
    // const FLOAT_PER_PARTICLE = 4 * this.sys._assembler._vfmt._bytes / 4;
    // const PositionType = cc.ParticleSystem.PositionType;

    // // Calculate pos
    // node._updateWorldMatrix();
    // if (psys.positionType === PositionType.FREE) {
    //     this._worldRotation = getWorldRotation(node);
    //     let m = node._worldMatrix.m;
    //     _pos.x = m[12];
    //     _pos.y = m[13];
    // } else if (psys.positionType === PositionType.RELATIVE) {
    //     this._worldRotation = node.angle;
    //     _pos.x = node.x;
    //     _pos.y = node.y;
    // } else {
    //     this._worldRotation = 0;
    // }

    // // Emission
    // if (this.active && psys.emissionRate) {
    //     var rate = 1.0 / psys.emissionRate;
    //     //issue #1201, prevent bursts of particles, due to too high emitCounter
    //     if (particles.length < psys.totalParticles)
    //         this.emitCounter += dt;

    //     while ((particles.length < psys.totalParticles) && (this.emitCounter > rate)) {
    //         this.emitParticle(_pos);
    //         this.emitCounter -= rate;
    //     }

    //     this.elapsed += dt;
    //     if (psys.duration !== -1 && psys.duration < this.elapsed) {
    //         psys.stopSystem();
    //     }
    // }

    // // Request buffer for particles
    // let buffer = psys._assembler.getBuffer();
    // let particleCount = particles.length;
    // buffer.reset();
    // buffer.request(particleCount * 4, particleCount * 6);

    // // Fill up uvs
    // if (particleCount > this._uvFilled) {
    //     this.updateUVs();
    // }

    // // Used to reduce memory allocation / creation within the loop
    // let particleIdx = 0;
    // while (particleIdx < particles.length) {
    //     // Reset temporary vectors
    //     _tpa.x = _tpa.y = _tpb.x = _tpb.y = _tpc.x = _tpc.y = 0;

    //     let particle = particles[particleIdx];

    //     // life
    //     particle.timeToLive -= dt;
    //     if (particle.timeToLive > 0) {
    //         // Mode A: gravity, direction, tangential accel & radial accel
    //         if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) {
    //             let tmp = _tpc, radial = _tpa, tangential = _tpb;

    //             // radial acceleration
    //             if (particle.pos.x || particle.pos.y) {
    //                 radial.set(particle.pos);
    //                 radial.normalizeSelf();
    //             }
    //             tangential.set(radial);
    //             radial.mulSelf(particle.radialAccel);

    //             // tangential acceleration
    //             let newy = tangential.x;
    //             tangential.x = -tangential.y;
    //             tangential.y = newy;

    //             tangential.mulSelf(particle.tangentialAccel);

    //             tmp.set(radial);
    //             tmp.addSelf(tangential);
    //             tmp.addSelf(psys.gravity);
    //             tmp.mulSelf(dt);
    //             particle.dir.addSelf(tmp);

    //             tmp.set(particle.dir);
    //             tmp.mulSelf(dt);
    //             particle.pos.addSelf(tmp);
    //         }
    //         // Mode B: radius movement
    //         else {
    //             // Update the angle and radius of the particle.
    //             particle.angle += particle.degreesPerSecond * dt;
    //             particle.radius += particle.deltaRadius * dt;

    //             particle.pos.x = -Math.cos(particle.angle) * particle.radius;
    //             particle.pos.y = -Math.sin(particle.angle) * particle.radius;
    //         }

    //         // color
    //         particle.color.r += particle.deltaColor.r * dt;
    //         particle.color.g += particle.deltaColor.g * dt;
    //         particle.color.b += particle.deltaColor.b * dt;
    //         particle.color.a += particle.deltaColor.a * dt;

    //         // size
    //         particle.size += particle.deltaSize * dt;
    //         if (particle.size < 0) {
    //             particle.size = 0;
    //         }

    //         // angle
    //         particle.rotation += particle.deltaRotation * dt;

    //         // update values in quad buffer
    //         let newPos = _tpa;
    //         newPos.set(particle.pos);
    //         if (psys.positionType !== PositionType.GROUPED) {
    //             newPos.addSelf(particle.startPos);
    //         }

    //         let offset = FLOAT_PER_PARTICLE * particleIdx;
    //         this.updateParticleBuffer(particle, newPos, buffer, offset);

    //         // update particle counter
    //         ++particleIdx;
    //     } else {
    //         // life < 0
    //         let deadParticle = particles[particleIdx];
    //         if (particleIdx !== particles.length - 1) {
    //             particles[particleIdx] = particles[particles.length - 1];
    //         }
    //         pool.put(deadParticle);
    //         particles.length--;
    //     }
    // }

    // if (particles.length > 0) {
    //     buffer.uploadData();
    //     psys._assembler._ia._count = particles.length * 6;
    // }
    // else if (!this.active && !this.readyToPlay) {
    //     this.finished = true;
    //     psys._finishedSimulation();
    // }
    // }
}