// @ts-ignore
const gfx = cc.gfx;

// @ts-ignore
const spine = sp.spine;

// @ts-ignore
const VFOneColor = gfx.VertexFormat.vfmtPosUvColor;
// @ts-ignore
const VFTwoColor = gfx.VertexFormat.vfmtPosUvTwoColor;

const FLAG_BATCH = 0x10;
const FLAG_TWO_COLOR = 0x01;

let _handleVal = 0x00;
let _quadTriangles = [0, 1, 2, 2, 3, 0];
let _slotColor = cc.color(0, 0, 255, 255);
let _boneColor = cc.color(255, 0, 0, 255);
let _originColor = cc.color(0, 255, 0, 255);
let _meshColor = cc.color(255, 255, 0, 255);

let _finalColor = null;
let _darkColor = null;
let _tempPos = null, _tempUv = null;
// @ts-ignore
if (!CC_NATIVERENDERER) {
    _finalColor = new spine.Color(1, 1, 1, 1);
    _darkColor = new spine.Color(1, 1, 1, 1);
    _tempPos = new spine.Vector2();
    _tempUv = new spine.Vector2();
}

let _premultipliedAlpha;
let _multiplier;
let _slotRangeStart;
let _slotRangeEnd;
let _useTint;
let _debugSlots;
let _debugBones;
let _debugMesh;
let _nodeR,
    _nodeG,
    _nodeB,
    _nodeA;
let _finalColor32, _darkColor32;
let _vertexFormat;
let _perVertexSize;
let _perClipVertexSize;

let _vertexFloatCount = 0, _vertexCount = 0, _vertexFloatOffset = 0, _vertexOffset = 0,
    _indexCount = 0, _indexOffset = 0, _vfOffset = 0;
let _tempr, _tempg, _tempb;
let _inRange;
let _mustFlush;
let _x, _y, _m00, _m04, _m12, _m01, _m05, _m13;
let _r, _g, _b, _fr, _fg, _fb, _fa, _dr, _dg, _db, _da;
let _comp, _buffer, _renderer, _node, _needColor, _vertexEffect;

function _getSlotMaterial (tex, blendMode) {
    let src, dst;
    switch (blendMode) {
        case spine.BlendMode.Additive:
            src = _premultipliedAlpha ? cc.macro.ONE : cc.macro.SRC_ALPHA;
            dst = cc.macro.ONE;
            break;
        case spine.BlendMode.Multiply:
            src = cc.macro.DST_COLOR;
            dst = cc.macro.ONE_MINUS_SRC_ALPHA;
            break;
        case spine.BlendMode.Screen:
            src = cc.macro.ONE;
            dst = cc.macro.ONE_MINUS_SRC_COLOR;
            break;
        case spine.BlendMode.Normal:
        default:
            src = _premultipliedAlpha ? cc.macro.ONE : cc.macro.SRC_ALPHA;
            dst = cc.macro.ONE_MINUS_SRC_ALPHA;
            break;
    }

    let useModel = !_comp.enableBatch;
    let baseMaterial = _comp._materials[0];
    if (!baseMaterial) return null;

    // The key use to find corresponding material
    let key = tex.getId() + src + dst + _useTint + useModel;
    let materialCache = _comp._materialCache;
    let material = materialCache[key];
    if (!material) {
        if (!materialCache.baseMaterial) {
            material = baseMaterial;
            materialCache.baseMaterial = baseMaterial;
        } else {
            material = cc.MaterialVariant.create(baseMaterial, null);
        }
        
        material.define('CC_USE_MODEL', useModel);
        material.define('USE_TINT', _useTint);
        // update texture
        material.setProperty('texture', tex);

        // update blend function
        material.setBlend(
            true,
            gfx.BLEND_FUNC_ADD,
            src, dst,
            gfx.BLEND_FUNC_ADD,
            src, dst
        );
        materialCache[key] = material;
    }
    return material;
}

