// Learn cc.Class:
//  - https://docs.cocos.com/creator/manual/en/scripting/class.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

const defaultData = require('../script/defaultData');
import {
    asyncTweenTo,
    asyncTweenBy,
    asyncDelay,
    asyncPlayDragonBoneAnimation,
    asyncPlayEffectByUrl,
    RandomInt,
    jelly
} from '../script/utils';

cc.Class({
    extends: cc.Component,

    properties: {

        letter: 'b',

        bgNode: {
            type: cc.Prefab,
            default: null,
        },

        monkeyUpperNode: {
            type: cc.Prefab,
            default: null,
        },

        letterPageUINode: {
            type: cc.Prefab,
            default: null,
        },

        cloudPrefab: {
            type: cc.Prefab,
            default: null,
        },

        starAnimePrefab: {
            type: cc.Prefab,
            default: null,
        },
        starPreFab: {
            type: cc.Prefab,
            default: null,
        },
        sharkPreFab: {
            type: cc.Prefab,
            default: null,
        },
        arrowPrefab: {
            type: cc.Prefab,
            default: null,
        },

        letterAPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterBPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterCPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterDPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterEPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterFPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterGPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterHPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterIPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterJPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterKPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterLPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterMPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterNPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterOPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterPPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterQPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterRPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterSPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterTPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterUPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterVPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterWPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterXPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterYPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterZPrefab: {
            type: cc.Prefab,
            default: null,
        },

        letteraPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterbPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettercPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterdPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterePrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterfPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettergPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterhPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letteriPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterjPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterkPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterlPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettermPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letternPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letteroPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterpPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterqPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterrPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettersPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettertPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letteruPrefab: {
            type: cc.Prefab,
            default: null,
        },
        lettervPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterwPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterxPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letteryPrefab: {
            type: cc.Prefab,
            default: null,
        },
        letterzPrefab: {
            type: cc.Prefab,
            default: null,
        },
    },

    // LIFE-CYCLE CALLBACKS:

    // onLoad () {},

    ctor() {
        this._imageResList = [];
        this._audioResList = [];
    },

    start() {
        cc.debug.setDisplayStats(false);
        this.data = defaultData;
        this.getData((data) => {
            if (data) {
                this.data = data;
            }
            this.preloadItem();
        });
    },

    getData(func) {
        if (window && window.courseware) {
            window.courseware.getData(func, 'Z_05_nxuan1');
        } else {
            func(defaultData);
        }
    },

    _imageResList: null,
    _audioResList: null,
    preloadItem() {
        this.addPreloadImage();
        this.addPreloadAudio();
        this.preload();
        this.onLoadFinish();
    },

    addPreloadImage() { },

    addPreloadAudio() { },

    preload() {
        const preloadArr = this._imageResList.concat(this._audioResList);
        cc.assetManager.loadAny(preloadArr, null, null, (err, data) => {
            this.hideAirClassLoading();
        });
        if (preloadArr.length == 0) {
            this.hideAirClassLoading();
        }
    },

    hideAirClassLoading() {
        if (window && window["air"]) {
            window["air"].hideAirClassLoading();
            console.log('hideAirClassLoading');
        }
    },

    initListeners() {
        const btnSwitch = cc.find('Canvas/LetterPicturePage/BtnSwitch');
        const changeToBig = cc.find('Canvas/LetterPicturePage/BtnSwitch/ChangeToBig');
        const changeToSmall = cc.find('Canvas/LetterPicturePage/BtnSwitch/ChangeToSmall');
        const smallLetterBase = cc.find('Canvas/LetterPicturePage/SmallLetterBase');
        const bigLetterBase = cc.find('Canvas/LetterPicturePage/BigLetterBase');
        const pictureWordSmallBg = cc.find('Canvas/LetterPicturePage/PictureWordSmallBg');
        const pictureWordBigBg = cc.find('Canvas/LetterPicturePage/PictureWordBigBg');

        btnSwitch.on('click', () => {
            this.playEffect('按钮');

            changeToBig.active = !changeToBig.active;
            bigLetterBase.active = !bigLetterBase.active;
            pictureWordBigBg.active = !pictureWordBigBg.active;

            changeToSmall.active = !changeToSmall.active;
            smallLetterBase.active = !smallLetterBase.active;
            pictureWordSmallBg.active = !pictureWordSmallBg.active;
        });

        smallLetterBase.on('click', () => {
            this.playEffect(`letter${this.letter.toUpperCase()}`);
            cc.tween(smallLetterBase)
                .to(0.1, { angle: 5 })
                .to(0.1, { angle: -5 })
                .to(0.1, { angle: 0 })
                .start();
        });
        bigLetterBase.on('click', () => {
            this.playEffect(`letter${this.letter.toUpperCase()}`);
            cc.tween(bigLetterBase)
                .to(0.1, { angle: 5 })
                .to(0.1, { angle: -5 })
                .to(0.1, { angle: 0 })
                .start();
        });
        pictureWordSmallBg.on('click', () => {
            jelly(pictureWordSmallBg);
            this.playEffect(`word${this.letter.toUpperCase()}`);
        });
        pictureWordBigBg.on('click', () => {
            jelly(pictureWordBigBg);
            this.playEffect(`word${this.letter.toUpperCase()}`);
        });

        let btnClicked = false;
        const btnBefore = cc.find('Canvas/LetterPicturePage/BtnBefore');
        const btnNext = cc.find('Canvas/LetterPicturePage/BtnNext');
        btnBefore.on('click', async () => {
            if (btnClicked) {
                return;
            }
            btnClicked = true;
            this.playEffect('按钮');
            await jelly(btnBefore);

            this._status.currentLetterIdx--;
            this._status.currentLetterIdx = Math.max(this._status.currentLetterIdx, 0);

            this.updateLetter();
            btnClicked = false;
        });
        btnNext.on('click', async () => {
            if (btnClicked) {
                return;
            }
            btnClicked = true;
            this.playEffect('按钮');
            await jelly(btnNext);

            this._status.currentLetterIdx++;
            this._status.currentLetterIdx = Math.min(this._status.currentLetterIdx, this.data.letterList.length - 1);

            this.updateLetter();
            btnClicked = false;
        });
    },

    onLoadFinish() {
        this.initListeners();

        this._status = {
            currentLetterIdx: 0,
            currentStrokeIdx: 0,
            currentStrokeStageIdx: 0
        }
        this.updateLetter();
    },

    updateLetter() {
        this.letter = this.data.letterList[this._status.currentLetterIdx];
        this.letter = this.letter.toUpperCase();
        this.createLetterPage(this.letter);
        this.updatePicturePage();
    },

    updatePicturePage() {
        const bigLetter = cc.find('Canvas/LetterPicturePage/BigLetterBase/BigLetter');
        bigLetter.getComponent(cc.Label).string = this.letter.toUpperCase();
        const smallLetter = cc.find('Canvas/LetterPicturePage/SmallLetterBase/SmallLetter');
        smallLetter.getComponent(cc.Label).string = this.letter.toLowerCase();

        const bgListToLetter = ['QV', 'CHKUX', 'BGIW', 'AEJNOY', 'DFLMPRSTZ'];
        const showBgIdx = bgListToLetter.findIndex(str => str.indexOf(this.letter.toUpperCase()) != -1);

        bgListToLetter.forEach((_, idx) => {
            const bgNode = cc.find(`Canvas/LetterPicturePage/BgNode_${idx}`);
            bgNode.active = idx == showBgIdx;
        });

        const letterWordMap = {
            a: { big: '<color=#73edff>A</c><color=#ffffff>PPLE</color>', small: '<color=#abff61>a</c><color=#ffffff>pple</color>' },
            b: { big: '<color=#73edff>B</c><color=#ffffff>AG</color>', small: '<color=#abff61>b</c><color=#ffffff>ag</color>' },
            c: { big: '<color=#73edff>C</c><color=#ffffff>AT</color>', small: '<color=#abff61>c</c><color=#ffffff>at</color>' },
            d: { big: '<color=#73edff>D</c><color=#ffffff>OG</color>', small: '<color=#abff61>d</c><color=#ffffff>og</color>' },
            e: { big: '<color=#73edff>E</c><color=#ffffff>GG</color>', small: '<color=#abff61>e</c><color=#ffffff>gg</color>' },
            f: { big: '<color=#73edff>F</c><color=#ffffff>LOWER</color>', small: '<color=#abff61>f</c><color=#ffffff>lower</color>' },
            g: { big: '<color=#73edff>G</c><color=#ffffff>IRL</color>', small: '<color=#abff61>g</c><color=#ffffff>irl</color>' },
            h: { big: '<color=#73edff>H</c><color=#ffffff>AT</color>', small: '<color=#abff61>h</c><color=#ffffff>at</color>' },
            i: { big: '<color=#73edff>I</c><color=#ffffff>NK</color>', small: '<color=#abff61>i</c><color=#ffffff>nk</color>' },
            j: { big: '<color=#73edff>J</c><color=#ffffff>AM</color>', small: '<color=#abff61>j</c><color=#ffffff>am</color>' },
            k: { big: '<color=#73edff>K</c><color=#ffffff>EY</color>', small: '<color=#abff61>k</c><color=#ffffff>ey</color>' },
            l: { big: '<color=#73edff>L</c><color=#ffffff>ION</color>', small: '<color=#abff61>l</c><color=#ffffff>ion</color>' },
            m: { big: '<color=#73edff>M</c><color=#ffffff>ONKEY</color>', small: '<color=#abff61>m</c><color=#ffffff>onkey</color>' },
            n: { big: '<color=#73edff>N</c><color=#ffffff>OODLES</color>', small: '<color=#abff61>n</c><color=#ffffff>oodles</color>' },
            o: { big: '<color=#73edff>O</c><color=#ffffff>RANGE</color>', small: '<color=#abff61>o</c><color=#ffffff>range</color>' },
            p: { big: '<color=#73edff>P</c><color=#ffffff>ANDA</color>', small: '<color=#abff61>p</c><color=#ffffff>anda</color>' },
            q: { big: '<color=#73edff>Q</c><color=#ffffff>UEEN</color>', small: '<color=#abff61>q</c><color=#ffffff>ueen</color>' },
            r: { big: '<color=#73edff>R</c><color=#ffffff>ABBIT</color>', small: '<color=#abff61>r</c><color=#ffffff>abbit</color>' },
            s: { big: '<color=#73edff>S</c><color=#ffffff>WAN</color>', small: '<color=#abff61>s</c><color=#ffffff>wan</color>' },
            t: { big: '<color=#73edff>T</c><color=#ffffff>IGER</color>', small: '<color=#abff61>t</c><color=#ffffff>iger</color>' },
            u: { big: '<color=#73edff>U</c><color=#ffffff>MBRELLA</color>', small: '<color=#abff61>u</c><color=#ffffff>mbrella</color>' },
            v: { big: '<color=#73edff>V</c><color=#ffffff>IOLIN</color>', small: '<color=#abff61>v</c><color=#ffffff>iolin</color>' },
            w: { big: '<color=#73edff>W</c><color=#ffffff>INDOW</color>', small: '<color=#abff61>w</c><color=#ffffff>indow</color>' },
            x: { big: '<color=#ffffff>BO</color><color=#73edff>X</c>', small: '<color=#ffffff>bo</color><color=#abff61>x</c>' },
            y: { big: '<color=#73edff>Y</c><color=#ffffff>OGURT</color>', small: '<color=#abff61>y</c><color=#ffffff>ogurt</color>' },
            z: { big: '<color=#73edff>Z</c><color=#ffffff>EBRA</color>', small: '<color=#abff61>z</c><color=#ffffff>ebra</color>' }
        }

        const bigPictureWordRichText = cc.find('Canvas/LetterPicturePage/PictureWordBigBg/PictureWordBigRichText');
        bigPictureWordRichText.getComponent(cc.RichText).string = letterWordMap[this.letter.toLowerCase()].big;
        const pictureWordSmallRichText = cc.find('Canvas/LetterPicturePage/PictureWordSmallBg/PictureWordSmallRichText');
        pictureWordSmallRichText.getComponent(cc.RichText).string = letterWordMap[this.letter.toLowerCase()].small;

        const letterPictureBase = cc.find('Canvas/LetterPicturePage/LetterPicture');
        letterPictureBase.children.forEach(node => node.active = false);
        const letterPictureNode = cc.find(`Canvas/LetterPicturePage/LetterPicture/bg_${this.letter.toLowerCase()}`);
        letterPictureNode.active = true;
        letterPictureNode.addComponent(cc.Button);
        letterPictureNode.on('click', () => {
            jelly(letterPictureNode);
            this.playEffect(`word${this.letter.toUpperCase()}`);
        });

        const changeToBig = cc.find('Canvas/LetterPicturePage/BtnSwitch/ChangeToBig');
        const changeToSmall = cc.find('Canvas/LetterPicturePage/BtnSwitch/ChangeToSmall');

        const smallLetterBase = cc.find('Canvas/LetterPicturePage/SmallLetterBase');
        const bigLetterBase = cc.find('Canvas/LetterPicturePage/BigLetterBase');

        const pictureWordSmallBg = cc.find('Canvas/LetterPicturePage/PictureWordSmallBg');
        const pictureWordBigBg = cc.find('Canvas/LetterPicturePage/PictureWordBigBg');

        changeToBig.active = false;
        changeToSmall.active = true;
        smallLetterBase.active = false;
        bigLetterBase.active = true;
        pictureWordSmallBg.active = false;
        pictureWordBigBg.active = true;

        const btnBefore = cc.find('Canvas/LetterPicturePage/BtnBefore');
        const btnNext = cc.find('Canvas/LetterPicturePage/BtnNext');

        btnBefore.active = true;
        btnNext.active = true;

        if (this._status.currentLetterIdx == 0) {
            btnBefore.active = false;
        }
        if (this._status.currentLetterIdx == this.data.letterList.length - 1) {
            btnNext.active = false;
        }
    },

    createLetterPage(letter) {
        this._status.currentStrokeIdx = 0;
        this._status.currentStrokeStageIdx = 0;

        const letterNodeBase = cc.instantiate(this[`letter${letter}Prefab`]);
        letterNodeBase.name = 'letterNodeBase';
        letterNodeBase.parent = this.node;

        const btnUpdate = letterNodeBase.getChildByName('BtnUpdate');
        btnUpdate.on('click', () => {
            this.drawCompleteLine(this._status.currentStrokeIdx);
            this._status.currentStrokeIdx++;
            if (this._status.currentStrokeIdx >= 4) {
                this._status.currentStrokeIdx = 0;
            }
            this.updateArrows(this._status.currentStrokeIdx);
        });
        btnUpdate.active = false;
        this.updateArrows(this._status.currentStrokeIdx);

        // this.drawCompleteLine(this._status.currentStrokeIdx);

        const letterPageUINode = cc.instantiate(this.letterPageUINode);
        letterPageUINode.name = 'letterPageUINode';
        letterPageUINode.parent = letterNodeBase;

        const speakerNode = letterPageUINode.getChildByName('SpeakerNode');
        speakerNode.on('click', async () => {
            this.startSpeakerAnime(speakerNode);

            await this.playEffect(`letter${this.letter.toUpperCase()}`);

            this.stopSpeakerAnime(speakerNode);
        });

        let showMonkey = false;
        if ('abcdefghijklmnopqrstuvwxyz'.indexOf(letter) !== -1) {
            showMonkey = true;
        }
        const pos0 = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_0_0`);
        this.createShark(pos0, this.getSharkAngle(0, 0), showMonkey);

        this.node.getComponent(cc.Widget).updateAlignment();
        letterNodeBase.scaleX = this.node.height / 720;
        letterNodeBase.scaleY = this.node.height / 720;

        const bgNode = cc.instantiate(this.bgNode);
        bgNode.parent = letterNodeBase;
        bgNode.zIndex = -1;
        bgNode.width = this.node.width / letterNodeBase.scaleY;

        bgNode.addComponent(cc.Button);
        bgNode.on('click', () => { });

        if (letter !== letter.toLowerCase()) {
            bgNode.getChildByName('BgLower').active = false;
        }

        if (letter == letter.toLowerCase()) {
            const monkeyUpperNode = cc.instantiate(this.monkeyUpperNode);
            monkeyUpperNode.parent = letterNodeBase;
            monkeyUpperNode.scaleX = 720 / this.node.height;
            monkeyUpperNode.scaleY = 720 / this.node.height;
            monkeyUpperNode.y = -letterNodeBase.height / 2;
        }

        const drawMaskNode = cc.find('Canvas/letterNodeBase/DrawMaskNode');
        const letterNode = cc.find('Canvas/letterNodeBase/bg_a');
        // drawMaskNode.scaleX = letterNodeBase.height / 720;
        // drawMaskNode.scaleY = letterNodeBase.height / 720;
        // letterNode.scaleX = letterNodeBase.height / 720;
        // letterNode.scaleY = letterNodeBase.height / 720;
    },

    startSpeakerAnime(speakerBase) {
        const speakers = [];
        for (let index = 0; index < 3; index++) {
            const node = cc.find(`Canvas/letterNodeBase/letterPageUINode/SpeakerNode/SpeakerNode_${index}`);
            speakers.push(node);
        }
        let currentIdx = 0;
        cc.tween(speakerBase)
            .call(() => {
                speakers.forEach(node => node.active = false);
                speakers[currentIdx % speakers.length].active = true;
                currentIdx++;
            })
            .delay(0.2)
            .union()
            .repeatForever()
            .start();
    },

    stopSpeakerAnime(speakerBase) {
        speakerBase.stopAllActions();
        for (let index = 0; index < 3; index++) {
            const node = cc.find(`Canvas/letterNodeBase/letterPageUINode/SpeakerNode/SpeakerNode_${index}`);
            node.active = false;
        }
        const node0 = cc.find(`Canvas/letterNodeBase/letterPageUINode/SpeakerNode/SpeakerNode_0`);
        node0.active = true;
    },

    getPosListByStrokeIdx(strokeIdx) {
        let stepIdx = 0;
        let pos0 = null;
        const drawMaskNode = cc.find('Canvas/letterNodeBase/DrawMaskNode');
        const posList = [];
        do {
            pos0 = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_${strokeIdx}_${stepIdx}`);
            stepIdx++;

            if (pos0) {
                posList.push({
                    x: pos0.x,
                    y: pos0.y
                });
            }
        } while (pos0);
        return posList;
    },

    getCurrentCheckPos() {
        const letterNodeBase = cc.find(`Canvas/letterNodeBase`);
        const nodePosBase = cc.find('Canvas/letterNodeBase/NodePosBase');
        let pos1 = nodePosBase.getChildByName(`NodePos_${this._status.currentStrokeIdx}_${this._status.currentStrokeStageIdx}`);
        let pos2 = nodePosBase.getChildByName(`NodePos_${this._status.currentStrokeIdx}_${this._status.currentStrokeStageIdx + 1}`);
        if (pos2) {
            return [
                { x: pos1.x, y: pos1.y },
                { x: pos2.x, y: pos2.y }
            ];
        }
        return null;
    },

    updateArrows(idx) {
        const nodePosBase = cc.find(`Canvas/letterNodeBase/NodePosBase`);

        if (this.arrowPrefabList) {
            this.arrowPrefabList.forEach(node => {
                node.parent = null;
            });
        }
        this.arrowPrefabList = [];

        for (let i = 0; i < 5; i++) {
            let startNode = nodePosBase.getChildByName(`NodePos_${i}_0`);
            if (!startNode) {
                break;
            }
            this.createStep(startNode, nodePosBase, i, idx);
        }
    },

    createStep(startNode, nodePosBase, stepIdx, idx) {

        startNode.opacity = 0;
        let lastNode = startNode;
        for (let j = 1; j < 100; j++) {
            let endNode = nodePosBase.getChildByName(`NodePos_${stepIdx}_${j}`);
            if (!endNode) {
                break;
            }
            endNode.opacity = 0;

            this.createArrows(endNode, lastNode, stepIdx, j, idx);
            lastNode = endNode;
        }
        if (stepIdx == idx) {
            this.createStar(lastNode);
        }
    },

    createArrows(endNode, lastNode, skipIdx, stepIdx, idx) {

        let letterNodeBase = cc.find(`Canvas/letterNodeBase`)
        const lineVec2 = cc.v2(endNode.x - lastNode.x, endNode.y - lastNode.y);
        const lineLength = lineVec2.mag();
        const arrowNumber = Math.ceil(lineLength / 100);
        for (let k = 0; k < arrowNumber; k++) {
            if (skipIdx != idx) {
                continue;
            }
            const arrowPrefab = cc.instantiate(this.arrowPrefab);
            arrowPrefab.x = lastNode.x + lineVec2.x * (k + 1) / (arrowNumber + 1);
            arrowPrefab.y = lastNode.y + lineVec2.y * (k + 1) / (arrowNumber + 1);
            arrowPrefab.angle = cc.v2(1, 0).signAngle(lineVec2) * 180 / Math.PI;
            // arrowPrefab.name = name;
            arrowPrefab.parent = letterNodeBase;
            this.arrowPrefabList.push(arrowPrefab);

            const nodeArrow = arrowPrefab.getChildByName('NodeArrow');
            cc.tween(nodeArrow)
                .delay((stepIdx + k) * 0.3)
                .to(0.2, { opacity: 0 })
                .delay(0.5)
                .to(0.2, { opacity: 255 })
                .delay(7 - (stepIdx + k) * 0.3)
                .union()
                .repeatForever()
                .start();
        }
    },

    createShark(startNode, angle, monkey = false) {
        let letterNodeBase = cc.find(`Canvas/letterNodeBase`)
        let sharkNode = null;
        if (!this.sharkNode) {
            this.sharkNode = cc.instantiate(this.sharkPreFab);
            this.sharkNode.parent = letterNodeBase;
            this.sharkNode.zIndex = 2;
            this.sharkNode.x = startNode.x;
            this.sharkNode.y = startNode.y;
            this.addSharkListeners();
        }
        sharkNode = this.sharkNode.getChildByName('SharkNode');
        sharkNode.angle = angle;
        sharkNode.active = !monkey;

        const monkeyNode = this.sharkNode.getChildByName('MonkeyNode');
        monkeyNode.active = monkey;

        cc.tween(this.sharkNode)
            .to(0.1, { x: startNode.x, y: startNode.y })
            .start();
        cc.tween(sharkNode)
            .to(0.1, { angle: angle })
            .start();
    },

    onSharkMoveStart(event) {
        jelly(this.sharkNode);
        this.sharkNode.startPos = event.getLocation();
        this.playEffect('按住');
        this.onDrawStart(event);
        this.onSharkMoving(event);
    },

    onSharkMoving(event) {
        if (!this.sharkNode) {
            return;
        }
        const startPos = this.sharkNode.startPos;
        if (!startPos) {
            return;
        }
        const location = event.getLocation();
        const currentPosList = this.getCurrentCheckPos();
        if (!currentPosList) {
            return;
        }
        const letterNodeBase = cc.find(`Canvas/letterNodeBase`);

        const [pos1, pos2] = currentPosList;
        const checkLine = cc.v2(pos2.x - pos1.x, pos2.y - pos1.y);

        const locationPos = letterNodeBase.convertToNodeSpaceAR(location);
        this.sharkNode.x = locationPos.x;
        this.sharkNode.y = locationPos.y;

        const trueSharkNode = this.sharkNode.getChildByName('SharkNode');

        let angle = cc.v2(1, 0).signAngle(checkLine) * 180 / Math.PI;
        if (angle - trueSharkNode.angle < -180) {
            angle += 360;
        }
        if (angle - trueSharkNode.angle > 180) {
            angle -= 360;
        }

        if (!this.sharkNode.angleAction) {
            this.sharkNode.angleAction = true;
            cc.tween(trueSharkNode)
                .to(0.2, { angle: angle })
                .call(() => {
                    this.sharkNode.angleAction = false;
                })
                .start();
        }

        if (this.checkPointInCircle(this.sharkNode, pos2, 50)) {
            this._status.currentStrokeStageIdx++
            if (this.getCurrentCheckPos() === null) {
                this.drawCompleteLine(this._status.currentStrokeIdx);
                this.showStar();
                this._status.currentStrokeIdx++;
                this._status.currentStrokeStageIdx = 0;
                if (this.getCurrentCheckPos() === null) {
                    this.showOver();
                }
                this.updateArrows(this._status.currentStrokeIdx);
                this.onSharkMoveEnd(event);
            }
        }

        const posList = this.getPosListByStrokeIdx(this._status.currentStrokeIdx);
        if (!this.checkPointInRangeList(letterNodeBase.convertToNodeSpaceAR(location), posList, 50)) {
            this._status.currentStrokeStageIdx = 0;
            this.onSharkMoveEnd(event);
            return;
        }

        this.onDrawMove(event);
    },

    getSharkAngle(currentStrokeIdx, currentStrokeStageIdx) {
        let pos1 = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_${currentStrokeIdx}_${currentStrokeStageIdx}`);
        let pos2 = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_${currentStrokeIdx}_${currentStrokeStageIdx + 1}`);
        const line = cc.v2(pos2.x - pos1.x, pos2.y - pos1.y);
        return cc.v2(1, 0).signAngle(line) * 180 / Math.PI;
    },

    onSharkMoveEnd(event) {
        let pos = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_${this._status.currentStrokeIdx}_0`);
        if (!pos) {
            return;
        }
        this._status.currentStrokeStageIdx = 0;

        cc.tween(this.sharkNode)
            .to(0.1, { x: pos.x, y: pos.y })
            .start();

        cc.tween(this.sharkNode.getChildByName('SharkNode'))
            .to(0.1, { angle: this.getSharkAngle(this._status.currentStrokeIdx, 0) })
            .start();

        this.sharkNode.startPos = null;
        this.onDrawEnd(event);
    },

    addSharkListeners() {
        this.sharkNode.on('touchstart', this.onSharkMoveStart, this);
        this.sharkNode.on('touchmove', this.onSharkMoving, this);
        this.sharkNode.on('touchend', this.onSharkMoveEnd, this);
        this.sharkNode.on('touchcancel', this.onSharkMoveEnd, this);
    },

    hideShark() {
        cc.tween(this.sharkNode)
            .to(0.3, { opacity: 0 })
            .call(() => {
                this.sharkNode.parent = null;
                this.sharkNode = null;
            })
            .start();
    },

    async showOver() {
        this.hideShark();
        this.playEffect('放大');
        await this.showCompleteStar();
        this.playEffect(`letter${this.letter.toUpperCase()}`);
        await this.showRibbons();
        this.playEffect('撒花');
        await this.showCloud();
        this.pageFlyOut();
        if (this.letter !== this.letter.toLowerCase()) {
            this.letter = this.letter.toLowerCase();
            this.createLetterPage(this.letter);
        }
    },

    async showCompleteStar() {
        return new Promise((resolve, reject) => {
            this.starNode.zIndex = 10;
            cc.tween(this.starNode)
                .to(0.1, { opacity: 255 })
                .to(1, { x: 0, y: 0, scale: 3 })
                .to(1, { scale: 6, opacity: 0 })
                .call(resolve)
                .start();
        })
    },

    async showCloud() {
        const cloudPosList = [];
        const posList = this.getCloudPosList();
        posList.forEach(stepList => {
            // stepList
            for (let i = 0; i < stepList.length - 1; i++) {
                const pos0 = stepList[i];
                const pos1 = stepList[i + 1];

                const line = cc.v2(pos1.x - pos0.x, pos1.y - pos0.y);
                cloudPosList.push(pos0);
                if (line.mag() > 150) {
                    const length = Math.ceil(line.mag() / 80);
                    for (let j = 1; j < length; j++) {
                        const newLine = line.mul(j / length);
                        cloudPosList.push({ x: newLine.x + pos0.x, y: newLine.y + pos0.y });
                    }
                }
            }
            cloudPosList.push(stepList[stepList.length - 1]);
        });

        await Promise.all(cloudPosList.map(async (pos, idx) => {
            await this.createCloud(pos, idx);
        }));
    },

    createCloud({ x, y }, idx) {
        return new Promise((resolve, reject) => {
            const letterNodeBase = cc.find(`Canvas/letterNodeBase`);
            const cloud = cc.instantiate(this.cloudPrefab);
            cloud.parent = letterNodeBase;
            cloud.x = x;
            cloud.y = y;
            cloud.scale = 0;

            cc.tween(cloud)
                .delay(idx * 0.1)
                .to(0.2, { scale: 1.2 })
                .to(0.05, { scale: 1 })
                .call(resolve)
                .start();
        });
    },

    getCloudPosList() {
        const posList = [];
        do {
            let currentPosList = [];
            let currentStepIdx = posList.length;
            let currentPosIdx = 0;
            do {
                let pos = cc.find(`Canvas/letterNodeBase/NodePosBase/NodePos_${currentStepIdx}_${currentPosIdx}`);
                if (!pos) {
                    break;
                }
                currentPosIdx++;
                currentPosList.push({ x: pos.x, y: pos.y });
            } while (true);

            if (currentPosList.length == 0) {
                break;
            }
            posList.push(currentPosList);
        } while (true);

        return posList;
    },

    pageFlyOut() {
        const letterNodeBase = cc.find(`Canvas/letterNodeBase`);
        letterNodeBase.name = 'letterNodeBase0';
        letterNodeBase.zIndex = 10;
        cc.tween(letterNodeBase)
            .to(1.2, { y: 1500 }, { easing: 'cubicIn' })
            .start();
    },

    createStar(endNode) {
        const letterNodeBase = cc.find(`Canvas/letterNodeBase`);
        if (this.starNode) {
            this.starNode.parent = null;
            this.starNode = null;
        }
        const starNode = cc.instantiate(this.starPreFab);
        starNode.parent = letterNodeBase;
        starNode.x = endNode.x;
        starNode.y = endNode.y;
        this.starNode = starNode;
    },

    async showStar() {
        let starAnimeNode = cc.instantiate(this.starAnimePrefab);
        starAnimeNode.parent = this.node;
        starAnimeNode.x = this.starNode.x;
        starAnimeNode.y = this.starNode.y;
        starAnimeNode.zIndex = 10;

        cc.tween(this.starNode)
            .to(0.1, { opacity: 0 })
            .start();

        this.playEffect('star');

        await asyncPlayDragonBoneAnimation(starAnimeNode, 'star', 1);
        starAnimeNode.parent = null;
        starAnimeNode = null;
    },

    async showRibbons() {
        for (let i = 0; i < 50; i++) {
            const ribbonNodeBase = cc.instantiate(cc.find('Canvas/RibbonNodeBase'));
            ribbonNodeBase.parent = this.node;
            ribbonNodeBase.y = 400 + RandomInt(720);
            ribbonNodeBase.zIndex = 10;
            ribbonNodeBase.x = RandomInt(-720, 720);
            ribbonNodeBase.angle = RandomInt(360);

            const ribbonNode = ribbonNodeBase.getChildByName(`RibbonNode_${RandomInt(4)}`);
            ribbonNode.active = true;

            cc.tween(ribbonNodeBase)
                .by(3, { y: -1600, x: RandomInt(-100, 100), angle: RandomInt(-60, 60) })
                .start();
        }
    },

    drawCompleteLine(strokeIdx) {
        const drawGraphyNode = cc.find('Canvas/letterNodeBase/DrawMaskNode/DrawGraphyNodeBase');
        this.drawLines(drawGraphyNode, this.getPosListByStrokeIdx(strokeIdx));
    },

    drawLines(drawNode, posList) {
        const graph = drawNode.getComponent(cc.Graphics);
        posList.forEach((pos, idx) => {
            if (idx == 0) {
                graph.moveTo(pos.x, pos.y);
                return;
            }
            graph.lineTo(pos.x, pos.y);
        });
        graph.stroke();
    },

    onDrawStart(event) {
        const drawGraphyNode = cc.find('Canvas/letterNodeBase/DrawMaskNode/DrawGraphyNodeCurrent')
        const graph = drawGraphyNode.getComponent(cc.Graphics);
        const location = event.getLocation();
        const pos = drawGraphyNode.convertToNodeSpaceAR(location);
        graph.moveTo(pos.x, pos.y);
    },

    onDrawMove(event) {
        if (!this._moveCount) {
            this._moveCount = 0;
        }
        this._moveCount++;
        if (this._moveCount < 5) {
            return;
        }
        this._moveCount = 0
        const drawGraphyNode = cc.find('Canvas/letterNodeBase/DrawMaskNode/DrawGraphyNodeCurrent')
        const graph = drawGraphyNode.getComponent(cc.Graphics);
        const location = event.getLocation();
        const pos = drawGraphyNode.convertToNodeSpaceAR(location);
        graph.lineTo(pos.x, pos.y);
        graph.stroke();
    },

    onDrawEnd(event) {
        const drawGraphyNode = cc.find('Canvas/letterNodeBase/DrawMaskNode/DrawGraphyNodeCurrent')
        const graph = drawGraphyNode.getComponent(cc.Graphics);
        graph.clear();
    },

    addDrawListeners() {
        const drawGraphyNode = cc.find('Canvas/letterNodeBase/DrawMaskNode/DrawGraphyNodeCurrent');
        drawGraphyNode.on('touchstart', this.onDrawStart, this);
        drawGraphyNode.on('touchmove', this.onDrawMove, this);
        drawGraphyNode.on('touchend', this.onDrawEnd, this);
        drawGraphyNode.on('touchcancel', this.onDrawEnd, this);
    },

    checkPointInCircle(pos, center, radius) {
        const line = cc.v2(pos.x - center.x, pos.y - center.y);
        if (line.mag() > radius) {
            return false;
        }
        return true;
    },

    checkPointInRangeList(pos, rangePosList, radius) {
        let inRange = false;
        rangePosList.forEach((rangePos, idx) => {
            if (idx == 0) {
                return;
            }
            inRange = inRange || this.checkPointInRange(pos, rangePosList[idx - 1], rangePos, radius);
        });
        return inRange;
    },

    checkPointInRange(pos, rangePos1, rangePos2, radius) {
        const linePos2 = cc.v2(rangePos2.x - rangePos1.x, rangePos2.y - rangePos1.y);
        const linePos = cc.v2(pos.x - rangePos1.x, pos.y - rangePos1.y);
        const angle = linePos2.signAngle(linePos);
        const distance = linePos.mag() * Math.sin(angle);
        // 如果pos点到pos1与pos2连线的距离大于半径，则超出
        if (distance > radius || distance < - radius) {
            return false;
        }
        const projection = linePos.mag() * Math.cos(angle);
        // 如果pos1到pos点的向量到pos1到pos2的向量投影长度超出范围
        if (projection < - radius || projection > radius + linePos2.mag()) {
            return false;
        }

        return true;
    },

    async playEffect(name, loop, volume) {
        let musicComponent = cc.find('Canvas/MusicNode').getComponent('MusicNode');
        await musicComponent.playEffect(name, loop, volume);
    }
});
