import { EventDispatcher } from '../events/EventDispatcher';
import { decomposeDataUri, getUrlFileExtension, isPow2, BaseTextureCache, TextureCache, uid } from '../utils';
import { SCALE_MODES, WRAP_MODES } from "../const"

/**
 * 每个texture都有一个BaseTexture，多数用于图集，texture可自身设置属性
 * 实际绘制用的都是BaseTexture
 * @class
 * @extends EventDispatcher
 */
export default class BaseTexture extends EventDispatcher {
    /**
     * 贴图回收时用到，
     * 标记是否被使用过
     */
    _touchedId: number;
    /**
     * 批处理时用到的标志位
     * 被使用着的id
     */
    _enabledId: number;
    /**
     * 宽度
     */
    width: number;
    /**
     * 高度
     */
    height: number;
    /**
     * SCALE_MODES，一般是线性
     * 用于glTexture
     */
    scaleMode: number;
    /**
     * 加载完成会设置为true
     * 加载失败或没有贴图数据则为false，
     * 通常用于标记基础纹理是否可用
     */
    hasLoaded: boolean;
    /**
     * 正在加载
     */
    private _isLoading: boolean;
    /**
     * image类型 eg. `png`
     * 暂时不考虑svg
     * @readonly
     */
    imageType: string;
    /**
     * rgb预乘alpha，webgl用到，png图片设置必为true，否则色值会出问题
     * @default true
     */
    premultipliedAlpha: boolean;
    /**
     * 图片路径
     * @member {string}
     */
    imageUrl: string;
    /**
     * 是否尺寸为2的次方，尽可能图集尺寸都为2的次方，gpu处理方便，并且能做mipmap缓存，性能更好
     */
    isPowerOfTwo: boolean;
    /**
     * 尺寸是2的次方下才能设置true，用于生成mipmap缓存
     * @default true
     */
    mipmap: boolean;
    /**
     * 非2的次方时要设置CLAMP
     * WebGL Texture wrap mode
     * @default WRAP_MODES.CLAMP
     */
    wrapMode: WRAP_MODES;
    /**
     * A map of renderer IDs to webgl textures
     * 不同渲染器对应的记录，暂时应该只需要一个
     * @member {object<number, WebGLTexture>}
     */
    _glTextures: {};
    /**
     * The ids under which this BaseTexture has been added to the base texture cache. This is
     * automatically set as long as BaseTexture.addToCache is used, but may not be set if a
     * BaseTexture is added directly to the BaseTextureCache array.
     * @member {string[]}
     */
    textureCacheIds: string[];

    /**
     * 实际元素，考虑是否set方法调用_loadSource();
     * 图片标签，canvas画布
     * @readonly
     * @member {HTMLImageElement|HTMLCanvasElement}
     */
    source: HTMLImageElement | HTMLCanvasElement;


    /**
     * @param {HTMLImageElement|HTMLCanvasElement} [source] - 源数据
     * @param {number} [scaleMode=settings.SCALE_MODE] - possible values
     */
    constructor(source?: HTMLImageElement | HTMLCanvasElement, scaleMode: number = SCALE_MODES.LINEAR) {
        super();
        this._touchedId = 0;
        this.width = 100;
        this.height = 100;
        this.scaleMode = scaleMode;
        this.hasLoaded = false;
        this._isLoading = false;
        this.source = null;
        this.imageType = null;
        this.premultipliedAlpha = true;
        this.imageUrl = null;
        this.isPowerOfTwo = false;
        this.mipmap = true;
        this.wrapMode = WRAP_MODES.CLAMP;
        this._glTextures = {};
        this._enabledId = 0;
        this.textureCacheIds = [];
        //有source要加载
        if (source) this._loadSource(source);
    }

    /**
     * 会触发更新事件
     * @fires BaseTexture#update
     */
    update() {
        //尺寸重置
        this.width = this.source.width;
        this.height = this.source.height;
        //再来判断一次
        this.hasLoaded = this.width && this.height ? true : false;
        //判断是否是2的n次方
        this.isPowerOfTwo = isPow2(this.width) && isPow2(this.height);

        //触发绑定过的更新事件，比如webgl纹理更新
        this.dispatchEvent("update")
    }

