import Cartoon from "./Cartoon"
import defaultData from "./defaultData"
import { jelly, randomSortByArr, showFireworks, RandomInt } from "./utils"
import { onHomeworkFinish } from "./util"


export default class GameLogic {
  g_cartoon = null;
  g_formData = null
  g_enableClick = true;
  g_canvas = null;
  g_systemOption = null;
  g_useDefaultData = false;
  g_curDragonDisplay = null;
  g_imageResList = [];
  g_audioResList = [];
  g_animaResList = [];
  g_properties = {};

  m_bubblesAll = [];
  m_currentQuestion = -1;
  m_currentQuestionIndex = -1;
  m_rightAns = 0;
  m_stopMoving = false;
  m_maxScale = 1;
  m_isPlayingAudio = false;

  constructor(formData, systemOption) {
    this.g_systemOption = systemOption;
    this.g_imageResList = this.g_systemOption.imageResList;
    this.g_audioResList = this.g_systemOption.audioResList;
    this.g_animaResList = this.g_systemOption.animaResList;
    this.g_properties = this.g_systemOption.properties;

    this.g_canvas = cc.find('Canvas');
    this.g_cartoon = new Cartoon(cc);

    if(!formData.contentObj.dataArray || formData.contentObj.dataArray.length == 0) {
      this.g_useDefaultData = true;
      this.g_formData = defaultData
      console.warn("User default data =>", this.g_formData)
    } else {
      this.g_formData = formData.contentObj;
      console.log("User saved data =>", this.g_formData)
    }
    
    this.disableClick("Sysyem_init")
    this.g_cartoon.initLocalAudios([])
    // this.initMaskLayer();
    
    this.initNodes();
    this.initBg();
    this.resetPosition();
    this.addEventLinstener();

    if(this.nextQuestion()) {
      if(this.g_formData.dataArray.length>1) {
        this.initStar()
      }
      this.g_cartoon.initDragEvent()
      this.enableClick("Sysyem_init");
      // this.hideAir();
      this.systemTick();
      this.releaseBubbles()
    }

    cc.view.setResizeCallback(() => {
      this.initBg();
      this.resetPosition();
   });
  }

  initNodes() {
    this.g_cartoon.initCartoonElement("background", this.g_cartoon.getNodeByPath(`Canvas/background/background`));
    this.g_cartoon.initCartoonElement("audio", this.g_cartoon.getNodeByPath(`Canvas/speaker`));
    this.g_cartoon.initCartoonElement("back", this.g_cartoon.getNodeByPath(`Canvas/btn_back/btn_back`));
    this.g_cartoon.initCartoonElement("restart", this.g_cartoon.getNodeByPath(`Canvas/btn_restart/btn_restart`));
    this.g_cartoon.initCartoonElement("lion", this.g_cartoon.getNodeByPath(`Canvas/lion/DB`));
  }

  initBg() {
    const bgMain = this.g_cartoon.getCartoonElement("background")
    bgMain.node.width = cc.winSize.width
    bgMain.node.height = cc.winSize.height
  }

  resetPosition() {
    let node = this.g_cartoon.getNodeByPath(`Canvas/speaker`)
    node.x = -cc.winSize.width / 2 + 100
    node.y = -cc.winSize.height / 2 + 100
    this.m_bubblesAll.push({node: node, move: {
      r: 50, x: node.x, y: node.y, vX: 0, vY: 0, enable: true, fix: true, scale: 1, id: 999, lastId: null
    }})

    node = this.g_cartoon.getNodeByPath(`Canvas/btn_back`)
    node.x = -cc.winSize.width / 2 + 60
    node.y = cc.winSize.height / 2 - 50
    this.m_bubblesAll.push({node: node, move: {
      r: 40, x: node.x, y: node.y, vX: 0, vY: 0, enable: true, fix: true, scale: 1, id: 998, lastId: null
    }})

    node = this.g_cartoon.getNodeByPath(`Canvas/btn_restart`)
    node.x = 0
    node.y = 20
    // this.m_bubblesAll.push({node: node, move: {
    //   r: 90, x: node.x, y: node.y, vX: 0, vY: 0, enable: true, fix: true, scale: 1
    // }})

    node = this.g_cartoon.getNodeByPath(`Canvas/lion`)
    node.x = cc.winSize.width / 2 - 150
    node.y = -cc.winSize.height / 2 + 170
    this.m_bubblesAll.push({node: node, move: {
      r: 90, x: node.x, y: node.y, vX: 0, vY: 0, enable: true, fix: true, scale: 1, id: 997, lastId: null
    }})

    node = this.g_cartoon.getNodeByPath(`Canvas/star_group`)
    node.y = -cc.winSize.height/2 - 140
  }

