import { SplitRenderHelper } from "./SplitRenderHelper";
import { _decorator, Node, Vec3, IAssembler, ccenum, Vec2, Mat4, Texture2D, v2, v3, EventTouch, gfx, SpriteFrame, UIRenderer } from 'cc';

const { ccclass, property, executeInEditMode } = _decorator;

const vec3_temps: Vec3[] = [];
for (let i = 0; i < 4; i++) {
    vec3_temps.push(new Vec3());
}

class AssemblerSplit implements IAssembler {
    createData(com: SplitRender) {
        let vertexCount = 4;
        let indexCount = 6;

        const renderData = com.requestRenderData();
        renderData.dataLength = vertexCount;
        renderData.resize(vertexCount, indexCount);
        return renderData;
    }

    resetData(com: SplitRender) {
        let points = com.polygon;
        if (!points || points.length < 3) return;

        let vertexCount = points.length;
        let indexCount = vertexCount + (vertexCount - 3) * 2;

        com.renderData.clear();
        com.renderData.dataLength = vertexCount;
        com.renderData.resize(vertexCount, indexCount);

        let material = com.renderData.material;
        com.renderData.material = material;
    }

    updateRenderData(com: SplitRender) {
        // dynamicAtlasManager.packToDynamicAtlas(com, frame);
        const renderData = com.renderData;
        if (renderData.vertDirty) {
            this.resetData(com);
            this.updateVertexData(com);
            this.updateUvs(com);
            this.updateColor(com);
            renderData.updateRenderData(com, com.spriteFrame);
        }
    }

    updateWorldVerts(com: SplitRender, verts: Float32Array) {
        let floatsPerVert = 9;

        let matrix: Mat4 = com.node.worldMatrix;
        let a = matrix.m00, b = matrix.m01, c = matrix.m04, d = matrix.m05,
            tx = matrix.m12, ty = matrix.m13;

        let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;
        if (justTranslate) {
            let polygon = com.polygon;
            for (let i = 0; i < polygon.length; i++) {
                verts[i * floatsPerVert] = polygon[i].x + tx;
                verts[i * floatsPerVert + 1] = polygon[i].y + ty;
            }
        } else {
            let polygon = com.polygon;
            for (let i = 0; i < polygon.length; i++) {
                verts[i * floatsPerVert] = a * polygon[i].x + c * polygon[i].y + tx;
                verts[i * floatsPerVert + 1] = b * polygon[i].x + d * polygon[i].y + ty;
            }
        }

        // @ts-ignore
        com.node._uiProps.uiTransformDirty = false;
    }

    fillBuffers(com: SplitRender, renderer: any) {
        const chunk = com.renderData.chunk;
        // indices generated
        let indicesArr = SplitRenderHelper.splitPolygon(com.polygon);
        this.updateWorldVerts(com, chunk.vb);

        // quick version
        const bid = chunk.bufferId;
        const vid = chunk.vertexOffset;
        const meshBuffer = chunk.vertexAccessor.getMeshBuffer(bid);
        const ib = chunk.vertexAccessor.getIndexBuffer(bid);
        let indexOffset = meshBuffer.indexOffset;

        // fill indices
        for (let i = 0, l = indicesArr.length; i < l; i++) {
            ib[indexOffset++] = vid + indicesArr[i];
        }
        meshBuffer.indexOffset += indicesArr.length;
    }

    updateVertexData(com: SplitRender) {
        const renderData = com.renderData;
        if (!renderData) {
            return;
        }
        const dataList = renderData.data;

        let polygon = com.polygon;
        for (let i = 0; i < polygon.length; i++) {
            dataList[i].x = polygon[i].x;
            dataList[i].y = polygon[i].y;
        }

        const chunk = com.renderData.chunk;
        const vid = chunk.vertexOffset;
        const ib = chunk.ib as any;

        let indicesArr = SplitRenderHelper.splitPolygon(com.polygon);
        for (let i = 0, l = indicesArr.length; i < l; i++) {
            ib[i] = vid + indicesArr[i];
        }
    }

    updateUvs(com: SplitRender) {
        let uvOffset = 3, floatsPerVert = 9;
        const vData = com.renderData.chunk.vb;

        let uvs = [];
        if (com.spriteFrame.texture) {
            uvs = SplitRenderHelper.computeUv(com.polygon, com.spriteFrame.texture.width, com.spriteFrame.texture.height)
        }

        let polygon = com.polygon;
        for (let i = 0; i < polygon.length; i++) {
            vData[uvOffset] = uvs[i].x;
            vData[uvOffset + 1] = uvs[i].y;
            uvOffset += floatsPerVert;
        }
    }

