import { Point, ObservablePoint, Rectangle } from '../math';
import { sign, TextureCache, hex2rgb } from '../utils';
import { BLEND_MODES } from '../const';
import Texture from '../texture/Texture';
import Container from './Container';

import { DisplayObject } from "./DisplayObject";
import CanvasRenderer from '../renderers/CanvasRenderer';
import { SCALE_MODES } from '../const';
import { WebglRenderer } from '../renderers/WebglRenderer';

const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
/**
 * Sprite类，显示图片和容器功能
 * @class
 * @extends Container
 */
export default class Sprite extends Container {
    /**
     * 混色模式
     * @default BLEND_MODES.NORMAL
     */
    _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
    }
    /**
     * 关于贴图的锚点，0到1，默认为texture自己的
     * 
     */
    private _anchorTexture: ObservablePoint
    /**
     * 色值调色
     */
    private _tint: number;
    /**
     * RGB形式色值，webgl用
     */
    _tintRGB: number;
    /**
     * 和_tint比较用，用于canvas调色缓存
     */
    _cachedTint: number;
    /**
     * 使用的贴图
     * @member {Texture}
     */
    _texture: Texture;
    /**
     * 初始化为texture的宽度
     */
    _width: number;
    /**
     * 初始化为texture的宽度
     */
    _height: number;
    /**
     * 标记更新transform
     */
    _transformID: number;
    /**
     * 标记更新过texture
     */
    _textureID: number;
    _transformTrimmedID: number;
    _textureTrimmedID: number;

    /**
     * 指定渲染用的渲染器插件
     */
    pluginName: string;

    /**
     * 顶点索引，下面几个均为webgl用到
     */
    _indices: Uint16Array;
    // size: number;
    // start: number;
    _uvs: Float32Array;
    /**
     * 顶点数据，长度8
     */
    _vertexData: Float32Array;
    /**
     * Trimmed的顶点数据，用于裁剪掉过透明像素的texture
     */
    _vertexTrimmedData: Float32Array;

    /**
     * @param {Texture} texture
     */
    constructor(texture?: Texture) {
        super();
        this._instanceType = "Sprite";
        this._anchorTexture = new ObservablePoint(
            this._onAnchorUpdate,
            this,
            (texture ? texture.defaultAnchor.x : 0),
            (texture ? texture.defaultAnchor.y : 0)
        );

        this._texture = null;
        this._width = 0;
        this._height = 0;

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


        this._uvs = null;

        // call texture setter
        this.texture = texture || Texture.EMPTY;

        this._vertexData = new Float32Array(8);

        this._vertexTrimmedData = null;

        this._transformID = -1;
        this._textureID = -1;

        this._transformTrimmedID = -1;
        this._textureTrimmedID = -1;

        // 批处理设置
        this._indices = indices;
        // this.size = 4;
        // this.start = 0;

        this.pluginName = 'sprite';
    }

    /**
     * texture更新时触发
     * 
     * @private
     */
    protected _onTextureUpdate() {
        //保证顶点要更新
        this._textureID = -1;
        this._textureTrimmedID = -1;
        this._cachedTint = 0xFFFFFF;
        //可用才赋值uv
        if (this._texture.valid) this._uvs = this._texture._uvs.uvsFloat32;
        //设置过宽高的话，就需要改变缩放值
        // so if _width is 0 then width was not set..
        if (this._width) {
            this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width;
        }

        if (this._height) {
            this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height;
        }

        //修改_localBoundsSelf
        const width = this._texture.orig.width;
        const height = this._texture.orig.height;
        this._localBoundsSelf.x = -width * this.anchorTexture.x;
        this._localBoundsSelf.y = -height * this.anchorTexture.y;
        this._localBoundsSelf.width = width;
        this._localBoundsSelf.height = height;
    }

    /**
     * 当贴图锚点修改时
     * 
     * @private
     */
    _onAnchorUpdate() {
        this._transformID = -1;
        this._transformTrimmedID = -1;
    }

    /**
     * 01——23
     * |   |
     * 67——45
     * calculates worldTransform * vertices, store it in vertexData
     */
    calculateVertices() {
        if (this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) {
            return;
        }

        this._transformID = this.transform._worldID;
        this._textureID = this._texture._updateID;

        // set the vertex data
        const texture = this._texture;
        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;
        const trim = texture.trim;
        const orig = texture.orig;
        const anchor = this._anchorTexture;

        let w0 = 0;
        let w1 = 0;
        let h0 = 0;
        let h1 = 0;

        if (trim) {
            // if the sprite is trimmed and is not a tilingsprite then we need to add the extra
            // space before transforming the sprite coords.
            //绘制永远根据frame，所以有trim过的，加个偏移
            w1 = trim.x - (anchor._x * orig.width);
            w0 = w1 + trim.width;

            h1 = trim.y - (anchor._y * orig.height);
            h0 = h1 + trim.height;
        }
        else {
            w1 = -anchor._x * orig.width;
            w0 = w1 + orig.width;

            h1 = -anchor._y * orig.height;
            h0 = h1 + orig.height;
        }

        // xy
        vertexData[0] = (a * w1) + (c * h1) + tx;
        vertexData[1] = (d * h1) + (b * w1) + ty;

        // xy
        vertexData[2] = (a * w0) + (c * h1) + tx;
        vertexData[3] = (d * h1) + (b * w0) + ty;

        // xy
        vertexData[4] = (a * w0) + (c * h0) + tx;
        vertexData[5] = (d * h0) + (b * w0) + ty;

        // xy
        vertexData[6] = (a * w1) + (c * h0) + tx;
        vertexData[7] = (d * h0) + (b * w1) + ty;
    }

    /**
     * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData
     * This is used to ensure that the true width and height of a trimmed texture is respected
     */
    calculateTrimmedVertices() {
        if (!this._vertexTrimmedData) {
            this._vertexTrimmedData = new Float32Array(8);
        }
        else if (this._transformTrimmedID === this.transform._worldID && this._textureTrimmedID === this._texture._updateID) {
            return;
        }

        this._transformTrimmedID = this.transform._worldID;
        this._textureTrimmedID = this._texture._updateID;

        // lets do some special trim code!
        const texture = this._texture;
        const vertexData = this._vertexTrimmedData;
        const orig = texture.orig;
        const anchor = this._anchorTexture;

        // lets calculate the new untrimmed bounds..
        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 w1 = -anchor._x * orig.width;
        const w0 = w1 + orig.width;

        const h1 = -anchor._y * orig.height;
        const h0 = h1 + orig.height;

        // xy
        vertexData[0] = (a * w1) + (c * h1) + tx;
        vertexData[1] = (d * h1) + (b * w1) + ty;

        // xy
        vertexData[2] = (a * w0) + (c * h1) + tx;
        vertexData[3] = (d * h1) + (b * w0) + ty;

        // xy
        vertexData[4] = (a * w0) + (c * h0) + tx;
        vertexData[5] = (d * h0) + (b * w0) + ty;

        // xy
        vertexData[6] = (a * w1) + (c * h0) + tx;
        vertexData[7] = (d * h0) + (b * w1) + ty;
    }

    /**
    * 自身webgl绘制方法
    * @private
    * @param {WebglRenderer} renderer
    */
    _renderWebGL(renderer: WebglRenderer) {
        //先计算顶点
        this.calculateVertices();
        renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
        renderer.plugins["batch"].render(this);
    }

    /**
    * 自身canvas绘制方法
    * @private
    * @param {CanvasRenderer} renderer
    */
    _renderCanvas(renderer: CanvasRenderer) {
        renderer.plugins[this.pluginName].render(this);
    }

    /**
     * 更新自己的bounds，计算全局
     * @private
     */
    _calculateBounds() {
        const trim = this._texture.trim;
        const orig = this._texture.orig;
        //无trim。或者trim的尺寸和orig相等
        if (!trim || (trim.width === orig.width && trim.height === orig.height)) {
            this.calculateVertices();
            Rectangle.createFromVertexData(this._bounds, this._vertexData);
        }
        else {
            //计算trimmed bounds...
            this.calculateTrimmedVertices();
            Rectangle.createFromVertexData(this._bounds, this._vertexTrimmedData);
        }
    }

    /**
     * 重写父类
     * @param {Rectangle} rect - The output rectangle.
     * @return {Rectangle} The bounds.
     */
    getLocalBounds(rect?: Rectangle): Rectangle {
        //如果没有child，直接
        if (this.children.length === 0) {
            if (!rect) {
                rect = DisplayObject.temBounds;
            }
            //直接用_localBoundsSelf
            rect.copy(this._localBoundsSelf)
            return rect;
        }

        return super.getLocalBounds.call(this, rect);
    }


    /**
     * 重写碰撞检测方法
     * @param globalPoint 
     * @param isMouseEvent 
     */
    hitTestPoint(globalPoint: Point, isMouseEvent: boolean = false) {
        //不可见，直接返回
        if (!this.visible) return null
        //如果以后加缓存成位图，另写
        let hitDisplayObject;
        //先检查子级，因为子级层级更高
        hitDisplayObject = super.hitTestPoint(globalPoint, isMouseEvent);
        //子级已有，返回
        if (hitDisplayObject) return hitDisplayObject;
        //检查自己
        hitDisplayObject = this.displayObjectHitTestPoint(globalPoint, isMouseEvent);
        if (hitDisplayObject) return hitDisplayObject;
        return null;
    }



    /**
     * 销毁
     */
    destroy() {
        super.destroy();

        //相应texture移除监听
        this._texture.removeEventListener('update', this._onTextureUpdate, this);

        this._anchorTexture = null;
        this._texture = null;
        this._indices = null;
        this._uvs = null;
        this._vertexData = null;
        this._vertexTrimmedData = null;

        //对于canvas可能有调色缓存
        if (this["tintedTexture"]) this["tintedTexture"] = null;
    }

    /**
     * 重写Container父类
     * texture的宽度和缩放乘积
     * @member {number}
     */
    get width() {
        return Math.abs(this.scale.x) * this._texture.orig.width;
    }

    set width(value) {
        const s = sign(this.scale.x) || 1;

        this.scale.x = s * value / this._texture.orig.width;
        this._width = value;
    }

    /**
     * texture的高度和缩放乘积
     * @member {number}
     */
    get height() {
        return Math.abs(this.scale.y) * this._texture.orig.height;
    }

    set height(value) {
        const s = sign(this.scale.y) || 1;
        this.scale.y = s * value / this._texture.orig.height;
        this._height = value;
    }

    /**
     * 0，0标识左上角，0.5，0.5表示中间，1，1表示右下角
     * @member {ObservablePoint}
     */
    get anchorTexture(): ObservablePoint {
        return this._anchorTexture;
    }

    set anchorTexture(value: ObservablePoint) {
        this._anchorTexture.copy(value);
    }


    /**
     * @member {Texture}
     */
    get texture() {
        return this._texture;
    }

    set texture(value) {
        if (this._texture === value) {
            return;
        }
        this._texture = value || Texture.EMPTY;

        this._textureID = -1;
        this._textureTrimmedID = -1;
        this._cachedTint = 0xFFFFFF;

        if (value) {
            if (value.baseTexture.hasLoaded) {
                this._onTextureUpdate();
            }
            else {
                value.once('update', this._onTextureUpdate, this);
            }
        }
    }

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

    //一些静态类方法
    /**
     * 方便创建sprite
     * The source can be - frame id, image url, canvas element, base texture
     * @static
     * @param {string|BaseTexture|HTMLCanvasElement|BaseTexture|Texture} source
     * @return {Sprite} The newly created sprite
     */
    static from(source: any): Sprite {
        return new Sprite(Texture.from(source));
    }

    /**
     * TextureCache缓存里取得frameId，通常图集里得名字
     * @static
     * @param {string} frameId - The frame Id of the texture in the cache
     * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId
     */
    static fromFrame(frameId: string): Sprite {
        const texture = TextureCache[frameId];
        if (!texture) {
            throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
        }
        return new Sprite(texture);
    }

    /**
     * 根据图片标签创建sprite
     * @param image 
     * @param scaleMode 
     */
    static fromImage(image: HTMLImageElement, scaleMode: number = SCALE_MODES.LINEAR) {
        return new Sprite(Texture.fromImage(image, scaleMode));
    }

    /**
     * 根据图片路径创建sprite
     * @static
     * @param {string} imageUrl - The image url of the texture
     * @param {boolean} [crossorigin=(auto)] - if you want to specify the cross-origin parameter
     * @param {number} [scaleMode=settings.SCALE_MODE] - if you want to specify the scale mode,
     *  see {@link SCALE_MODES} for possible values
     * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id
     */
    static fromUrl(imageUrl: string, crossorigin: boolean = true, scaleMode: number = SCALE_MODES.LINEAR): Sprite {
        return new Sprite(Texture.fromUrl(imageUrl, crossorigin, scaleMode));
    }
}