    /**
     * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture.
     */
    private _loadSource(source: any) {
        //根据_isLoading预先标记加载状态
        const wasLoading = this._isLoading;

        this.hasLoaded = false;
        this._isLoading = false;

        //如果是正在加载原先的source，置空onload和onerror
        if (wasLoading && this.source) {
            this.source.onload = null;
            this.source.onerror = null;
        }

        //原先没有source，则是首次加载
        const firstSourceLoaded = !this.source;

        //赋值
        this.source = source;

        //已加载，并且宽高都存在
        if (((source.src && source.complete) || source.getContext) && source.width && source.height) {
            this._updateImageType();
            this._sourceLoaded();
            if (firstSourceLoaded) {
                //如果原先没有source图片，并且现在传入的图片已经加载好，要触发loaded事件
                this.dispatchEvent("loaded");
            }
        }
        //不是canvas，加载图片
        else if (!source.getContext) {
            // Image fail / not ready
            this._isLoading = true;
            const scope = this;
            source.onload = () => {
                scope._updateImageType();
                source.onload = null;
                source.onerror = null;
                if (!scope._isLoading) return;
                scope._isLoading = false;
                scope._sourceLoaded();
                scope.dispatchEvent("loaded")
            };
            source.onerror = () => {
                source.onload = null;
                source.onerror = null;
                if (!scope._isLoading) return;
                scope._isLoading = false;
                scope.dispatchEvent("error")
            };
            //以防万一，再次检查complete，解释如下
            // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element
            //   "The value of `complete` can thus change while a script is executing."
            // So complete needs to be re-checked after the callbacks have been added..
            // NOTE: complete will be true if the image has no src so best to check if the src is set.
            if (source.complete && source.src) {
                // 如果已经完成，不需要回调，置空onload和onerror
                source.onload = null;
                source.onerror = null;
                this._isLoading = false;
                if (source.width && source.height) {
                    this._sourceLoaded();
                    //如果之前是正在加载状态，触发loaded事件
                    if (wasLoading) this.dispatchEvent("loaded")
                }
                //如果之前是正在加载状态，触发error事件
                else if (wasLoading) {
                    this.dispatchEvent("error")
                }
            }
        }
    }

    /**
     * 更新图片类型
     */
    private _updateImageType() {
        if (!this.imageUrl) return;
        const dataUri = decomposeDataUri(this.imageUrl);
        let imageType;
        if (dataUri && dataUri.mediaType === 'image') {
            // Check for subType validity
            const firstSubType = dataUri.subType.split('+')[0];
            imageType = getUrlFileExtension(`.${firstSubType}`);
            if (!imageType) throw new Error('Invalid image type in data URI.');
        }
        else {
            imageType = getUrlFileExtension(this.imageUrl);
            if (!imageType) imageType = 'png';
        }
        this.imageType = imageType;
    }
    /**
     * 加载完成后执行
     * @private
     */
    private _sourceLoaded() {
        this.hasLoaded = true;
        this.update();
    }

    /**
     * 销毁 base texture
     */
    destroy() {
        if (this.imageUrl) {
            delete TextureCache[this.imageUrl];
            this.imageUrl = null;
        }
        this.source = null;
        this.dispose();
        BaseTexture.removeFromCache(this);
        this.textureCacheIds = null;
    }

    /**
     * 用于释放gpu缓存，并不销毁纹理，需要时可再上传到GPU
     * @fires BaseTexture#dispose
     */
    dispose() {
        //用于触发TextureManager中监听的
        this.dispatchEvent("dispose")
    }

    /**
     * 修改source路径
     * 原先source必须也是imageElement
     * 貌似基本不用，新建比较好
     * @param {string} newSrc - 图片路径
     */
    updateSourceImage(newSrc: string) {
        this.source["src"] = newSrc;
        this._loadSource(this.source);
    }

    //辅助静态方法
    /**
     * 通过image标签
     * 会缓存非base64的图片
     * @param image 
     * @param scaleMode 
     */
    static fromImage(image: HTMLImageElement, scaleMode: SCALE_MODES = SCALE_MODES.LINEAR) {
        //图片标签
        const imageUrl = image.src;
        let baseTexture = BaseTextureCache[imageUrl];
        if (!baseTexture) {
            baseTexture = new BaseTexture(image, scaleMode);
            baseTexture.imageUrl = imageUrl;
            //不缓存base64，如需要，设名字，手动缓存
            if (imageUrl.indexOf('data:') !== 0) BaseTexture.addToCache(baseTexture, imageUrl);
        }
        return baseTexture;
    }

