import { Point, Rectangle } from "../math";
import { Polygon } from "../graphics/shapes";
import Container from "../display/Container";
import Texture from "../texture/Texture";
import { BLEND_MODES } from "../const";
import TextureMatrix from "../texture/TextureMatrix";
import CanvasRenderer from "../renderers/CanvasRenderer";
import { WebglRenderer } from "../renderers/WebglRenderer";
import { DisplayObject } from "../display";

//两个用于计算hitTest
const tempPoint = new Point();
const tempPolygon = new Polygon();

/**
 * Mesh网格类，其实最优的是仿pixiV5和threejs用geometry和material
 * 简便起见，顶点，索引，uv都干一起吧
 * 用batch渲染
 * canvas模式下，暂时只有九宫格有效，以后再说
 */
export class Mesh extends Container {
    /**
     * 混色模式
     * @default BLEND_MODES.NORMAL
     */
    private _blendMode: BLEND_MODES = BLEND_MODES.NORMAL;
    //以后修改
    get blendMode(): BLEND_MODES {
        return this._blendMode
    }
    /**
     * 很多效果暂时无效，再查原因，先不能设置吧
     */
    set blendMode(value: BLEND_MODES) {
        // if (value != this._blendMode) this._blendMode = value
    }
    /**
     * 贴图
     * @member {Texture}
     * @default Texture.EMPTY
     * @private
     */
    private _texture: Texture;

    /**
     * 原先自身的顶点数据,到时候封装几何全进
     */
    _vertices: Float32Array;
    /**
     * 是否要更新执行_refresh
     */
    protected _needRefresh: boolean;

    /**
     * 索引
     */
    _indices: Uint16Array;
    /**
     * uv。比sprite复杂，图集和自身顶点的都要考虑
     */
    _uvs: Float32Array;
    /**
     * 要传入批的顶点数据
     */
    _vertexData: Float32Array;
    /**
     * 传入批处理的永远是上面三个，上面三个的计算需要记录dirty
     * 但是每次都需要传Gpu,性能再说吧
     * 标记自身顶点数据_vertices是否修改了
     * @member {number}
     */
    protected _vertexDirty: number;

    /**
     * 记录全局顶点_vertexData原标记Id，确保与_vertexDirty保持一致
     * 默认-1，保证算一次
     */
    private _vertexId: number;
    /**
     * 记录LocalBoundsSelf的id，确保与_vertexDirty保持一致
     * 默认-1，保证算一次
     */
    private _localBoundsSelfId: number;
    /**
     * 需要与transform._worldID的保持一致
     * 用于标记是否重新计算全局顶点数据
     */
    private _transformID: number
    /**
     * canvas模式下用，还没做
     * 用于绘制时允许重叠，否则锯齿等问题严重
     * @member {number}
     */
    canvasPadding: number;
    /**
     * 色值调色
     * 十六进制
     */
    private _tint: number;
    /**
     * RGB形式色值，webgl用
     */
    private _tintRGB: number;
    /**
     * 用于处理贴图的uv，进而计算自身的uv
     * @member {TextureMatrix}
     * @private
     */
    private _uvTransform: TextureMatrix;
    /**
     * @param {Texture} texture - 贴图
     * @param {Float32Array} [vertices] - 顶点
     * @param {Float32Array} [uvs] - uv
     * @param {Uint16Array} [indices] - 索引
     */
    constructor(texture?: Texture, vertices?: Float32Array, uvs?: Float32Array, indices?: Uint16Array) {
        super();

        /**
         * 赋值有问题，再处理
         */
        this._texture = texture || Texture.EMPTY;

        this._uvs = uvs || new Float32Array([
            0, 0,
            1, 0,
            1, 1,
            0, 1]);

        this._vertices = vertices || new Float32Array([
            0, 0,
            100, 0,
            100, 100,
            0, 100]);

        this._indices = indices || new Uint16Array([0, 1, 3, 2]);


        this._vertexDirty = 0;

        //置为-1，确保计算
        this._vertexId = -1;
        this._localBoundsSelfId = -1;

        this.blendMode = BLEND_MODES.NORMAL;

        this.canvasPadding = 0;

        this._tint = null;
        this._tintRGB = null;
        this.tint = 0xFFFFFF;

        this._uvTransform = new TextureMatrix(this._texture);

        //标记，至少更新一次
        this._needRefresh = true;
    }

