import { LogUtils } from "../Util/LogUtils";
import FMItemLayout from "./FMItemLayout";


1./*
 * @Descripttion: 自定义带遮罩的容器,里面包含一个 content节点
 * @version: 1.0.0
 * @Author: YeeChan
 * @Date: 2020-07-17 16:52:59
 */

const { ccclass, property, requireComponent, disallowMultiple, menu, } = cc._decorator;

const EPSILON = 1e-4;
//滑动的方向
export enum SlideDirection {
    HORIZONTAL = 1,//水平滚动
    VERTICAL = 2//垂直滚动
}

export enum FMTouchEvent {
    TouchStart = "touch_start",
    Scrolling = "scrolling",
    TouchEnded = "touch_ended"
}



@ccclass
@disallowMultiple() //防止多个相同类型(或子类型)的组件被添加到同一个节点
//@menu("FM组件/FMTouchMaskView")
export default class FMTouchMaskView extends cc.Component {

    //layout预制体
    @property({ tooltip: "layout预制体(0,1)", type: cc.Prefab })
    public itemPrefab: cc.Prefab = null;

    //滑动模式 
    @property()
    protected _slideDirection: SlideDirection = SlideDirection.HORIZONTAL;
    @property({ type: cc.Enum(SlideDirection), tooltip: "滚动方向:\n HORIZONTAL 水平滚动\nVERTICAL垂直滚动" })
    set slideDirection(val: SlideDirection) {
        this._slideDirection = val;
    }
    get slideDirection() { return this._slideDirection; }

    //-----------------------------------

    private _tempPoint_custom = cc.v2();
    private _tempPrevPoint_custom = cc.v2();
    private _outOfBoundaryAmount_custom = cc.v2(0, 0);
    private _outOfBoundaryAmountDirty_custom = true;
    //是否初始化过 标签
    private _inited_custom: boolean = false;

    //-------------------------
    //是否移动
    protected _touchMoved_custom = false;
    //如果这个属性被设置为 true,那么滚动行为会取消子节点上注册的触摸事件,默认被设置为 true。* 注意,子节点上的 touchstart 事件仍然会触发,触点移动距离非常短的情况下 touchmove 和 touchend 也不会受影响。
    protected cancelInnerEvents_custom = true;
    //预制的宽高
    protected itemPrefabWidth_custom: number = 0;
    protected itemPrefabHeight_custom: number = 0;
    //滚动区域
    protected content_custom: cc.Node;

    //预制体的脚本
    private scriptItemPrefab_custom: FMItemLayout;

    protected onLoad() {
        this._init_custom();
    }

    /**
     * 初始
     */
    protected _init_custom(): boolean {
        if (this._inited_custom) {
            //是否初始化过
            return false;
        }
        let widget = this.node.getComponent(cc.Widget);
        if (widget) {
            widget.updateAlignment();
        }

        //遮罩
        let view: cc.Node = new cc.Node();
        view.anchorX = 0;
        view.anchorY = 1;
        let viewWidget = view.addComponent(cc.Widget);
        viewWidget.top = 0;
        viewWidget.left = 0;
        viewWidget.right = 0;
        viewWidget.bottom = 0;
        viewWidget.isAlignTop = true;
        viewWidget.isAlignLeft = true;
        viewWidget.isAlignRight = true;
        viewWidget.isAlignBottom = true;
        this.node.addChild(view);
        viewWidget.updateAlignment();
        let mash = view.addComponent(cc.Mask);
        mash.type = cc.Mask.Type.RECT;

        //显示面板 滚动节点
        this.content_custom = new cc.Node();
        view.addChild(this.content_custom);
        this.content_custom.width = view.width;
        this.content_custom.height = view.height;
        this.content_custom.anchorX = 0;
        this.content_custom.anchorY = 1;
        this.content_custom.x = 0;
        this.content_custom.y = 0;


        this.itemPrefabWidth_custom = this.itemPrefab.data.width;
        this.itemPrefabHeight_custom = this.itemPrefab.data.height;

        this.scriptItemPrefab_custom = this.itemPrefab.data.getComponent(FMItemLayout);
        if (!this.scriptItemPrefab_custom) {
            LogUtils.error_custom("FMTouchMaskView -> _init -> scriptItemPrefab is null");
        } else {
            if (this.scriptItemPrefab_custom.getItemChildrenCount_custom() == 0) {
                LogUtils.error_custom("FMTouchMaskView -> _init -> scriptItemPrefab item child count ==0");
            }
        }

        this._inited_custom = true;
        return true;
    }