    /**
     * 根据图片路径创建BaseTexture
     * 会缓存非base64的图片路径
     * @static
     * @param {string} imageUrl 图片路径
     * @param {boolean} [crossorigin=(auto)] -是否跨域，也可传string
     * @param {number} [scaleMode] 
     * @return {BaseTexture} The new base texture.
     */
    static fromUrl(imageUrl: string, crossorigin?: any, scaleMode: SCALE_MODES = SCALE_MODES.LINEAR): BaseTexture {
        let baseTexture = BaseTextureCache[imageUrl];
        if (!baseTexture) {
            const image = new Image();
            //如果没传crossorigin并且imageUrl不是base64，自动设置crossOrigin anonymous
            if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) {
                image.crossOrigin = "anonymous"
            }
            //也得保证不是base64
            else if (crossorigin && imageUrl.indexOf('data:') !== 0) {
                image.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous';
            }

            baseTexture = new BaseTexture(image, scaleMode);
            baseTexture.imageUrl = imageUrl;
            image.src = imageUrl;
            //加入缓存，不缓存base64，如需要，设名字，手动缓存
            if (imageUrl.indexOf('data:') !== 0) BaseTexture.addToCache(baseTexture, imageUrl);
        }

        return baseTexture;
    }

    /**
     * 根据canvas创建BaseTexture
     * baseTexture会缓存
     * @static
     * @param {HTMLCanvasElement} canvas - canvas标签
     * @param {number} scaleMode - See {@link SCALE_MODES} for possible values
     * @param {string} [origin='canvas'] - 类型标志位，用于生成缓存id
     * @return {BaseTexture} The new base texture.
     */
    static fromCanvas(canvas: HTMLCanvasElement, scaleMode: number = SCALE_MODES.LINEAR, origin: string = 'canvas'): BaseTexture {
        //标记canvasId
        if (!canvas["_canvasId"]) {
            canvas["_canvasId"] = `${origin}_${uid()}`;
        }
        let baseTexture = BaseTextureCache[canvas["_canvasId"]];
        if (!baseTexture) {
            baseTexture = new BaseTexture(canvas, scaleMode);
            BaseTexture.addToCache(baseTexture, canvas["_canvasId"]);
        }
        return baseTexture;
    }

    /**
     * @static
     * @param {string|HTMLImageElement|HTMLCanvasElement|BaseTexture} source 可能值
     * @param {number} [scaleMode=settings.SCALE_MODE] 
     * @return {BaseTexture} 
     */
    static from(source: any, scaleMode: number = SCALE_MODES.LINEAR): BaseTexture {
        if (typeof source === 'string') {
            //图片路径
            return BaseTexture.fromUrl(source, undefined, scaleMode);
        }
        else if (source instanceof HTMLImageElement) {
            return BaseTexture.fromImage(source, scaleMode)
        }
        else if (source instanceof HTMLCanvasElement) {
            //canvas标签
            return BaseTexture.fromCanvas(source, scaleMode);
        }
        //如果source本身就是BaseTexture
        return source;
    }

    /**
     * 加入缓存
     * @static
     * @param {BaseTexture} baseTexture
     * @param {string} id
     */
    static addToCache(baseTexture: BaseTexture, id: string) {
        if (id) {
            if (baseTexture.textureCacheIds.indexOf(id) === -1) {
                baseTexture.textureCacheIds.push(id);
            }

            if (BaseTextureCache[id]) {
                //覆盖
                console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`);
            }
            BaseTextureCache[id] = baseTexture;
        }
    }

    /**
     * 移除缓存
     * @static
     * @param {string|BaseTexture} baseTexture id或者BaseTexture
     * @return {BaseTexture|null} 移除的BaseTexture或null
     */
    static removeFromCache(baseTexture: string | BaseTexture): BaseTexture | null {
        if (typeof baseTexture === 'string') {
            const baseTextureFromCache = BaseTextureCache[baseTexture];
            if (baseTextureFromCache) {
                const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture);
                if (index > -1) {
                    baseTextureFromCache.textureCacheIds.splice(index, 1);
                }
                delete BaseTextureCache[baseTexture];
                return baseTextureFromCache;
            }
        }
        else if (baseTexture && baseTexture.textureCacheIds) {
            for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) {
                delete BaseTextureCache[baseTexture.textureCacheIds[i]];
            }
            baseTexture.textureCacheIds.length = 0;
            return baseTexture;
        }
        return null;
    }
}