  addEventLinstener() {
    const btn_restart = this.g_cartoon.getCartoonElement("restart")
    this.g_cartoon.subscribeTouchEvent("restart", _=>{
      if(!this.g_enableClick) {
        return
      }
      this.g_cartoon.tweenChange(btn_restart.node, {scaleX: 0, scaleY: 0}, 0.2, _=>{
        this.enableClick("Restart")
        this.restartGame();
      })
    })

    const btn_lion = this.g_cartoon.getCartoonElement("lion")
    btn_lion.play = () => {
      let a = btn_lion.node.getComponent(dragonBones.ArmatureDisplay)
      a.playAnimation('play', 1);
      setTimeout(() => {
        a.playAnimation('normal', -1);
      }, 3200);
    }


    // 音频播放
    const btn_speaker = this.g_cartoon.getCartoonElement("audio");
    intervalId = null;
    btn_speaker.startAni = () => {
      const speakers = [];
      for (let index = 3; index >= 0; index--) {
        const node = cc.find(`Canvas/speaker/speaker_${index}`);
        speakers.push(node);
      }

      const _node = cc.find(`Canvas/speaker/default`);
      let currentIdx = 0;
      let num = speakers.length;
      intervalId = setInterval(() => {
        _node.active = false;
        speakers.forEach((node, index) => {
          node.active = false;
        })
        currentIdx++;
        speakers[currentIdx%num].active = true;
      }, 200);
    }

    btn_speaker.stopAni = ()=>{
      if(intervalId) {
        clearTimeout(intervalId)
        intervalId = null;
      }

      for (let index = 3; index >= 0; index--) {
        const node = cc.find(`Canvas/speaker/speaker_${index}`);
        node.active = false;
      }

      const node = cc.find(`Canvas/speaker/default`);
      node.active = true;
    }

    btn_speaker.showHide = (status) => {
      if(status) {
        btn_speaker.node.active = true;
      } else {
        btn_speaker.node.active = false;
      }
    }

    this.g_cartoon.subscribeTouchEvent("audio", _=>{
      if(!this.g_enableClick) {
        return
      }
      if(this.m_isPlayingAudio) {
        this.m_isPlayingAudio = false;
        btn_speaker.stopAni();
        this.g_cartoon.stopAllAudio()
      } else {
        this.m_isPlayingAudio = true;
        btn_speaker.startAni()
        this.g_cartoon.playAudio(this.g_formData.dataArray[this.m_currentQuestionIndex].audio_url1, ()=>{
          this.m_isPlayingAudio = false;
          btn_speaker.stopAni();
        })
      }
    })
  }

  endGame() {
    const btn_restart = this.g_cartoon.getCartoonElement("restart")
    this.disableClick("EndGame")
    this.g_cartoon.playAudio("撒花")
    this.fireworks();
    this.g_cartoon.tweenChange(btn_restart.node, {scale: 1}, 0.3, _=>{
      this.enableClick("EndGame");
      onHomeworkFinish();
    })
  }

  restartGame() {
    this.m_currentQuestion = -1;
    this.m_currentQuestionIndex = -1;
    this.m_rightAns = 0;
    this.m_stopMoving = false;
    if(this.g_formData.dataArray.length>1) {
      this.cleanScore();
      this.initStar();
    }
    this.nextQuestion()
    this.releaseBubbles();
  }