function _handleColor (color) {
    // temp rgb has multiply 255, so need divide 255;
    _fa = color.fa * _nodeA;
    _multiplier = _premultipliedAlpha ? _fa / 255 : 1;
    _r = _nodeR * _multiplier;
    _g = _nodeG * _multiplier;
    _b = _nodeB * _multiplier;

    _fr = color.fr * _r;
    _fg = color.fg * _g;
    _fb = color.fb * _b;
    _finalColor32 = ((_fa<<24) >>> 0) + (_fb<<16) + (_fg<<8) + _fr;

    _dr = color.dr * _r;
    _dg = color.dg * _g;
    _db = color.db * _b;
    _da = _premultipliedAlpha ? 255 : 0;
    _darkColor32 = ((_da<<24) >>> 0) + (_db<<16) + (_dg<<8) + _dr;
}

function _spineColorToInt32 (spineColor) {
    return ((spineColor.a<<24) >>> 0) + (spineColor.b<<16) + (spineColor.g<<8) + spineColor.r;
}

// @ts-ignore
sp.Skeleton.__assembler__.fillVertices = function (skeletonColor, attachmentColor, slotColor, clipper, slot) {

    let vbuf = _buffer._vData,
        ibuf = _buffer._iData,
        uintVData = _buffer._uintVData;
    let offsetInfo;

    _finalColor.a = slotColor.a * attachmentColor.a * skeletonColor.a * _nodeA * 255;
    _multiplier = _premultipliedAlpha? _finalColor.a : 255;
    _tempr = _nodeR * attachmentColor.r * skeletonColor.r * _multiplier;
    _tempg = _nodeG * attachmentColor.g * skeletonColor.g * _multiplier;
    _tempb = _nodeB * attachmentColor.b * skeletonColor.b * _multiplier;
    
    _finalColor.r = _tempr * slotColor.r;
    _finalColor.g = _tempg * slotColor.g;
    _finalColor.b = _tempb * slotColor.b;

    if (slot.darkColor == null) {
        _darkColor.set(0.0, 0.0, 0.0, 1.0);
    } else {
        _darkColor.r = slot.darkColor.r * _tempr;
        _darkColor.g = slot.darkColor.g * _tempg;
        _darkColor.b = slot.darkColor.b * _tempb;
    }
    _darkColor.a = _premultipliedAlpha ? 255 : 0;

    if (!clipper.isClipping()) {
        if (_vertexEffect) {
            for (let v = _vertexFloatOffset, n = _vertexFloatOffset + _vertexFloatCount; v < n; v += _perVertexSize) {
                _tempPos.x = vbuf[v];
                _tempPos.y = vbuf[v + 1];
                _tempUv.x = vbuf[v + 2];
                _tempUv.y = vbuf[v + 3];
                _vertexEffect.transform(_tempPos, _tempUv, _finalColor, _darkColor);

                vbuf[v]     = _tempPos.x;        // x
                vbuf[v + 1] = _tempPos.y;        // y
                vbuf[v + 2] = _tempUv.x;         // u
                vbuf[v + 3] = _tempUv.y;         // v
                uintVData[v + 4]  = _spineColorToInt32(_finalColor);                  // light color
                _useTint && (uintVData[v + 5] = _spineColorToInt32(_darkColor));      // dark color
            }
        } else {
            _finalColor32 = _spineColorToInt32(_finalColor);
            _darkColor32 = _spineColorToInt32(_darkColor);
            
            for (let v = _vertexFloatOffset, n = _vertexFloatOffset + _vertexFloatCount; v < n; v += _perVertexSize) {
                uintVData[v + 4]  = _finalColor32;                   // light color
                _useTint && (uintVData[v + 5]  = _darkColor32);      // dark color
            }
        }
    } else {
        let uvs = vbuf.subarray(_vertexFloatOffset + 2);
        clipper.clipTriangles(vbuf.subarray(_vertexFloatOffset), _vertexFloatCount, ibuf.subarray(_indexOffset), _indexCount, uvs, _finalColor, _darkColor, _useTint, _perVertexSize);
        let clippedVertices = new Float32Array(clipper.clippedVertices);
        let clippedTriangles = clipper.clippedTriangles;
        
        // insure capacity
        _indexCount = clippedTriangles.length;
        _vertexFloatCount = clippedVertices.length / _perClipVertexSize * _perVertexSize;

        offsetInfo = _buffer.request(_vertexFloatCount / _perVertexSize, _indexCount);
        _indexOffset = offsetInfo.indiceOffset,
        _vertexOffset = offsetInfo.vertexOffset,
        _vertexFloatOffset = offsetInfo.byteOffset >> 2;
        vbuf = _buffer._vData,
        ibuf = _buffer._iData;
        uintVData = _buffer._uintVData;

        // fill indices
        ibuf.set(clippedTriangles, _indexOffset);

        // fill vertices contain x y u v light color dark color
        if (_vertexEffect) {
            for (let v = 0, n = clippedVertices.length, offset = _vertexFloatOffset; v < n; v += _perClipVertexSize, offset += _perVertexSize) {
                _tempPos.x = clippedVertices[v];
                _tempPos.y = clippedVertices[v + 1];
                _finalColor.set(clippedVertices[v + 2], clippedVertices[v + 3], clippedVertices[v + 4], clippedVertices[v + 5]);
                _tempUv.x = clippedVertices[v + 6];
                _tempUv.y = clippedVertices[v + 7];
                if (_useTint) {
                    _darkColor.set(clippedVertices[v + 8], clippedVertices[v + 9], clippedVertices[v + 10], clippedVertices[v + 11]);
                } else {
                    _darkColor.set(0, 0, 0, 0);
                }
                _vertexEffect.transform(_tempPos, _tempUv, _finalColor, _darkColor);

                vbuf[offset] = _tempPos.x;             // x
                vbuf[offset + 1] = _tempPos.y;         // y
                vbuf[offset + 2] = _tempUv.x;          // u
                vbuf[offset + 3] = _tempUv.y;          // v
                uintVData[offset + 4] = _spineColorToInt32(_finalColor);
                if (_useTint) {
                    uintVData[offset + 5] = _spineColorToInt32(_darkColor);
                }
            }
        } else {
            for (let v = 0, n = clippedVertices.length, offset = _vertexFloatOffset; v < n; v += _perClipVertexSize, offset += _perVertexSize) {
                vbuf[offset]     = clippedVertices[v];         // x
                vbuf[offset + 1] = clippedVertices[v + 1];     // y
                vbuf[offset + 2] = clippedVertices[v + 6];     // u
                vbuf[offset + 3] = clippedVertices[v + 7];     // v

                _finalColor32 = ((clippedVertices[v + 5]<<24) >>> 0) + (clippedVertices[v + 4]<<16) + (clippedVertices[v + 3]<<8) + clippedVertices[v + 2];
                uintVData[offset + 4] = _finalColor32;

                if (_useTint) {
                    _darkColor32 = ((clippedVertices[v + 11]<<24) >>> 0) + (clippedVertices[v + 10]<<16) + (clippedVertices[v + 9]<<8) + clippedVertices[v + 8];
                    uintVData[offset + 5] = _darkColor32;
                }
            }
        }
    }
}

