import Container from "./Container";
import { RENDERER_TYPE, VERSION, osType } from "../const"
// import SystemRenderer from "../renderers/SystemRenderer";
import { Rectangle, Point } from "../math";
import { EventDispatcher } from "../events/EventDispatcher";
import { Event } from "../events/Event";
// import { FloatDisplay } from "./FloatDisplay";
import { DisplayObject } from "./DisplayObject";
import { MouseEvent } from "../events/MouseEvent";

import { WebglRenderer } from "../renderers/WebglRenderer";

import { CanvasRenderer } from "../renderers/CanvasRenderer";
import { SystemRenderer } from "../renderers/SystemRenderer";
import { getEnv, isWebGLSupported } from "../utils";

export class Stage extends Container {

    /**
     * 当前stage所使用的渲染器
     * 渲染器有两种,一种是canvas 一种是webGl
     * @property renderObj
     * @public
     * @since 1.0.0
     * @type {IRender}
     * @default null
     */
    public renderObj: SystemRenderer = null;


    /**
     * 直接获取stage的引用，避免总是从Event.ADD_TO_STAGE 事件中去获取stage引用
     * @property getStage
     * @param {string} stageName
     * @return {any}
     * @since 2.0.0
     */
    public static getStage(stageName: string = "cusEngine"): Stage {
        return Stage._stageList[stageName];
    }

    /**
     * @property _stageList
     * @static
     * @type {Object}
     * @private
     */
    private static _stageList: any = {};

    /**
     * 舞台在设备里截取后的可见区域,有些时候知道可见区域是非常重要的,因为这样你就可以根据舞台的可见区域做自适应了。
     * @property viewRect
     * @public
     * @readonly
     * @since 1.0.0
     * @type {Rectangle}
     * @default {x:0,y:0,width:0,height:0}
     * @readonly
     */
    public viewRect: Rectangle = new Rectangle();

    /**
     * 舞台的尺寸宽,也就是我们常说的设计尺寸
     * @property desWidth
     * @public
     * @since 1.0.0
     * @default 320
     * @type {number}
     * @readonly
     */
    public desWidth: number = 0;
    /**
     * 舞台的尺寸高,也就是我们常说的设计尺寸
     * @property desHeight
     * @public
     * @since 1.0.0
     * @default 240
     * @type {number}
     * @readonly
     */
    public desHeight: number = 0;
    /**
     * 舞台在当前设备中的真实高
     * @property divHeight
     * @public
     * @since 1.0.0
     * @default 320
     * @type {number}
     * @readonly
     */
    public divHeight: number = 0;
    /**
     * 舞台在当前设备中的真实宽
     * @property divWidth
     * @public
     * @since 1.0.0
     * @default 240
     * @readonly
     * @type {number}
     */
    public divWidth: number = 0;
    /**
     * 舞台的背景色
     * 默认就是透明背景
     * 可能设置一个颜色值改变舞台背景
     * @property bgColor
     * @public
     * @since 1.0.0
     * @type {number}
     * @default "";
     */
    private _bgColor: number = 0x000000;

    public get bgColor(): number {
        return this._bgColor;
    }
    /**
     * 设置颜色，即改变渲染器颜色
     */
    public set bgColor(value: number) {
        if (this._bgColor === value) return
        this._bgColor = value;
        this.renderObj.backgroundColor = value;
    }

    /**
     * 上一次鼠标或触碰经过的显示对象列表
     * @property _lastDpList
     * @type {Object}
     * @private
     */
    private _lastDpList: any = {};

    private _dpi: number;

    /**
     * 
     */
    private canvas: HTMLCanvasElement;

    /**
     * 淘宝小程序环境canvas的偏移,淘宝环境才用,web环境实时,字段保留,但是不会计算了
     */
    private canvasOffsetTb: { x: number, y: number } = { x: 0, y: 0 };
    private offsetTimeId: any

    get stageWidth() {
        return this.viewRect.width;
    }