  systemTick() {
    let maxNum = this.m_bubblesAll.length;
    let maxX = cc.winSize.width / 2;
    let maxY = cc.winSize.height / 2;
    setInterval(()=>{
      this.m_bubblesAll.forEach((bubble, index_x)=>{
        if(!this.m_stopMoving && bubble.move.enable && !bubble.move.fix) {
          bubble.move.x += bubble.move.vX
          bubble.move.y += bubble.move.vY

          if (bubble.move.x >= maxX - bubble.move.r * this.m_maxScale) {
            bubble.move.x = maxX - bubble.move.r * this.m_maxScale;
            bubble.move.vX = -bubble.move.vX;
            // bubble.move.lastId = null;
          }
          if (bubble.move.x <= -maxX + bubble.move.r * this.m_maxScale) {
            bubble.move.x = -maxX + bubble.move.r * this.m_maxScale;
            bubble.move.vX = -bubble.move.vX;
            // bubble.move.lastId = null;
          }
          if (bubble.move.y >= maxY - bubble.move.r * this.m_maxScale) {
            bubble.move.y = maxY - bubble.move.r * this.m_maxScale;
            bubble.move.vY = -bubble.move.vY;
            // bubble.move.lastId = null;
          }
          if (bubble.move.y <= -maxY + bubble.move.r * this.m_maxScale) {
            bubble.move.y = -maxY + bubble.move.r * this.m_maxScale;
            bubble.move.vY = -	bubble.move.vY;
            // bubble.move.lastId = null;
          }
  
          bubble.node.x = bubble.move.x
          bubble.node.y = bubble.move.y
          
          for (var j = 0; j < maxNum; j++) {
            if (j !== index_x) {
              if (Math.round(Math.pow(this.m_bubblesAll[index_x].node.x - this.m_bubblesAll[j].node.x, 2) +
                Math.pow(this.m_bubblesAll[index_x].node.y - this.m_bubblesAll[j].node.y, 2)) <=
                Math.round(Math.pow(this.m_bubblesAll[index_x].move.r * this.m_maxScale + this.m_bubblesAll[j].move.r * this.m_maxScale, 2))) {
  
                var tempX = this.m_bubblesAll[index_x].move.vX;
                var tempY = this.m_bubblesAll[index_x].move.vY;
                if(this.m_bubblesAll[index_x].move.lastId != this.m_bubblesAll[j].move.id) {
                  this.m_bubblesAll[index_x].move.lastId = this.m_bubblesAll[j].move.id;
                  this.m_bubblesAll[j].move.lastId = this.m_bubblesAll[index_x].move.id;
                  if(this.m_bubblesAll[j].move.fix) {
                    this.m_bubblesAll[index_x].move.vX = -this.m_bubblesAll[index_x].move.vX;
                    this.m_bubblesAll[index_x].move.vY = -this.m_bubblesAll[index_x].move.vY;
                  } else {
                    this.m_bubblesAll[index_x].move.vX = this.m_bubblesAll[j].move.vX;
                    this.m_bubblesAll[j].move.vX = tempX;
                    this.m_bubblesAll[index_x].move.vY = this.m_bubblesAll[j].move.vY;
                    this.m_bubblesAll[j].move.vY = tempY;
                  }
                }
              }
            }
          }
        }
      })
    }, 30)
  }

  initStar() {
    const starGroup = this.g_cartoon.getNodeByPath(`Canvas/star_group`)
    const scoreGroup = this.g_cartoon.getNodeByPath(`Canvas/score_group`)
    const scoreAll = []
    const num = this.g_formData.dataArray.length;
    if(num>9) {
      scoreGroup.scale = 0.6
    }

    for(let i=0; i<this.g_formData.dataArray.length; i++) {
      const star = cc.instantiate(this.g_systemOption.prefabs.star);
      star.parent = starGroup;
      this.g_cartoon.setCartoonElement(`star_${i}`, star)

      const score = cc.instantiate(this.g_systemOption.prefabs.score);
      score.parent = scoreGroup;
      this.g_cartoon.setCartoonElement(`score_${i}`, score)
      scoreAll.push(score)
    }

    let startY = (scoreAll.length * 66) / 2;
    scoreAll.forEach((score, index)=>{
      score.y = startY
      startY -= 66 + 10
    })
  }