// @ts-ignore
sp.Skeleton.__assembler__.realTimeTraverse = function (worldMat) {
    let vbuf;
    let ibuf;

    let locSkeleton = _comp._skeleton;
    let skeletonColor = locSkeleton.color;
    let graphics = _comp._debugRenderer;
    let clipper = _comp._clipper;
    let material = null;
    let attachment, attachmentColor, slotColor, uvs, triangles;
    let isRegion, isMesh, isClip;
    let offsetInfo;
    let slot;
    let worldMatm;

    _slotRangeStart = _comp._startSlotIndex;
    _slotRangeEnd = _comp._endSlotIndex;
    _inRange = false;
    if (_slotRangeStart == -1) _inRange = true;

    _debugSlots = _comp.debugSlots;
    _debugBones = _comp.debugBones;
    _debugMesh = _comp.debugMesh;
    if (graphics && (_debugBones || _debugSlots || _debugMesh)) {
        graphics.clear();
        graphics.lineWidth = 2;
    }

    // x y u v r1 g1 b1 a1 r2 g2 b2 a2 or x y u v r g b a 
    _perClipVertexSize = _useTint ? 12 : 8;

    _vertexFloatCount = 0;
    _vertexFloatOffset = 0;
    _vertexOffset = 0;
    _indexCount = 0;
    _indexOffset = 0;

    for (let slotIdx = 0, slotCount = locSkeleton.drawOrder.length; slotIdx < slotCount; slotIdx++) {
        slot = locSkeleton.drawOrder[slotIdx];

        if(slot == undefined || !slot.bone.active) {
            continue;
        }

        if (_slotRangeStart >= 0 && _slotRangeStart == slot.data.index) {
            _inRange = true;
        }
        
        if (!_inRange) {
            clipper.clipEndWithSlot(slot);
            continue;
        }

        if (_slotRangeEnd >= 0 && _slotRangeEnd == slot.data.index) {
            _inRange = false;
        }

        _vertexFloatCount = 0;
        _indexCount = 0;

        attachment = slot.getAttachment();
        if (!attachment) {
            clipper.clipEndWithSlot(slot);
            continue;
        }

        isRegion = attachment instanceof spine.RegionAttachment;
        isMesh = attachment instanceof spine.MeshAttachment;
        isClip = attachment instanceof spine.ClippingAttachment;

        if (isClip) {
            clipper.clipStart(slot, attachment);
            continue;
        }

        if (!isRegion && !isMesh) {
            clipper.clipEndWithSlot(slot);
            continue;
        }

        material = _getSlotMaterial(attachment.region.texture._texture, slot.data.blendMode);
        if (!material) {
            clipper.clipEndWithSlot(slot);
            continue;
        }

        if (_mustFlush || material.getHash() !== _renderer.material.getHash()) {
            _mustFlush = false;
            _renderer._flush();
            _renderer.node = _node;
            _renderer.material = material;
        }

        if (isRegion) {
            
            triangles = _quadTriangles;

            // insure capacity
            _vertexFloatCount = 4 * _perVertexSize;
            _indexCount = 6;

            offsetInfo = _buffer.request(4, 6);
            _indexOffset = offsetInfo.indiceOffset,
            _vertexOffset = offsetInfo.vertexOffset,
            _vertexFloatOffset = offsetInfo.byteOffset >> 2;
            vbuf = _buffer._vData,
            ibuf = _buffer._iData;

            // compute vertex and fill x y
            attachment.computeWorldVertices(slot.bone, vbuf, _vertexFloatOffset, _perVertexSize);

            // draw debug slots if enabled graphics
            if (graphics && _debugSlots) {
                graphics.strokeColor = _slotColor;
                graphics.moveTo(vbuf[_vertexFloatOffset], vbuf[_vertexFloatOffset + 1]);
                for (let ii = _vertexFloatOffset + _perVertexSize, nn = _vertexFloatOffset + _vertexFloatCount; ii < nn; ii += _perVertexSize) {
                    graphics.lineTo(vbuf[ii], vbuf[ii + 1]);
                }
                graphics.close();
                graphics.stroke();
            }
        }
        else if (isMesh) {
            
            triangles = attachment.triangles;

            // insure capacity
            _vertexFloatCount = (attachment.worldVerticesLength >> 1) * _perVertexSize;
            _indexCount = triangles.length;

            offsetInfo = _buffer.request(_vertexFloatCount / _perVertexSize, _indexCount);
            _indexOffset = offsetInfo.indiceOffset,
            _vertexOffset = offsetInfo.vertexOffset,
            _vertexFloatOffset = offsetInfo.byteOffset >> 2;
            vbuf = _buffer._vData,
            ibuf = _buffer._iData;

            // compute vertex and fill x y
            attachment.computeWorldVertices(slot, 0, attachment.worldVerticesLength, vbuf, _vertexFloatOffset, _perVertexSize);

            // draw debug mesh if enabled graphics
            if (graphics && _debugMesh) {
                graphics.strokeColor = _meshColor;

                for (let ii = 0, nn = triangles.length; ii < nn; ii += 3) {
                    let v1 = triangles[ii] * _perVertexSize + _vertexFloatOffset;
                    let v2 = triangles[ii + 1] * _perVertexSize + _vertexFloatOffset;
                    let v3 = triangles[ii + 2] * _perVertexSize + _vertexFloatOffset;
                    
                    graphics.moveTo(vbuf[v1], vbuf[v1 + 1]);
                    graphics.lineTo(vbuf[v2], vbuf[v2 + 1]);
                    graphics.lineTo(vbuf[v3], vbuf[v3 + 1]);
                    graphics.close();
                    graphics.stroke();
                }
            }
        }

        if (_vertexFloatCount == 0 || _indexCount == 0) {
            clipper.clipEndWithSlot(slot);
            continue;
        }

        // fill indices
        ibuf.set(triangles, _indexOffset);

        // fill u v
        uvs = attachment.uvs;
        for (let v = _vertexFloatOffset, n = _vertexFloatOffset + _vertexFloatCount, u = 0; v < n; v += _perVertexSize, u += 2) {
            vbuf[v + 2] = uvs[u];           // u
            vbuf[v + 3] = uvs[u + 1];       // v
        }

        attachmentColor = attachment.color,
        slotColor = slot.color;

        this.fillVertices(skeletonColor, attachmentColor, slotColor, clipper, slot);
        
        // reset buffer pointer, because clipper maybe realloc a new buffer in file Vertices function.
        vbuf = _buffer._vData,
        ibuf = _buffer._iData;

        if (_indexCount > 0) {
            for (let ii = _indexOffset, nn = _indexOffset + _indexCount; ii < nn; ii++) {
                ibuf[ii] += _vertexOffset;
            }

            if (worldMat) {
                worldMatm = worldMat.m;
                _m00 = worldMatm[0];
                _m04 = worldMatm[4];
                _m12 = worldMatm[12];
                _m01 = worldMatm[1];
                _m05 = worldMatm[5];
                _m13 = worldMatm[13];
                for (let ii = _vertexFloatOffset, nn = _vertexFloatOffset + _vertexFloatCount; ii < nn; ii += _perVertexSize) {
                    _x = vbuf[ii];
                    _y = vbuf[ii + 1];
                    vbuf[ii] = _x * _m00 + _y * _m04 + _m12;
                    vbuf[ii + 1] = _x * _m01 + _y * _m05 + _m13;
                }
            }
            _buffer.adjust(_vertexFloatCount / _perVertexSize, _indexCount);
        }

        clipper.clipEndWithSlot(slot);
    }

    clipper.clipEnd();

    if (graphics && _debugBones) {
        let bone;
        graphics.strokeColor = _boneColor;
        graphics.fillColor = _slotColor; // Root bone color is same as slot color.

        for (let i = 0, n = locSkeleton.bones.length; i < n; i++) {
            bone = locSkeleton.bones[i];
            let x = bone.data.length * bone.a + bone.worldX;
            let y = bone.data.length * bone.c + bone.worldY;

            // Bone lengths.
            graphics.moveTo(bone.worldX, bone.worldY);
            graphics.lineTo(x, y);
            graphics.stroke();

            // Bone origins.
            graphics.circle(bone.worldX, bone.worldY, Math.PI * 1.5);
            graphics.fill();
            if (i === 0) {
                graphics.fillColor = _originColor;
            }
        }
    }
}