    get stageHeight() {
        return this.viewRect.height;
    }
    /**
     * 缩放模式
     */
    private _scaleMode: "fixedWidth" | "fixedHeight";
    /**
     * 舞台是否居中
     */
    private _stageCenter: boolean;
    /**
     * canvas的实际宽高及显示宽高外部自己设定,stage内部不对尺寸做任何修改
     * 且需要根据实际尺寸和显示尺寸确定dpi
     * @param canvas canvas标签
     * @param desWidth 舞台设计宽
     * @param desHeight 舞台设计高
     * @param divWidth canvas显示宽
     * @param divHeight canvas显示高
     * @param renderType 渲染类型,默认canvas
     * @param stageCenter 舞台是否根据设计尺寸居中,默认false(左上角置顶)
     * @param fixedHeight 是否定高,默认false(定宽)
     */
    public constructor(
        canvas: HTMLCanvasElement,
        desWidth: number = 750,
        desHeight: number = 1624,
        divWidth: number,
        divHeight: number,
        renderType: RENDERER_TYPE = RENDERER_TYPE.CANVAS,
        stageCenter: boolean = false,
        fixedHeight: boolean = false
    ) {
        super();
        this.canvas = canvas;
        let s: Stage = this;
        this._instanceType = "Stage";
        Stage._stageList["canvas"] = s;
        s.stage = this;
        s.name = "stageInstance_" + s.instanceId;
        s.desWidth = desWidth;
        s.desHeight = desHeight;
        s.divWidth = divWidth;
        s.divHeight = divHeight;

        // console.log("%c ", "background: url(http://5b0988e595225.cdn.sohucs.com/images/20180315/d41842ad9b5443d3854a480ea49f3b09.gif) no-repeat center;padding-left:80px;padding-bottom: 80px;border-radius:50%;")
        //打印个版本号
        console.log(
            "%cfyge版本：" + VERSION,
            "text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);font-size:3em"
        )
        //webgl不支持时的兼容，回退canvas
        if (renderType == RENDERER_TYPE.WEBGL && !isWebGLSupported()) {
            renderType = RENDERER_TYPE.CANVAS;
            console.warn("不支持webgl")
        }
        if (renderType == RENDERER_TYPE.CANVAS) {
            let context = canvas.getContext("2d");
            s.renderObj = new CanvasRenderer(context, canvas.width, canvas.height);
        } else {
            let contextOptions: WebGLContextAttributes = {
                alpha: true,
                antialias: false,//抗锯齿尽量别开，耗性能
                premultipliedAlpha: true,  //一般png图片都不会预乘alpha，所以必为true,除非有些图集工具选择了premultipliedAlpha
                stencil: true,
                // preserveDrawingBuffer: this.options.preserveDrawingBuffer,//需要toDataUrl时要开
                // powerPreference: this.options.powerPreference,
            };
            let context = canvas.getContext("webgl", contextOptions) || canvas.getContext("experimental-webgl", contextOptions) as WebGLRenderingContext;
            s.renderObj = new WebglRenderer(context, canvas.width, canvas.height);
            //监听下上下文丢失的情况
            if (getEnv() == "web") {
                canvas.addEventListener(
                    'webglcontextlost',
                    (s.renderObj as WebglRenderer).handleContextLost.bind(s.renderObj),
                    false
                );
                canvas.addEventListener(
                    'webglcontextrestored',
                    (s.renderObj as WebglRenderer).handleContextRestored.bind(s.renderObj),
                    false
                );
            }

        }
        //TODO,还有一种方式,传入分辨率和显示宽高,canvas尺寸在里面修改,这样要多传个参数,暂时不改传参,不然版本问题,
        //到时修改这个放到上面,因为渲染器也用到了canvas的尺寸,
        //注意淘宝小程序环境必须先设置canvas的尺寸再getContext,类似CanvasRenderTarget构造函数里的问题
        //分辨率,
        s._dpi = canvas.width / divWidth;
        s._stageCenter = stageCenter;
        s._scaleMode = fixedHeight ? "fixedHeight" : "fixedWidth";
        //设置缩放及视窗
        s._setAlign();

        //延时派发舞台初始化事件
        setTimeout(function () {
            //派发事件
            s.dispatchEvent(Event.INIT_STAGE);
        }, 100);

        //淘宝环境额外处理canvas偏移,到时需要修改id传入，再说把，不需要了，淘宝上返回的就是canvas上的显示坐标
        // if (getEnv() == "tb") {//暂时先去掉吧，会出现一些问题，有需要，手动设置canvasOffsetTb的xy，屏幕坐标
        //     s.offsetTimeId = setInterval(() => {
        //         //@ts-ignore
        //         my.createSelectorQuery().select('#canvas').boundingClientRect().exec((r) => {
        //             if (r && r[0]) {
        //                 s.canvasOffsetTb.x = r[0].left || 0;
        //                 s.canvasOffsetTb.y = r[0].top || 0;
        //             }
        //         });
        //     }, 200)
        // }
    }
    /**
     * 暂时不存在外部调用的情况
     */
    private _setAlign() {
        let s = this;
        let canvas = s.canvas;
        //舞台设置居中处理锚点和位置偏移
        if (s._stageCenter) {
            s.anchorX = s.desWidth >> 1;
            s.anchorY = s.desHeight >> 1;
            s.x = (canvas.width - s.desWidth) >> 1;
            s.y = (canvas.height - s.desHeight) >> 1;
        }
        //默认定宽
        var scale = s._scaleMode == "fixedHeight" ? canvas.height / s.desHeight : canvas.width / s.desWidth;
        s.scaleX = scale;
        s.scaleY = scale;
        //视窗计算
        s.viewRect = s.viewRect || new Rectangle();
        s.viewRect.x = (s.desWidth - canvas.width / scale) >> 1;
        s.viewRect.y = (s.desHeight - canvas.height / scale) >> 1;
        s.viewRect.width = s.desWidth - s.viewRect.x * 2;
        s.viewRect.height = s.desHeight - s.viewRect.y * 2;

        //额外逻辑,不设置舞台居中时,x,y为左上角,置0
        if (!s._stageCenter) s.viewRect.x = s.viewRect.y = 0;
    }
    /**
     * 移动端不常用
     * 微信浏览器使用情景:ios返回页面下面出现操作栏
     * window.addEventListener('resize', () => {stage.resize()});
     * 这里会按照原_dpi修改canvas的实际尺寸
     * 一般设备的dpi不会改变,
     * web全屏环境可不传参数,否则自行计算显示尺寸传入
     * @param divWidth 
     * @param divHeight 
     */
    public resize(divWidth?: number, divHeight?: number) {
        if (getEnv() == "web") {//web环境不传当作全屏处理
            divWidth = divWidth || document.body.clientWidth;
            divHeight = divHeight || document.body.clientHeight;
        }
        if (!divWidth || !divWidth) {
            console.error("传入的显示尺寸不能为空")
            return
        }
        let s = this, d = s._dpi, c = s.canvas;
        //如果一致不修改
        if (divWidth == s.divWidth && divHeight == s.divHeight) return;
        //这里修改canvas尺寸
        if (divWidth != s.divWidth) c.width = divWidth * d;
        if (divHeight != s.divHeight) c.height = divHeight * d;
        //赋值显示宽高
        s.divWidth = divWidth;
        s.divHeight = divHeight;
        //修改缩放显示及视窗
        s._setAlign();
        //渲染器修改尺寸
        s.renderObj.resize(c.width, c.height);
        //派发事件
        s.dispatchEvent(Event.RESIZE);
    }