    /**
     * 自身绘制方法
     * @param renderer 
     */
    _renderWebGL(renderer: WebglRenderer) {
        //刷一次
        this.refresh();
        //计算全局顶点
        this.calculateVertices();
        renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
        renderer.plugins["batch"].render(this);
    }

    /**
     * 自身canvas绘制方法，mesh插件没写，九宫格用自身方法绘制
     * @param renderer 
     */
    _renderCanvas(renderer: CanvasRenderer) {
        this.refresh();
        renderer.plugins["mesh"].render(this);
    }

    /**
     * 贴图改变时需标记更新uv
     * @private
     */
    protected _onTextureUpdate() {
        this._uvTransform.texture = this._texture;
        this._needRefresh = true;
    }

    /**
     * 重新计算uv，为了图集上的uv和自身uv
     */
    multiplyUvs() {
        this._uvTransform.multiplyUvs(this._uvs);
    }

    /**
     * mesh统一刷新方法，顶点，uv，索引更新
     * @param {boolean} [forceUpdate=false] 标记true时，必刷新一次
     */
    refresh(forceUpdate: boolean = false) {
        //贴图不可用，不更新
        if (!this._texture || !this._texture.valid) return;
        //更新贴图的uv
        if (this._uvTransform.update(forceUpdate) || this._needRefresh) {
            this._needRefresh = false;
            this._refresh();
        }
    }

    /**
     * 子类重写，用来计算 顶点，uv，索引
     * @protected
     */
    protected _refresh() {
        /* empty */
    }

    /**
     * 计算一边全局的顶点
     */
    calculateVertices() {
        //顶点没变，坐标没变，就返回
        if (this._vertexId === this._vertexDirty && this._transformID === this.transform._worldID) return;
        this._vertexId = this._vertexDirty
        this._transformID = this.transform._worldID;

        if (this._vertices.length <= 2) return
        //首次新建
        if (!this._vertexData) this._vertexData = new Float32Array(this._vertices.length)

        const wt = this.transform.worldMatrix;
        const a = wt.a;
        const b = wt.b;
        const c = wt.c;
        const d = wt.d;
        const tx = wt.tx;
        const ty = wt.ty;

        const vertexData = this._vertexData;

        for (let i = 0; i < vertexData.length / 2; i++) {
            const x = this._vertices[(i * 2)];
            const y = this._vertices[(i * 2) + 1];
            vertexData[(i * 2)] = (a * x) + (c * y) + tx;
            vertexData[(i * 2) + 1] = (b * x) + (d * y) + ty;
        }
    }


    /**
     * 用localBounds计算
     * 考虑用localBoundsSelf计算好，还是全局顶点计算好，顶点复杂的用localBoundsSelf，
     * sprite和graphics都有同样的问题，都不好缓存，以后再说
     * Mesh和graphics用的是localBoundsSelf，sprite用全局顶点计算
     */
    _calculateBounds() {
        // 考虑缓存，判断是否要执行,有两个要判断_vertexDirty（自身顶点），worldMatrix（是否改变过）
        this.updateLocalBoundsSelf();
        const rect = this._localBoundsSelf;
        var matrix = this.transform.worldMatrix;
        matrix.transformPoint(rect.x, rect.y, DisplayObject._p1);
        matrix.transformPoint(rect.x + rect.width, rect.y, DisplayObject._p2);
        matrix.transformPoint(rect.x + rect.width, rect.y + rect.height, DisplayObject._p3);
        matrix.transformPoint(rect.x, rect.y + rect.height, DisplayObject._p4);
        Rectangle.createFromPoints(this._bounds, DisplayObject._p1, DisplayObject._p2, DisplayObject._p3, DisplayObject._p4);
    }
    /**
     * 用全局顶点计算
     */
    // _calculateBounds() {
    //     this.calculateVertices();
    //     Rectangle.createFromVertexData(this._bounds, this._vertexData);
    // }