    protected _dispatchEvent_custom(name: FMTouchEvent) {
        console.log("FMTouchMaskView -> _dispatchEvent -> name", name);
    }

    /**
     * 如果这个属性被设置为 true,那么滚动行为会取消子节点上注册的触摸事件,默认被设置为 true。* 
     * 注意,子节点上的 touchstart 事件仍然会触发,触点移动距离非常短的情况下 touchmove 和 touchend 也不会受影响。
     * @param inner 
     */
    public setCancelInnerEvents_custom(inner: boolean) {
        this.cancelInnerEvents_custom = inner;
    }



    /**
     * 按下
     * @param touch
     */
    protected handlePressLogic_custom(touch: cc.Touch) {
        this._dispatchEvent_custom(FMTouchEvent.TouchStart);
    }

    /**
     * 移动
     * @param touch
     */
    protected handleMoveLogic_custom(touch: cc.Touch) {
        let deltaMove = this.getLocalAxisAlignDelta_custom(touch);
        deltaMove = this.clampDelta_custom(deltaMove);
        let realMove = deltaMove;
        let outOfBoundary = this.getHowMuchOutOfBoundary_custom(realMove);
        realMove = realMove.add(outOfBoundary);

        if (realMove.x !== 0 || realMove.y !== 0) {
            this._moveContent_custom(realMove);
            this._dispatchEvent_custom(FMTouchEvent.Scrolling);
        }
    }



    /**
     * 释放
     * @param touch
     */
    protected handleReleaseLogic_custom(touch: cc.Touch) {
        let delta = this.getLocalAxisAlignDelta_custom(touch);

        this._dispatchEvent_custom(FMTouchEvent.TouchEnded);
    }


    private _flattenVectorByDirection_custom(vector) {
        let result = vector;
        result.x = this._slideDirection == SlideDirection.HORIZONTAL ? result.x : 0;
        result.y = this._slideDirection == SlideDirection.VERTICAL ? result.y : 0;
        return result;
    }

    private _moveContent_custom(deltaMove) {
        let adjustedMove = this._flattenVectorByDirection_custom(deltaMove);
        let newPosition = this.getContentPosition_custom().add(adjustedMove);
        this.setContentPosition_custom(newPosition);
    }

    // Contains node angle calculations
    private getLocalAxisAlignDelta_custom(touch): cc.Vec2 {
        this.node.convertToNodeSpaceAR(touch.getLocation(), this._tempPoint_custom);
        this.node.convertToNodeSpaceAR(touch.getPreviousLocation(), this._tempPrevPoint_custom);
        return this._tempPoint_custom.sub(this._tempPrevPoint_custom);
    }

    //this is for nested scrollview
    private _hasNestedViewGroup_custom(event: cc.Event.EventTouch, captureListeners) {
        if (event.eventPhase !== cc.Event.CAPTURING_PHASE) return;

        if (captureListeners) {
            //captureListeners are arranged from child to parent
            for (let i = 0; i < captureListeners.length; ++i) {
                let item = captureListeners[i];

                if (this.node === item) {
                    if (event.target.getComponent(cc.ViewGroup)) {
                        return true;
                    }
                    return false;
                }

                if (item.getComponent(cc.ViewGroup)) {
                    return true;
                }
            }
        }
        return false;
    }
    //This is for Scrollview as children of a Button
    private _stopPropagationIfTargetIsMe_custom(event) {
        if (event.eventPhase === cc.Event.AT_TARGET && event.target === this.node) {
            event.stopPropagation();
        }
    }

    // touch event handler
    private _onTouchBegan_custom(event: cc.Event.EventTouch, captureListeners) {
        if (!this.enabledInHierarchy) return;
        if (this._hasNestedViewGroup_custom(event, captureListeners)) return;

        let touch = event.touch;
        if (this.content_custom) {
            this.handlePressLogic_custom(touch);
        }
        this._touchMoved_custom = false;
        this._stopPropagationIfTargetIsMe_custom(event);
    }