    /**
     * 这个是鼠标事件的MouseEvent对象池,因为如果用户有监听鼠标事件,如果不建立对象池,那每一秒将会new Fps个数的事件对象,影响性能
     * @property _ml
     * @type {Array}
     * @private
     */
    private _ml: any = [];
    /**
     * 这个是事件中用到的Point对象池,以提高性能
     * @property _mp
     * @type {Array}
     * @private
     */
    private _mp: any = [];

    /**
     * 刷新mouse或者touch事件
     * @method _initMouseEvent
     * @private
     */
    private _initMouseEvent(event: MouseEvent, cp: Point, sp: Point, identifier: number): void {
        event["_pd"] = false;
        event.clientX = cp.x;
        event.clientY = cp.y;
        event.stageX = sp.x;
        event.stageY = sp.y;
        event.identifier = identifier;
    }

    //每一个手指事件的对象池
    /**
     * @property _mouseDownPoint
     * @type {Object}
     * @private
     */
    private _mouseDownPoint: any = {};

    /**
     * 循环刷新页面的函数
     * @method flush
     * @private
     * @return {void}
     */
    public flush(): void {
        this.renderObj.render(this);
    }

    /**
     * html的鼠标或单点触摸对应的引擎事件类型名
     * touchcancel:"onMouseUp"不常用，先不加
     * @property _mouseEventTypes
     * @type {{mousedown: string, mouseup: string, mousemove: string, touchstart: string, touchmove: string, touchend: string}}
     * @private
     */
    private _mouseEventTypes: any = {
        //pc
        mousedown: "onMouseDown",
        mousemove: "onMouseMove",
        mouseup: "onMouseUp",
        //mobile
        touchstart: "onMouseDown",
        touchmove: "onMouseMove",
        touchend: "onMouseUp",
        //tbMini
        touchStart: "onMouseDown",//小程序返回的时间名是驼峰的
        touchMove: "onMouseMove",
        touchEnd: "onMouseUp"
    };