  addStar(callback) {
    if(this.g_formData.dataArray.length<=1) {
      if(callback) {
        callback()
      }
      return;
    }

    const star = this.g_cartoon.getCartoonElement(`star_${this.m_currentQuestionIndex}`);
    const score = this.g_cartoon.getCartoonElement(`score_${this.m_currentQuestionIndex}`);
    const score_b = score.node.getChildByName("b")
    const starGroup = this.g_cartoon.getNodeByPath(`Canvas/star_group`);
    const scoreGroup = this.g_cartoon.getNodeByPath(`Canvas/score_group`);

    this.g_cartoon.playAudio("大星")
    this.g_cartoon.tweenChange(star.node, {y: 380}, 1, ()=>{
      setTimeout(() => {
        const worldPos = starGroup.convertToWorldSpaceAR(cc.v2(star.node.x, star.node.y));
        const localPos = scoreGroup.convertToNodeSpaceAR(cc.v2(worldPos.x, worldPos.y));

        star.node.parent = scoreGroup;
        star.node.x = localPos.x;
        star.node.y = localPos.y;

        this.g_cartoon.tweenChange(star.node, {x: score.node.x, y: score.node.y, scale: 0.2}, 0.8, _=>{
          score_b.active = true;
          score_b.scaleX = 0;
          score_b.scaleY = 0;
          star.node.destroy();
          this.g_cartoon.tweenChange(score_b, {scale: 1}, 0.1, _=>{
            if(callback) {
              callback()
            }
          })
        })
      }, 1000);
    })
  }

  nextQuestion() {
    this.m_currentQuestionIndex++;
    // 检查全局音乐
    const btn_speaker = this.g_cartoon.getCartoonElement("audio");
    if(this.g_formData.dataArray[this.m_currentQuestionIndex] && this.g_formData.dataArray[this.m_currentQuestionIndex].audio_url1) {
      btn_speaker.showHide(true);
    } else {
      btn_speaker.showHide(false);
    }

    if(this.m_currentQuestionIndex >= this.g_formData.dataArray.length) {
      btn_speaker.showHide(false);
      return false;
    }

    const btn_lion = this.g_cartoon.getCartoonElement("lion")
    btn_lion.play();

    const rightNum = this.g_formData.dataArray[this.m_currentQuestionIndex].correct.length;
    const wrongNum = this.g_formData.dataArray[this.m_currentQuestionIndex].incorrect.length;
    const bubbleNum = rightNum + wrongNum;
    const bubbleParent = this.g_cartoon.getNodeByPath(`Canvas/bubble_group`)

    const scale = bubbleNum<=15?1:0.7;
    for(let i=0; i<bubbleNum; i++) {
      const bubble = cc.instantiate(this.g_systemOption.prefabs.bubble);
      bubble.parent = bubbleParent;
      this.m_maxScale = scale;
      let bubb = this.g_cartoon.setCartoonElement(`bubble_${i}`, bubble)
      bubb.boom = () => {
        let _p = []
        bubb.node.getChildByName("image_container").active = false;
        bubb.node.getChildByName("text_container").active = false;
        bubb.node.getChildByName("image").active = false;
        for(let i=0; i<8; i++) {
          _p.push(new Promise((resolve,reject) => {
            setTimeout(() => {
              if(i>1) {
                bubb.node.getChildByName(`bubble_ani (${i})`).active = false;
              }
              bubb.node.getChildByName(`bubble_ani (${i+1})`).active = true;
              resolve()
            }, 30 * i);
          }))
        }
        return Promise.all(_p)
      }

      bubb.shake = (callback) => {
        let _x = bubb.node.x;
        bubb.node.getChildByName("color").active = true;
        this.g_cartoon.tweenChange(bubb.node, {x: _x-5}, 0.05, _=>{
          this.g_cartoon.tweenChange(bubb.node, {x: _x+5}, 0.1, _=>{
            this.g_cartoon.tweenChange(bubb.node, {x: _x-5}, 0.1, _=>{
              this.g_cartoon.tweenChange(bubb.node, {x: _x}, 0.05, _=>{
                bubb.node.getChildByName("color").active = false;
                if(callback) {
                  callback()
                }
              })
            })
          })
        })
      }
    }

    for(let i=0; i<bubbleNum; i++) {
      const bubble = this.g_cartoon.getCartoonElement(`bubble_${i}`)
      let item = i<rightNum?this.g_formData.dataArray[this.m_currentQuestionIndex].correct[i]:this.g_formData.dataArray[this.m_currentQuestionIndex].incorrect[i-rightNum]
      bubble.isRight = i<rightNum?true:false;
      bubble.move = {
        r: 95, x: 0, y: 0, vX: 1, vY: 1.3, enable: false, fix: false, scale: 0, id: i, lastId: null
      }

      if(item.type == 'Image') {
        let image = bubble.node.getChildByName("image_container")
        image.active = true;
        this.g_cartoon.mountImageToNode(item.image_url, image, 120, 120)
      } else {
        let text = bubble.node.getChildByName("text_container")
        text.active = true;
        var tLabel = text.getComponent(cc.Label)
        tLabel.string = item.text
      }

      bubble.node.scaleX = 0;
      bubble.node.scaleY = 0;
      bubble.node.x = 0;
      bubble.node.y = -2000;
      this.m_bubblesAll.push(bubble)

      this.g_cartoon.subscribeTouchEvent(`bubble_${i}`, ()=>{
        if(!this.g_enableClick) {
          return
        }
        this.disableClick(`bubble_${i}`)
        let clickBubble = this.g_cartoon.getCartoonElement(`bubble_${i}`)
        const btn_speaker = this.g_cartoon.getCartoonElement("audio");
        this.g_cartoon.stopAllAudio();
        btn_speaker.stopAni();
        
        if(bubble.isRight) {
          this.g_cartoon.playAudio("正确");
          clickBubble.boom().then(_=>{
            bubble.node.active = false;
            this.m_rightAns++;
            if(this.m_rightAns>=rightNum) {
              this.m_stopMoving = true;
              this.addStar(_=>{
                this.destroyBubbles().then(_=>{
                  if(!this.nextQuestion()) {
                    this.endGame()
                  } else {
                    this.releaseBubbles();
                  }
                }) 
              })
            } else {
              this.enableClick("Destroy")
            }
          })
        } else {
          this.m_stopMoving = true;
          clickBubble.shake(_=>{
            this.m_stopMoving = false;
            this.enableClick(`bubble_${i}`)
          })
          this.g_cartoon.playAudio("错误泡泡爆炸");
        }
      }, true)
    }
    return true;
  }