    /**
     * 更新自身包围盒
     * 通过原先的顶点数据玩，不经过transform
     */
    updateLocalBoundsSelf() {
        if (this._localBoundsSelfId == this._vertexDirty) return;
        this._localBoundsSelfId = this._vertexDirty;
        //如果小于等于1个点清空
        if (this._vertices.length <= 2) this._localBoundsSelf.clear();
        Rectangle.createFromVertexData(this._localBoundsSelf, this._vertices);
    }
    /**
     * 碰撞检测方法，和graphics类似
     * @param point 全局点
     * @param isMouseEvent 
     */
    hitTestPoint(point: Point, isMouseEvent: boolean = false): DisplayObject {
        //不可见，直接返回
        if (!this.visible) return null;
        let hitDisplayObject;
        //先检查子级，因为子级层级更高
        hitDisplayObject = super.hitTestPoint(point, isMouseEvent);
        //子级已有，返回
        if (hitDisplayObject) return hitDisplayObject;
        //子级没有的话，为了可以不updateLocalBoundsSelf，判断一下
        if (isMouseEvent && !this.mouseEnable) return null;
        //再粗略检查自己，先计算自身盒子
        this.updateLocalBoundsSelf();
        //简单检测
        hitDisplayObject = this.displayObjectHitTestPoint(point, isMouseEvent);
        //如果在LocalBoundsSelf内，再继续查点组成的
        if (hitDisplayObject) return this.hitTestPointAccuratly(point, isMouseEvent)
        return null
    }
    /**
     * 用几何方法精确检测
     * @param point 
     * @param isMouseEvent 
     */
    private hitTestPointAccuratly(point: Point, isMouseEvent): DisplayObject {
        if (isMouseEvent) {
            this.globalToLocal(point, tempPoint);
        } else {
            tempPoint.set(point.x, point.y);
        }
        const vertices = this._vertices;
        const points = tempPolygon.points;
        const indices = this._indices;
        const len = this._indices.length;
        for (let i = 0; i + 2 < len; i += 3) {
            const ind0 = indices[i] * 2;
            const ind1 = indices[i + 1] * 2;
            const ind2 = indices[i + 2] * 2;

            points[0] = vertices[ind0];
            points[1] = vertices[ind0 + 1];
            points[2] = vertices[ind1];
            points[3] = vertices[ind1 + 1];
            points[4] = vertices[ind2];
            points[5] = vertices[ind2 + 1];

            if (tempPolygon.isPointIn(tempPoint)) return this;

        }
        return null
    }


    get texture():Texture {
        return this._texture;
    }

    set texture(value:Texture) {
        if (this._texture === value) return;
        this._texture = value;
        if (value) {
            // wait for the texture to load
            if (value.baseTexture.hasLoaded) {
                this._onTextureUpdate();
            }
            else {
                value.once('update', this._onTextureUpdate, this);
            }
        }
    }

    get tint() {
        return this._tint;
    }
    set tint(value: number) {
        if (value === this._tint) return;
        this._tint = value;
        this._tintRGB = (value >> 16) + (value & 0xff00) + ((value & 0xff) << 16);
    }

    /**
     * 销毁方法
     */
    destroy() {
        super.destroy();
        //相应texture移除监听
        this._texture.removeEventListener('update', this._onTextureUpdate, this);

        this._texture =null;

        //TextureMatrix居然没写过销毁方法，再说
        this._uvTransform = null;

        this._uvs = null;
        this._vertices = null;
        this._indices = null;
        this._vertexData = null
    }
}