    private _onTouchMoved_custom(event: cc.Event.EventTouch, captureListeners) {
        if (!this.enabledInHierarchy) return;
        if (this._hasNestedViewGroup_custom(event, captureListeners)) return;

        let touch = event.touch;
        if (!this.node["_hitTest"](touch.getLocation(), this.node)) {//过滤掉不在区域内移动
            return;
        }


        if (this.content_custom) {
            this.handleMoveLogic_custom(touch);
        }
        // Do not prevent touch events in inner nodes

        if (!this.cancelInnerEvents_custom) {
            return;
        }

        let deltaMove = touch.getLocation().sub(touch.getStartLocation());
        //FIXME: touch move delta should be calculated by DPI.
        if (deltaMove.mag() > 7) {
            if (!this._touchMoved_custom) {
                // && event.target !== this.node
                // Simulate touch cancel for target node
                let cancelEvent = new cc.Event.EventTouch(
                    event.getTouches(),
                    event.bubbles
                );
                cancelEvent.type = cc.Node.EventType.TOUCH_CANCEL;
                cancelEvent.touch = event.touch;
                cancelEvent["simulate"] = true;
                event.target.dispatchEvent(cancelEvent);
                this._touchMoved_custom = true;
            }
        }
        this._stopPropagationIfTargetIsMe_custom(event);
    }

    private _onTouchEnded_custom(event: cc.Event.EventTouch, captureListeners) {
        if (!this.enabledInHierarchy) return;
        if (this._hasNestedViewGroup_custom(event, captureListeners)) return;

        let touch = event.touch;
        if (this.content_custom) {
            this.handleReleaseLogic_custom(touch);
        }
        if (this._touchMoved_custom) {
            event.stopPropagation();
        } else {
            this._stopPropagationIfTargetIsMe_custom(event);
        }
    }

    private _onTouchCancelled_custom(event, captureListeners) {
        if (!this.enabledInHierarchy) return;
        if (this._hasNestedViewGroup_custom(event, captureListeners)) return;

        // Filte touch cancel event send from self
        if (!event.simulate) {
            let touch = event.touch;
            if (this.content_custom) {
                this.handleReleaseLogic_custom(touch);
            }
        }
        this._stopPropagationIfTargetIsMe_custom(event);
    }
    private clampDelta_custom(delta) {
        if (this._slideDirection == SlideDirection.HORIZONTAL) {
            delta.y = 0;
        } else if (this._slideDirection == SlideDirection.VERTICAL) {
            delta.x = 0;
        }


        return delta;
    }


    private getHowMuchOutOfBoundary_custom(addition) {
        addition = addition || cc.v2(0, 0);
        if (addition.fuzzyEquals(cc.v2(0, 0), EPSILON) && !this._outOfBoundaryAmountDirty_custom) {
            return this._outOfBoundaryAmount_custom;
        }

        let outOfBoundaryAmount = cc.v2(0, 0);
        if (addition.fuzzyEquals(cc.v2(0, 0), EPSILON)) {
            this._outOfBoundaryAmount_custom = outOfBoundaryAmount;
            this._outOfBoundaryAmountDirty_custom = false;
        }

        outOfBoundaryAmount = this.clampDelta_custom(outOfBoundaryAmount);

        return outOfBoundaryAmount;
    }


    private getContentPosition_custom() {
        return this.content_custom.getPosition();
    }
    private setContentPosition_custom(position) {
        if (position.fuzzyEquals(this.getContentPosition_custom(), EPSILON)) {
            return;
        }

        this.content_custom.setPosition(position);
        this._outOfBoundaryAmountDirty_custom = true;
    }


    onEnable() {
        this._registerEvent_custom();
    }

    onDisable() {
        this._unregisterEvent_custom();
    }

    //private methods
    private _registerEvent_custom() {
        this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan_custom, this, true);
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved_custom, this, true);
        this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded_custom, this, true);
        this.node.on(
            cc.Node.EventType.TOUCH_CANCEL,
            this._onTouchCancelled_custom,
            this,
            true
        );
    }

    private _unregisterEvent_custom() {
        this.node.off(
            cc.Node.EventType.TOUCH_START,
            this._onTouchBegan_custom,
            this,
            true
        );
        this.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved_custom, this, true);
        this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded_custom, this, true);
        this.node.off(
            cc.Node.EventType.TOUCH_CANCEL,
            this._onTouchCancelled_custom,
            this,
            true
        );
    }
}