  destroyBubbles() {
    this.disableClick("DestroyBubbles")
    this.save = []
    this.m_bubblesAll.forEach(item=>{
      if(!item.move.fix) {
        this.g_cartoon.tweenChange(item.node, {y: item.node.y + cc.winSize.height}, 1, ()=>{
          item.node.destroy()
        })
      } else {
        this.save.push(item)
      }
    })
    this.m_bubblesAll = this.save;

    return new Promise((resolve, reject)=>{
      setTimeout(() => {
        resolve();
        this.enableClick("DestroyBubbles")
      }, 1100);
    })
  }

  releaseBubbles() {
    const pos = [
      [
        {x: -280, y: 24}, {x: 0, y: 5}, {x: -172, y: -172}, {x: 34, y: -220}, {x: 235, y: -37},
        {x: -119, y: 191}, {x: 287, y: 186}, {x: -320, y: 252}, {x: -486, y: -45}, {x: 452, y: 30},
        {x: -380, y: -247}, {x: 87, y: 252}, {x: 258, y: -244}, {x: 505, y: 236}, {x: -500, y: 160},
      ],
      [
        {x: -328.374, y: 110.355}, {x: 216.211, y: 7.251}, {x: -121.121, y: -134.579}, {x: 119.35, y: -114.68}, {x: -37.682, y: 152.075},
        {x: 0, y: -13.458}, {x: 297.835, y: 125.057}, {x: -277.234, y: -48.448}, {x: -201.869, y: 180.336}, {x: 17.41, y: -217.592},
        {x: -427.963, y: 2.691}, {x: 270.477, y: -136.746}, {x: 111.701, y: 117.084}, {x: -423.925, y: 218.018}, {x: -259.738, y: -215.326},
        {x: 216.778, y: 263.332}, {x: -157.458, y: 43.066}, {x: 375.715, y: -17.987}, {x: -563.888, y: -67.289}, {x: -91.514, y: 283.963},
        {x: 456.774, y: 131.414}, {x: -557.159, y: 275.888}, {x: 149.026, y: -275.623}, {x: -405.084, y: -141.308}, {x: 385.252, y: 271.279},
        {x: -549.084, y: 106.318}, {x: 299.425, y: -283.412}, {x: -410.468, y: -278.579}, {x: 64.598, y: 279.925}, {x: 558.494, y: 242.67},
        {x: -292.038, y: 282.617}, {x: -131.889, y: -283.962}, {x: 547.368, y: 2.675},
      ]
    ]
    
    let scaleIndex = this.m_bubblesAll.length<=15?0:1;
    let startx = cc.winSize.width / 2
    let starty = -cc.winSize.height / 2
    const vArr = [
      {vX: -0.5, vY: 0.5 }, {vX: 0.5, vY: -0.5 }, {vX: -0.5, vY: -0.5 }, {vX: 0.5, vY: 0.5 }
    ]
    let pAll = []
    let count = 0;
    this.m_bubblesAll.forEach((bubble, index) => {
      if(!bubble.move.fix) {
        bubble.node.x = startx;
        bubble.node.y = starty;
        bubble.node.scale = 0;
        let _p = new Promise((resolve, reject) => {
          bubble.move.x = pos[scaleIndex][count].x
          bubble.move.y = pos[scaleIndex][count].y
          bubble.move.vX = vArr[count%4].vX
          bubble.move.vY = vArr[count%4].vY
          count++;
          setTimeout(() => {
            this.g_cartoon.playAudio("出现")
            this.g_cartoon.tweenChange(bubble.node, {scale: this.m_maxScale, x: bubble.move.x, y: bubble.move.y}, 3, _=>{
              resolve()
            })
          }, 200 * index);
        })
        pAll.push(_p)
      }
    })
    Promise.all(pAll).then(_=>{
      this.m_stopMoving = false;
      this.m_bubblesAll.forEach(bubble=>{
        if(!bubble.move.fix) {
          bubble.move.enable = true;
        }
      })
    })
  }