    /**
     * 无多指，无拖动
     * @method onMouseEvent
     * @param e
     * @private
     */
    onMouseEvent(e: any): void {
        //@ts-ignore
        if (getEnv() == "web") e.preventDefault();
        let s: Stage = this;
        //检查mouse或touch事件是否有，如果有的话，就触发事件函数
        if (EventDispatcher._totalMEC > 0) {
            let points: any;
            //事件类型

            let item = s._mouseEventTypes[e.type];
            let events: any;
            let event: any;
            //stageMousePoint
            let sp: Point;
            //localPoint;
            let lp: Point;
            //clientPoint
            let cp: Point;
            //事件个数
            let eLen: number;
            let identifier: any;
            if (osType == "pc") {
                e.identifier = 0;
                points = [e];
            } else {
                points = [e.changedTouches[0]];
            }
            // points = [e.changedTouches[0]];
            //  points = [e.touches[0]];//不能用这个
            let { x: offsetX, y: offsetY } = s.canvasOffsetTb;
            //@ts-ignore
            if (getEnv() == "web") {
                let doc = document.documentElement;
                let box = s.canvas.getBoundingClientRect();
                offsetX = box.left + window.pageXOffset - doc.clientLeft;
                offsetY = box.top + window.pageYOffset - doc.clientTop;
            }


            for (let o = 0; o < points.length; o++) {
                if (!points[o]) continue;
                eLen = 0;
                events = [];
                identifier = "m" + points[o].identifier;
                if (s._mp.length > 0) {
                    cp = s._mp.shift();
                } else {
                    cp = new Point();
                }
                cp.x = ((points[o].pageX || points[o].x || points[o].b) - offsetX) * s._dpi// devicePixelRatio;
                cp.y = ((points[o].pageY || points[o].y || points[o].c) - offsetY) * s._dpi// devicePixelRatio;
                // my.alert({
                //   title: JSON.stringify(points[o])
                // });
                //计算舞台中的点
                sp = s.globalToLocal(cp, DisplayObject._bp);
                //检查是否有鼠标事件
                if (EventDispatcher.getMouseEventCount() > 0) {
                    if (!s._ml[eLen]) {
                        event = new MouseEvent(item);
                        s._ml[eLen] = event;
                    } else {
                        event = s._ml[eLen];
                        event.type = item;
                    }
                    events[events.length] = event;
                    s._initMouseEvent(event, cp, sp, identifier);
                    eLen++;
                }
                if (item == "onMouseDown") {
                    s._mouseDownPoint[identifier] = cp;
                    //清空上次存在的显示列表
                } else if (item == "onMouseUp") {
                    if (s._mouseDownPoint[identifier]) {
                        if (Point.distance(s._mouseDownPoint[identifier], cp) < 20) {
                            //检查是否有添加对应的click事件
                            if (EventDispatcher.getMouseEventCount("onMouseClick") > 0) {
                                if (!s._ml[eLen]) {
                                    event = new MouseEvent("onMouseClick");
                                    s._ml[eLen] = event;
                                } else {
                                    event = s._ml[eLen];
                                    event.type = "onMouseClick";
                                }
                                events[events.length] = event;
                                s._initMouseEvent(event, cp, sp, identifier);
                                eLen++;
                            }
                        }
                    }
                }
                if (eLen > 0) {
                    //有事件开始遍历显示列表
                    //找出最底层的显示对象
                    let d: DisplayObject = s.hitTestPoint(cp, true);
                    // console.log(d)
                    let displayList: Array<DisplayObject> = [];
                    // my.alert({
                    //   title: '55729:' + d.instanceId
                    // });
                    if (d) {
                        //证明有点击到事件,然后从最底层追上来,看看一路是否有人添加过mouse或touch事件,还要考虑mousechildren和阻止事件方法
                        //找出真正的target,因为有些父级可能会mouseChildren=false;
                        while (d) {
                            if (d["mouseChildren"] === false) {
                                //丢掉之前的层级,因为根本没用了
                                displayList.length = 0;
                            }
                            displayList[displayList.length] = d;
                            d = d.parent;
                        }
                    } else {
                        displayList[displayList.length] = s;
                    }
                    let len: number = displayList.length;
                    for (let i = len - 1; i >= 0; i--) {
                        d = displayList[i];
                        for (let j = 0; j < eLen; j++) {
                            if (!events[j]["_pd"]) {
                                //有事件，且mouseEnable为true
                                if (d.hasEventListener(events[j].type) && d.mouseEnable) {
                                    events[j].target = d;
                                    events[j].currentTarget = displayList[0];
                                    lp = d.globalToLocal(cp, DisplayObject._bp);
                                    events[j].localX = lp.x;
                                    events[j].localY = lp.y;
                                    d.dispatchEvent(events[j]);
                                }
                            }
                        }
                    }
                    //这里一定要反转一下，因为会影响mouseOut mouseOver
                    displayList.reverse();
                    for (let i = len - 1; i >= 0; i--) {
                        d = displayList[i];
                        for (let j = 0; j < eLen; j++) {
                            if (!events[j]["_pd"]) {
                                //有事件，且mouseEnable为true
                                if (d.hasEventListener(events[j].type, false) && d.mouseEnable) {
                                    events[j].target = d;
                                    events[j].currentTarget = displayList[eLen - 1];
                                    lp = d.globalToLocal(cp, DisplayObject._bp);
                                    events[j].localX = lp.x;
                                    events[j].localY = lp.y;
                                    d.dispatchEvent(events[j], null, false);
                                }
                            }
                        }
                    }
                    //最后要和上一次的遍历者对比下，如果不相同则要触发onMouseOver和onMouseOut
                    if (item != "onMouseDown") {
                        if (EventDispatcher.getMouseEventCount("onMouseOver") > 0 || EventDispatcher.getMouseEventCount("onMouseOut") > 0) {
                            if (s._lastDpList[identifier]) {
                                //从第二个开始，因为第一个对象始终是stage顶级对象
                                let len1 = s._lastDpList[identifier].length;
                                let len2 = displayList.length;
                                len = len1 > len2 ? len1 : len2;
                                let isDiff = false;
                                let overEvent: MouseEvent;
                                let outEvent: MouseEvent;
                                for (let i = 1; i < len; i++) {
                                    if (!isDiff) {
                                        if (s._lastDpList[identifier][i] != displayList[i]) {
                                            //确定哪些有onMouseOver,哪些有onMouseOut
                                            isDiff = true;
                                            if (!s._ml[eLen]) {
                                                overEvent = new MouseEvent("onMouseOver");
                                                s._ml[eLen] = overEvent;
                                            } else {
                                                overEvent = s._ml[eLen];
                                                overEvent.type = "onMouseOver";
                                            }
                                            s._initMouseEvent(overEvent, cp, sp, identifier);
                                            eLen++;
                                            if (!s._ml[eLen]) {
                                                outEvent = new MouseEvent("onMouseOut");
                                                s._ml[eLen] = outEvent;
                                            } else {
                                                outEvent = s._ml[eLen];
                                                outEvent.type = "onMouseOut";
                                            }
                                            s._initMouseEvent(outEvent, cp, sp, identifier);
                                        }
                                    }
                                    if (isDiff) {
                                        if (s._lastDpList[identifier][i]) {
                                            //触发onMouseOut事件
                                            if (!outEvent["_pd"]) {
                                                d = s._lastDpList[identifier][i];
                                                if (d.hasEventListener("onMouseOut")) {
                                                    outEvent.currentTarget = s._lastDpList[identifier][len1 - 1];
                                                    outEvent.target = d;
                                                    lp = d.globalToLocal(cp, DisplayObject._bp);
                                                    outEvent.localX = lp.x;
                                                    outEvent.localY = lp.y;
                                                    d.dispatchEvent(outEvent);
                                                }
                                            }
                                        }
                                        if (displayList[i]) {
                                            //触发onMouseOver事件
                                            if (!overEvent["_pd"]) {
                                                d = displayList[i];
                                                if (d.hasEventListener("onMouseOver")) {
                                                    overEvent.currentTarget = displayList[len2 - 1];
                                                    overEvent.target = d;
                                                    lp = d.globalToLocal(cp, DisplayObject._bp);
                                                    overEvent.localX = lp.x;
                                                    overEvent.localY = lp.y;
                                                    d.dispatchEvent(overEvent);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        s._mp[s._mp.length] = cp;
                    }
                    if (item == "onMouseUp") {
                        delete s._mouseDownPoint[identifier];
                        delete s._lastDpList[identifier];
                    } else {
                        s._lastDpList[identifier] = displayList;
                    }
                }
            }
        }
    };

    public getBounds(): Rectangle {
        return this.viewRect;
    }

    public destroy(): void {
        let s = this;
        // if (getEnv() == "tb") clearInterval(s.offsetTimeId);
        s.renderObj.destroy();
        s.renderObj = null;
        s.viewRect = null;
        s._lastDpList = null;
        s._ml = null;
        super.destroy();
    }
}