    updateColor(com: SplitRender) {
        const renderData = com.renderData!;

        let colorOffset = 5, floatsPerVert = renderData.floatStride;
        let vData = renderData.chunk.vb;

        const color = com.color;
        const colorR = color.r / 255;
        const colorG = color.g / 255;
        const colorB = color.b / 255;
        const colorA = color.a / 255;

        let polygon = com.polygon;
        for (let i = 0; i < polygon.length; i++) {
            vData![colorOffset] = colorR;
            vData![colorOffset + 1] = colorG;
            vData![colorOffset + 2] = colorB;
            vData![colorOffset + 3] = colorA;
            colorOffset += floatsPerVert;
        }
    }
};

enum TextureType {
    Cut,            // 裁剪
    Stretch         // 拉伸, 暂未实现
}
ccenum(TextureType);

let _vec2_temp = new Vec2();
let _mat4_temp = new Mat4();

@ccclass('SplitRender')
@executeInEditMode
export class SplitRender extends UIRenderer {
    static Type = TextureType;

    @property({ type: SpriteFrame, serializable: true })
    protected _spriteFrame: SpriteFrame | null = null;
    @property({ type: SpriteFrame, serializable: true })
    get spriteFrame() {
        return this._spriteFrame;
    }

    set spriteFrame(value) {
        if (!value || this._spriteFrame === value) {
            this._spriteFrame = value;
            return;
        }

        this._spriteFrame = value;

        let l = -value.width / 2, b = -value.height / 2, t = value.height / 2, r = value.width / 2;
        this.polygon = [v2(l, b), v2(r, b), v2(r, t), v2(l, t)];

        this.markForUpdateRenderData(false);
        this._applySpriteSize();
    }

    @property({ type: TextureType, serializable: true })
    _type: TextureType = 0;
    @property({ type: TextureType, serializable: true })
    get type() {
        return this._type;
    }
    set type(val: TextureType) {
        this._type = val;
        this.markForUpdateRenderData();
    }

    @property
    editing: boolean = false;

    @property({ type: [Vec2], serializable: true })
    _polygon: Vec2[] = [];
    @property({ type: [Vec2], serializable: true })
    public get polygon() {
        return this._polygon;
    }
    public set polygon(points: Vec2[]) {
        this._polygon = points;
        this.markForUpdateRenderData();
    }

    protected _assembler: IAssembler = null;

    constructor() {
        super();
    }

    onLoad() {
        this._renderEntity.setNode(this.node);
        this.node['_hitTest'] = this._hitTest.bind(this);
    }

    start() {
        // this.node.on(Node.EventType.TOUCH_START, (e: EventTouch) => {
        //     console.log("click texture plus -");
        // }, this);

        // this.node.on(Node.EventType.TOUCH_MOVE, (e: EventTouch) => {
        //     console.log("click texture plus +");
        //     this.node.setPosition(v3(this.node.position.x + e.getDeltaX(),
        //         this.node.position.y + e.getDeltaY(),
        //         this.node.position.z));
        // }, this);
        // console.log(this.node.uuid);
    }

    _hitTest(cameraPt: Vec2) {
        let node = this.node;
        let testPt = _vec2_temp;

        node.updateWorldTransform();
        // If scale is 0, it can't be hit.
        if (!Mat4.invert(_mat4_temp, node.worldMatrix)) {
            return false;
        }

        Vec2.transformMat4(testPt, cameraPt, _mat4_temp);
        return SplitRenderHelper.isPointInsidePolygon(testPt, this.polygon);
    }

    private _applySpriteSize() {
        if (this._spriteFrame) {
            const size = this._spriteFrame.originalSize;
            this.node._uiProps.uiTransformComp!.setContentSize(size);
        }

        this._activateMaterial();
    }

    private _activateMaterial() {
        const spriteFrame = this._spriteFrame;
        const material = this.getRenderMaterial(0);
        if (spriteFrame) {
            if (material) {
                this.markForUpdateRenderData();
            }
        }

        if (this.renderData) {
            this.renderData.material = material;
        }
    }

    protected _render(render: any) {
        render.commitComp(this, this.renderData, this._spriteFrame, this._assembler!);
    }

    protected _canRender() {
        if (!super._canRender()) {
            return false;
        }

        const spriteFrame = this._spriteFrame;
        if (!spriteFrame || !spriteFrame.texture) {
            return false;
        }

        return true;
    }

    protected _flushAssembler(): void {
        if (this._assembler == null) {
            this.destroyRenderData();
            this._assembler = new AssemblerSplit();
        }

        if (!this.renderData) {
            if (this._assembler && this._assembler.createData) {
                this._renderData = this._assembler.createData(this);
                this.renderData!.material = this.getRenderMaterial(0);
                this.markForUpdateRenderData();
                this._updateColor();
            }
        }
    }

    protected updateMaterial() {
        if (this._customMaterial) {
            this.setSharedMaterial(this._customMaterial, 0);
            // this._customMaterial.overridePipelineStates({ priority: 128 }, 0);
            // this._blendHash = -1;
            return;
        }
        const mat = this._updateBuiltinMaterial();
        this.setSharedMaterial(mat, 0);
        this._updateBlendFunc();
    }
}