  cleanScore() {
    const scoreGroup = this.g_cartoon.getNodeByPath(`Canvas/score_group`);
    const scoreAll = []
    for(let i=0; i<this.g_formData.dataArray.length; i++) {
      const _score = this.g_cartoon.getCartoonElement(`score_${i}`)
      _score.node.destroy();
    }
  }

  fireworks() {
    this.showFireworks(
      cc.find('Canvas/background/background'),
        cc.find('Canvas/RibbonNodeBase').children,
        cc.v2(0, -cc.winSize.height/2), cc.v2(0, cc.winSize.height * 0.85), 200, 200, 4
    );
    this.showFireworks(
      cc.find('Canvas/background/background'),
        cc.find('Canvas/RibbonNodeBase').children,
        cc.v2(-600, -cc.winSize.height/2), cc.v2(200, cc.winSize.height * 0.85), 200, 200, 4
    );
    this.showFireworks(
        cc.find('Canvas/background/background'),
        cc.find('Canvas/RibbonNodeBase').children,
        cc.v2(600, -cc.winSize.height/2), cc.v2(-200, cc.winSize.height * 0.85), 200, 200, 4
    );
  }

  asyncTweenBy(node, duration, obj, ease = undefined) {
    return new Promise((resolve, reject) => {
      cc.tween(node)
        .by(duration, obj, ease)
        .call(() => {
          resolve();
        })
        .start();
    });
  }
  
  
  rabbonFall(node) {
    const time = 1 + Math.random();
    const offsetX = RandomInt(-200, 200) * time;
    this.asyncTweenBy(node, time, { x: offsetX, angle: offsetX * 60 / 200 }).then(_ => {
      this.rabbonFall(node);
    })
  }
  