// @ts-ignore
sp.Skeleton.__assembler__.cacheTraverse = function (worldMat) {
    
    let frame = _comp._curFrame;
    if (!frame) return;

    let segments = frame.segments;
    if (segments.length == 0) return;

    let vbuf, ibuf, uintbuf;
    let material;
    let offsetInfo;
    let vertices = frame.vertices;
    let indices = frame.indices;
    let worldMatm;

    let frameVFOffset = 0, frameIndexOffset = 0, segVFCount = 0;
    if (worldMat) {
        worldMatm = worldMat.m;
        _m00 = worldMatm[0];
        _m01 = worldMatm[1];
        _m04 = worldMatm[4];
        _m05 = worldMatm[5];
        _m12 = worldMatm[12];
        _m13 = worldMatm[13];
    }

    let justTranslate = _m00 === 1 && _m01 === 0 && _m04 === 0 && _m05 === 1;
    let needBatch = (_handleVal & FLAG_BATCH);
    let calcTranslate = needBatch && justTranslate;

    let colorOffset = 0;
    let colors = frame.colors;
    let nowColor = colors[colorOffset++];
    let maxVFOffset = nowColor.vfOffset;
    _handleColor(nowColor);

    for (let i = 0, n = segments.length; i < n; i++) {
        let segInfo = segments[i];
        material = _getSlotMaterial(segInfo.tex, segInfo.blendMode);
        if (!material) continue;

        if (_mustFlush || material.getHash() !== _renderer.material.getHash()) {
            _mustFlush = false;
            _renderer._flush();
            _renderer.node = _node;
            _renderer.material = material;
        }

        _vertexCount = segInfo.vertexCount;
        _indexCount = segInfo.indexCount;

        offsetInfo = _buffer.request(_vertexCount, _indexCount);
        _indexOffset = offsetInfo.indiceOffset;
        _vertexOffset = offsetInfo.vertexOffset;
        _vfOffset = offsetInfo.byteOffset >> 2;
        vbuf = _buffer._vData;
        ibuf = _buffer._iData;
        uintbuf = _buffer._uintVData;

        for (let ii = _indexOffset, il = _indexOffset + _indexCount; ii < il; ii++) {
            ibuf[ii] = _vertexOffset + indices[frameIndexOffset++];
        }

        segVFCount = segInfo.vfCount;
        vbuf.set(vertices.subarray(frameVFOffset, frameVFOffset + segVFCount), _vfOffset);
        frameVFOffset += segVFCount;

        if (calcTranslate) {
            for (let ii = _vfOffset, il = _vfOffset + segVFCount; ii < il; ii += 6) {
                vbuf[ii] += _m12;
                vbuf[ii + 1] += _m13;
            }
        } else if (needBatch) {
            for (let ii = _vfOffset, il = _vfOffset + segVFCount; ii < il; ii += 6) {
                _x = vbuf[ii];
                _y = vbuf[ii + 1];
                vbuf[ii] = _x * _m00 + _y * _m04 + _m12;
                vbuf[ii + 1] = _x * _m01 + _y * _m05 + _m13;
            }
        }

        _buffer.adjust(_vertexCount, _indexCount);
        if ( !_needColor ) continue;

        // handle color
        let frameColorOffset = frameVFOffset - segVFCount;
        for (let ii = _vfOffset + 4, il = _vfOffset + 4 + segVFCount; ii < il; ii += 6, frameColorOffset += 6) {
            if (frameColorOffset >= maxVFOffset) {
                nowColor = colors[colorOffset++];
                _handleColor(nowColor);
                maxVFOffset = nowColor.vfOffset;
            }
            uintbuf[ii] = _finalColor32;
            uintbuf[ii + 1] = _darkColor32;
        }
    }
}