  asyncDelay(time) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve();
      }, time * 1000);
    })
  }
  
  showFireworks(baseNode, nodeList, pos = cc.v2(0, 0), side = cc.v2(0, 100), range = 50, number = 100) {
    new Array(number).fill(' ').forEach((_, i) => {
      let rabbonNode = new cc.Node();
      rabbonNode.parent = baseNode;
      rabbonNode.x = pos.x;
      rabbonNode.y = pos.y;
      rabbonNode.angle = 60 * Math.random() - 30;
  
      let node = cc.instantiate(nodeList[RandomInt(nodeList.length)]);
      node.parent = rabbonNode;
      node.active = true;
      node.x = 0;
      node.y = 0;
      node.angle = 0;
  
      const rate = Math.random();
      const angle = Math.PI * (Math.random() * 2 - 1);
  
      this.asyncTweenBy(rabbonNode, 0.3, {
        x: side.x * rate + Math.cos(angle) * range * rate,
        y: side.y * rate + Math.sin(angle) * range * rate
      }, {
        easing: 'quadIn'
      }).then(_ => {
        cc.tween(rabbonNode)
          .by(8, { y: -2000 })
          .start();
  
        this.rabbonFall(rabbonNode);
  
        this.asyncDelay(Math.random()).then(_ => {
          cc.tween(node)
            .by(0.15, { x: -10, angle: -10 })
            .by(0.3, { x: 20, angle: 20 })
            .by(0.15, { x: -10, angle: -10 })
            .union()
            .repeatForever()
            .start();
  
          cc.tween(rabbonNode)
            .delay(5)
            .to(0.3, { opacity: 0 })
            .call(() => {
              node.stopAllActions();
              node.active = false;
              node.parent = null;
              node = null;
            })
            .start();
        })
      })

    });
  }









  // Common function
  maskLayer = null;
  initMaskLayer() {
    const parentNode = this.g_cartoon.getNodeByPath("MaskLayer")
    parentNode.x = cc.winSize.width / 2
    parentNode.y = cc.winSize.height / 2
    const bNode = this.getSprNodeByResName('black');
    bNode.scaleX = cc.winSize.width / bNode.width;
    bNode.scaleY = cc.winSize.height / bNode.height;
    bNode.x = 0
    bNode.y = 0
    parentNode.setSiblingIndex(255)
    bNode.parent = cc.find('MaskLayer');
    bNode.active = false;
    this.maskLayer = bNode;
  }

  showMaskLayer(cb) {
    this.maskLayer.opacity = 0;
    this.maskLayer.active = true;
    cc.tween(this.maskLayer)
      .to(0.5, {opacity: 255}, {easing: 'sineOut'})
      .call(() => {
        cb()
      })
      .start()
  }

  hideMaskLayer(cb = null) {
    this.maskLayer.opacity = 255;
    cc.tween(this.maskLayer)
    .to(0.5, {opacity: 0}, {easing: 'sineIn'})
    .call(() => {
      this.maskLayer.active = false;
      if (cb) {
        cb()
      }
    })
    .start()
  }

  startTwinkleAnime(speakerBase, path, len, speed = 0.2) {
    const speakers = [];
    for (let index = 0; index < len; index++) {
      const node = cc.find(`${path}_${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(speed)
      .union()
      .repeatForever()
      .start();
  }

  stopTwinkleAnime(speakerBase, path, len) {
    speakerBase.stopAllActions();
    for (let index = 0; index < len; index++) {
      const node = cc.find(`${path}_${index}`);
      node.active = false;
    }
    const node0 = cc.find(`${path}_0`);
    node0.active = true;
  }

  // Common API
  getSprNodeByUrl(resName) {
    return new Promise((resolve, reject) => {
      cc.assetManager.loadRemote(url, (err, img) => {
        const node = new cc.Node();
        const spriteFrame = new cc.SpriteFrame(img)
        node.addComponent(cc.Sprite).spriteFrame = sf;
        resolve(node)
      })
    })
  }

  getSprNodeByResName(resName) {
    const sf = cc.find('Canvas/res/img/' + resName).getComponent(cc.Sprite).spriteFrame;
    const node = new cc.Node();
    node.addComponent(cc.Sprite).spriteFrame = sf;
    return node;
  }



  // Sysyem function
  hideAir() {
    if (window && window["air"]) {
      window["air"].hideAirClassLoading();
    }
    cc.debug.setDisplayStats(false);
  }

  checkClickEnable(eventName) {
    if(this.g_enableClick) {
      return true;
    } else {
      // console.log(`Click disabled by event [${this.g_disableClickEventName}]!`)
      return false;
    }
  }

  disableClick(eventName) {
    this.g_enableClick = false;
    this.g_disableClickEventName = eventName
    // console.log(`Click disabled! [${eventName}]`)
  }

  enableClick(eventName) {
    this.g_enableClick = true;
    this.g_disableClickEventName = ""
    // console.log(`Click enabled! [${eventName}]`)
  }

}