// @ts-ignore
sp.Skeleton.__assembler__.fillBuffers = function (comp, renderer) {
    
    let node = comp.node;
    // @ts-ignore
    node._renderFlag |= cc.RenderFlow.FLAG_UPDATE_RENDER_DATA;
    if (!comp._skeleton) return;

    let nodeColor = node._color;
    _nodeR = nodeColor.r / 255;
    _nodeG = nodeColor.g / 255;
    _nodeB = nodeColor.b / 255;
    _nodeA = nodeColor.a / 255;

    _useTint = comp.useTint || comp.isAnimationCached();
    _vertexFormat = _useTint? VFTwoColor : VFOneColor;
    // x y u v color1 color2 or x y u v color
    _perVertexSize = _useTint ? 6 : 5;

    _node = comp.node;
    _buffer = renderer.getBuffer('spine', _vertexFormat);
    _renderer = renderer;
    _comp = comp;

    _mustFlush = true;
    _premultipliedAlpha = comp.premultipliedAlpha;
    _multiplier = 1.0;
    _handleVal = 0x00;
    _needColor = false;
    _vertexEffect = comp._effectDelegate && comp._effectDelegate._vertexEffect;

    if (nodeColor._val !== 0xffffffff || _premultipliedAlpha) {
        _needColor = true;
    }

    if (_useTint) {
        _handleVal |= FLAG_TWO_COLOR;
    }

    let worldMat = undefined;
    if (_comp.enableBatch) {
        worldMat = _node._worldMatrix;
        _mustFlush = false;
        _handleVal |= FLAG_BATCH;
    }

    if (comp.isAnimationCached()) {
        // Traverse input assembler.
        this.cacheTraverse(worldMat);
    } else {
        if (_vertexEffect) _vertexEffect.begin(comp._skeleton);
        this.realTimeTraverse(worldMat);
        if (_vertexEffect) _vertexEffect.end();
    }

    // Clear temp var.
    _node = undefined;
    _buffer = undefined;
    _renderer = undefined;
    _comp = undefined;
    _vertexEffect = null;
}