import {
  Component,
  ElementRef,
  ViewChild,
  OnInit,
  Input,
  OnDestroy,
  HostListener
} from "@angular/core";

import {
  MySprite,
  getMinScale,
  ShapeRect,
  ShapeCircle,
  tweenChange,
  randomSortByArr,
  Label,
  showPopParticle,
  moveItem,
  removeItemFromArr,
  rotateItem,
  hideItem,
  showItem,
  ShapeRectNew
} from "./Unit";

import { localImages, localAudios } from "./resources";

import { Cartoon } from './Cartoon'
import { Subject } from "rxjs";
import { debounceTime, map } from "rxjs/operators";
import * as _ from "lodash";
import TWEEN from "@tweenjs/tween.js";

const defauleFormData = {}
const position = {
  "4": [{x:640,y:140},{x:920,y:140},{x:640,y:387},{x:920,y:387}],
  "6": [{x:604,y:184},{x:791,y:184},{x:983,y:184},{x:604,y:364},{x:791,y:364},{x:983,y:364}],
  "9": [{x:616,y:144},{x:808,y:144},{x:987,y:144},{x:616,y:298},{x:808,y:298},{x:987,y:298},{x:616,y:447},{x:808,y:447},{x:987,y:447}]
}
@Component({
  selector: "app-play",
  templateUrl: "./play.component.html",
  styleUrls: ["./play.component.scss"]
})

export class PlayComponent implements OnInit, OnDestroy {
  
  g_cartoon = new Cartoon()

  // ------------  全局数据  ------------
  g_stage; //中心舞台
  g_enableMapDown = true; // 触摸使能
  g_enableMapUp = true; // 抬起使能
  g_enableMapMove = true; // 移动ss使能
  g_canvasLeft;
  g_canvasTop;
  g_animationId: any;
  g_mapScale = 1; // 缩放比例
  g_KEY = "DataKey_PicturePuzzle";
  g_canvasWidth = 1280;
  g_canvasHeight = 720;
  g_canvasBaseW = 1280;
  g_canvasBaseH = 720;
  g_winResizeEventStream = new Subject();
  g_clickX; // 点击坐标 X
  g_clickY; // 点击坐标 Y
  g_ctx; // canvas 实例
  g_data; // 数据
  g_formData; // 核心表单数据
  g_teacherFlag = false; // 默认角色
  g_currentUser;
  // ------------------------------------

  // ------------  私有数据  ------------
  m_mapDownQueue = {} //按下事件处理队列
  m_mapUpQueue = {} //抬起事件处理队列
  m_mapMoveQueue = {} //移动事件处理队列
  m_endPageArr;
  m_showPetalFlag;
  m_elementPetalArr;
  m_showElementPetalFlag;
  m_PetalImage = "petal_"  // 飘落动画
  m_renderArr // 渲染队列
  // ------------------------------------
  
  // ------------  游戏逻辑数据  ------------
  m_puzzleCards_id = []
  m_currentQuestionIndex = 0;
  m_currentMovedCardIndex = -1;
  m_totalQusetion = 0;
  // ------------------------------------


  // ------------  消息  ------------
  // ------------------------------------


  // ------------  调试变量  ------------
  g_EnableStageRuler = true; // 使能舞台背景格尺
  g_ForceChangeDefaultRole = false // 强制当前角色为默认角色
  g_EnableTestSendEvent = false // 发送模拟Web数据
  // ------------------------------------


  // 当数据加载完毕后，执行
  systemReady(){
    this.initGame()
  }
  
  // 屏幕尺寸变化后执行
  handleScreenResize(){

  }

  // 映射预加载图片[网路]资源 返回包含图片路径的数组
  mapToImageArray(contentObj){
    let array = []

    array.push(this.g_data.contentObj.bgItem.url)
    let hotZoneItemArr = contentObj.hotZoneItemArr;
    if(hotZoneItemArr){
      hotZoneItemArr.forEach(item => {
        array.push(item.media.image_url)
      });
    }
    return array
  }

  // 映射预加载音频[网路]资源 返回包含音频路径的数组
  mapToAduioArray(contentObj){
    let array = []
    let hotZoneItemArr = contentObj.hotZoneItemArr;
    if(hotZoneItemArr){
      hotZoneItemArr.forEach(item => {
        if(item.media.audio_url){
          array.push(item.media.audio_url)
        }
      });
    }
    return array
  }

  restartGame() {
    this.cleanGameVar()
    this.initGame()
  }


  // ------------------------------------------------------------------------------
  // 游戏核心处理区
  // ------------------------------------------------------------------------------
  // ------------------------------------------------------------------------------
  // 
  // 
  // 
  // ------------------------------------------------------------------------------
  // ------------------------------------------------------------------------------

  initGame(){
    this.initAnswerSheet()
    this.showAnswer()
    this.initPuzzles()
    setTimeout(()=>{
      this.startGame()
    }, 1000)
  }

  startGame(){
    this.g_cartoon.getCartoonElement("AnswerSheet").moveLeft(()=>{
      this.showPuzzleCards();
    })
  }

  nextQusetion(){
    this.m_currentQuestionIndex++;
    if(this.m_currentQuestionIndex==this.m_totalQusetion){
      this.finisedAll()
    }
  }

  finisedAll(){
    this.g_cartoon.getCartoonElement("AnswerSheet").moveMiddle()
  }

  initAnswerSheet(){
    let element = this.g_cartoon.createCartoonElement("AnswerSheet", "MySprite");
    element.ref.init(this.g_cartoon.images.get("answer-sheet"));
    let leftX = this.g_cartoon.getOrigin().x + 155*this.g_mapScale + (element.ref.width*this.g_mapScale)/2;
    let rightX = this.g_cartoon.getOrigin().y + 75*this.g_mapScale + (element.ref.height*this.g_mapScale)/2;
    let middleX = this.g_canvasWidth / 2;
    let middleY = this.g_canvasHeight / 2;
    element.ref.x = middleX;
    element.ref.y = middleY;

    element.ref.setScaleXY(this.g_mapScale)

    let element_t = this.g_cartoon.createCartoonElement("quertion-image-container", "MySprite")
    element_t.ref.init(this.g_cartoon.images.get(this.g_formData.bgItem.url))
    element_t.contentScale = Math.min((200 / element_t.ref.width), (300 / element_t.ref.height))
    element_t.originX = -(element_t.ref.width)/2
    element_t.originY = -(element_t.ref.height)/2
    element_t.ref.x = 0
    element_t.ref.y = 0
    element_t.ref.setScaleXY( element_t.contentScale )

    element.moveLeft = (callback)=>{
      tweenChange(element.ref, {x:leftX , y:rightX}, 0.5, ()=>{
        callback && callback()
      })
    }

    element.moveMiddle = ()=>{
      tweenChange(element.ref, {x:middleX , y:middleY}, 0.5)
    }

    element.ref.addChild(element_t.ref)
    this.render(element.ref)
  }


  showAnswer(){
    let hotZoneItemArr = this.g_formData.hotZoneItemArr
    let parent = this.g_cartoon.getCartoonElement("quertion-image-container")
    hotZoneItemArr.forEach( (item,index) => {
      let element = this.g_cartoon.createCartoonElement("quertion-image-puzzle-"+index, "MySprite")
      element.ref.init(this.g_cartoon.images.get(item.media.image_url))
      element.ref.scaleX = item.rect.width / (element.ref.width*parent.contentScale)
      element.ref.scaleY = item.rect.height / (element.ref.height*parent.contentScale)
      element.ref.x = parent.originX + item.rect.x/parent.contentScale + (element.ref.width*element.ref.scaleX)/2
      element.ref.y = parent.originY + item.rect.y/parent.contentScale + (element.ref.height*element.ref.scaleY/2)
      this.g_cartoon.getCartoonElement("quertion-image-container").ref.addChild(element.ref)
      element.cardIndex = index;
      element.ref.alpha = 0;

      element.show = ()=>{
        tweenChange(element.ref, { alpha:1 } , 0.1)
      }

      this.subscribeMapUpEvent("quertion-image-puzzle-"+index, ()=>{
        if(this.m_currentMovedCardIndex == -1){
          return;
        }
        if(this.checkSelectedZone(index)){
          let rect = element.ref.getBoundingBox()
          element.show()
          this.g_cartoon.getCartoonElement(`puzzle-card-${index}-container`).enableMove = false;
          this.g_cartoon.getCartoonElement(`puzzle-card-${index}-container`).isPlaced = true;
          this.g_cartoon.getCartoonElement(`puzzle-card-${index}-container`).moveTo(rect.x + rect.width/2, rect.y + rect.height/2)
          this.g_cartoon.getCartoonElement(`puzzle-card-${index}-container`).hide()
          this.g_enableMapUp = true;
        }else{
          this.g_cartoon.getCartoonElement(`puzzle-card-${this.m_currentMovedCardIndex}-container`).back()
        }  
      })
    });
  }

  initPuzzles(){
    let hotZoneItemArr = this.g_formData.hotZoneItemArr
    let maxNum = "9"
    let size = "small"
    if(hotZoneItemArr.length<=4){
      maxNum = "4"
      size = "large"
    }else if(hotZoneItemArr.length<=6){
      maxNum = "6"
      size = "middle"
    }else if(hotZoneItemArr.length<=9){
      maxNum = "9"
      size = "small"
    }
    this.m_totalQusetion = hotZoneItemArr.length;
    hotZoneItemArr.forEach( (item,index) => {
      this.render(this.createPuzzleCard(index, position[maxNum][index].x, position[maxNum][index].y, size, item).ref)
      this.addToCardList(`puzzle-card-${index}-container`)
    });
  }


  createPuzzleCard(index:number, x:number, y:number, size:string, content={media:{image_url:""}}){
    let containerSize_Width = 0;
    let containerSize_Height = 0;
    let cardSize_Width = 0;
    let cardSize_Height = 0;
    switch(size){
      case "large": 
        containerSize_Width = 193;
        containerSize_Height = 193;
        cardSize_Width = 140;
        cardSize_Height = 140;
        break;
      case "middle": 
        containerSize_Width = 148;
        containerSize_Height = 148;
        cardSize_Width = 125;
        cardSize_Height = 125;
        break;
      case "small": 
        containerSize_Width = 133;
        containerSize_Height = 133;
        cardSize_Width = 120;
        cardSize_Height = 120;
        break;
      default: 
        containerSize_Width = 193;
        containerSize_Height = 193;
        cardSize_Width = 110;
        cardSize_Height = 110;
        break;
    }
    let container = this.g_cartoon.createCartoonElement(`puzzle-card-${index}-container`, "MySprite")
    container.ref.init(this.g_cartoon.images.get("baiquan"))
    let container_sx = (containerSize_Width / container.ref.width)*this.g_mapScale
    let container_sy = (containerSize_Height / container.ref.height)*this.g_mapScale
    container.ref.scaleX = 0
    container.ref.scaleY = 0
    let container_x = this.g_cartoon.getOrigin().x + x*this.g_mapScale + (containerSize_Width*this.g_mapScale)/2
    let container_y = this.g_cartoon.getOrigin().y + y*this.g_mapScale + (containerSize_Height*this.g_mapScale)/2
    container.ref.x = container_x
    container.ref.y = container_y
    container.cardIndex = index;
    container.isPlaced = false;

    let element = this.g_cartoon.createCartoonElement(`puzzle-card-${index}`, "MySprite")
    element.ref.init(this.g_cartoon.images.get(content.media.image_url))
    let element_sx = cardSize_Width / element.ref.width
    let element_sy = cardSize_Height / element.ref.height
    element.ref.setScaleXY(Math.min(element_sx,element_sy))
    element.ref.x = 0;
    element.ref.y = 0;
    container.ref.addChild(element.ref);

    container.enableMove = false;
    container.ref.alpha = 1;

    container.show = (delay:number)=>{
      setTimeout(() => {
        tweenChange(container.ref, { alpha:1 } , 0.1)
        tweenChange(element.ref, { alpha:1 } , 0.1)
        tweenChange(container.ref, { scaleX:container_sx, scaleY:container_sy } , 0.1)
      }, delay);
    }

    container.back = ()=>{
      tweenChange(container.ref, { x:container_x, y:container_y } , 0.1)
    }

    container.hide = ()=>{
      tweenChange(container.ref, { alpha:0 } , 0.1)
      tweenChange(element.ref, { alpha:0 } , 0.1)
    }

    container.moveTo = (x, y)=>{
      tweenChange(container.ref, { x:x, y:y } , 0.1)
    }

    this.subscribeMapDownEvent(`puzzle-card-${index}-container`, ()=>{
      this.m_currentMovedCardIndex = container.cardIndex
      container.enableMove= true;
      this.g_enableMapDown = true;
    })
    this.subscribeMapMoveEvent(`puzzle-card-${index}-container`, ()=>{
      if(container.enableMove){
        container.ref.x  = this.g_clickX
        container.ref.y  = this.g_clickY
      }
      this.g_enableMapMove = true;
    })
    this.subscribeMapUpEvent(`puzzle-card-${index}-container`, ()=>{
      this.m_currentMovedCardIndex = -1;
      container.enableMove= false;
      this.g_enableMapUp = true;
      if(!container.isPlaced){
        container.back();
      }
    })

   return container;
  }

  checkSelectedZone(zoneIndex){
    // console.log("currentQuestionIndex:" + this.m_currentQuestionIndex + " zoneIndex:" + zoneIndex + " currentMovedCardIndex: " + this.m_currentMovedCardIndex)
    if(this.m_currentQuestionIndex == zoneIndex){
      if(zoneIndex == this.m_currentMovedCardIndex){
        this.nextQusetion()
        return true;
      }
    }
    return false;
  }

  showPuzzleCards(){
    this.m_puzzleCards_id.forEach( (id,index) => {
      this.g_cartoon.getCartoonElement(id).show(index*100)
    });
  }

  addToCardList(id){
    this.m_puzzleCards_id.push(id)
  }

  cleanCardList(){
    this.m_puzzleCards_id.length = 0;
  }

  cleanGameVar(){
    this.m_puzzleCards_id.forEach(card => {
      this.g_cartoon.getCartoonElement(card).isPlaced = false;
    });
    this.cleanCardList()
    this.m_totalQusetion = 0;
    this.m_currentQuestionIndex = 0;
  }

  













  // --------------------------------------------------
  // --------------  Template function  ---------------
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  // 
  //                                  _________
  //                                 /         /.
  //           .-------------.      /_________/ |
  //         /             / |      |         | |
  //        /+============+\ |      | |====|  | |
  //        ||C:\>        || |      |         | |
  //        ||            || |      | |====|  | |
  //        ||            || |      |   ___   | |
  //        ||            || |      |  |166|  | |
  //        ||            ||/@@@    |   ---   | |
  //        \+============+/    @   |_________|./.
  //                          @          ..  ....'
  //        ..................@     __.'.'  ''
  //      /oooooooooooooooo//     ///
  //      /................//     /_/
  //      ------------------
  //
  // --------------------------------------------------

  @ViewChild("canvas") canvas: ElementRef;
  @ViewChild("wrap") wrap: ElementRef;
  @HostListener("window:resize", ["$event"])
  onResize(event) {
    this.g_winResizeEventStream.next();
  }
  ngOnDestroy() {
    window["curCtx"] = null;
    window.cancelAnimationFrame(this.g_animationId);
  }
  ngOnInit() {
    const getData = (<any>window).courseware.getData;
    getData(data => {
      if (window['air'].airClassInfo.user.classRole == 'tea') {
        if(!this.g_ForceChangeDefaultRole){
          this.g_teacherFlag = true;
        }
      }
      this.g_currentUser = window['air'].airClassInfo.user;
      if (data && typeof data == "object") {
        this.g_data = data;
        this.g_formData = data.contentObj;
      } else {
        this.g_data = {};
      }

      if (!this.g_data.contentObj) {
        this.g_data.contentObj = {};
        this.g_formData = {};
      }

      this.initDefaultData();
      this.initAudio();
      this.initImg();
      
       // 预加载资源
       this.g_cartoon.loadResources().then(() => {
        window["air"].hideAirClassLoading(this.g_KEY, this.g_data);
        this.initSystem();
        this.update();
        this.systemReady()
      });

      this.initListener();
    }, this.g_KEY);
  }

  // ----------------------------------
  // 初始化默认数据
  // ----------------------------------
  initDefaultData() {
    if ( Object.keys(this.g_formData).length===0 ) {
      // console.log("Init default form data")
      this.g_formData = defauleFormData;
    }
  }


  // ----------------------------------
  // 初始化音乐
  // ----------------------------------
  initAudio() {
    const contentObj = this.g_formData;
    if (!contentObj) {
      return;
    }
    const arr = contentObj.dataArray;
    // 添加用户上传音效
    let images:Array<string> = this.mapToAduioArray(contentObj)
    images.forEach(image => {
      this.g_cartoon.addAudio( image, image );
    });

    // 添加本地音效
    for( var key in localAudios ){
      this.g_cartoon.addAudio( key, localAudios[key] );
    }
  }

  // ----------------------------------
  // 初始化图片
  // ----------------------------------
  initImg() {
    const contentObj = this.g_formData;
    if (contentObj) {
      const addPicUrl = url => {
        if (url) {
          this.g_cartoon.addImage(url, url);
        }
      };
      let images:Array<string> = this.mapToImageArray(contentObj)
      images.forEach(image => {
        addPicUrl(image)
      });
    }
    // 添加本地图片
    for( var key in localImages ){
     this.g_cartoon.addImage( key, localImages[key] );
    }
  }


  mapDown(event) {
    if (!this.g_enableMapDown) {
      return;
    }
    for(let cartoonID in this.m_mapDownQueue){
      if (this.checkClickTarget(this.g_cartoon.getCartoonElementRef(cartoonID))) {
        this.g_enableMapDown = false;
        this.m_mapDownQueue[cartoonID]();
      }
    }
  }

  mapMove(event) {
    if (!this.g_enableMapMove) {
      return;
    }
    for(let cartoonID in this.m_mapMoveQueue){
      if (this.checkClickTarget(this.g_cartoon.getCartoonElementRef(cartoonID))) {
        this.g_enableMapMove = false;
        this.m_mapMoveQueue[cartoonID]();
      }
    }
  }

  mapUp(event) {
    if (!this.g_enableMapUp) {
      return;
    }
    for(let cartoonID in this.m_mapUpQueue){
      if (this.checkClickTarget(this.g_cartoon.getCartoonElementRef(cartoonID))) {
        this.g_enableMapUp = false;
        this.m_mapUpQueue[cartoonID]();
      }
    }
  }


  update() {
    this.g_animationId = window.requestAnimationFrame(this.update.bind(this));
    // 清除画布内容
    this.g_ctx.clearRect(0, 0, this.g_canvasWidth, this.g_canvasHeight);
    TWEEN.update();
    this.updateArr(this.m_renderArr);
    this.updateArr(this.m_endPageArr);
    this.updateArr(this.m_elementPetalArr);
  }


  updateItem(item) {
    if (item) {
      item.update();
    }
  }


  updateArr(arr) {
    if (!arr) {
      return;
    }
    for (let i = 0; i < arr.length; i++) {
      arr[i].update(this);
    }
  }


  initListener() {
    this.g_winResizeEventStream.pipe(debounceTime(500)).subscribe(data => {
      this.renderAfterResize();
    });

    if (this.IsPC()) {
      this.canvas.nativeElement.addEventListener("mousedown", event => {
        setMxMyByMouse(event);
        this.mapDown(event);
      });

      this.canvas.nativeElement.addEventListener("mousemove", event => {
        setMxMyByMouse(event);
        this.mapMove(event);
      });

      this.canvas.nativeElement.addEventListener("mouseup", event => {
        setMxMyByMouse(event);
        this.mapUp(event);
      });

      const setMxMyByMouse = event => {
        this.g_clickX = event.offsetX;
        this.g_clickY = event.offsetY;
      };
    } else {
      this.canvas.nativeElement.addEventListener("touchstart", event => {
        setMxMyByTouch(event);
        this.mapDown(event);
      });

      this.canvas.nativeElement.addEventListener("touchmove", event => {
        setMxMyByTouch(event);
        this.mapMove(event);
      });

      this.canvas.nativeElement.addEventListener("touchend", event => {
        setMxMyByTouch(event);
        this.mapUp(event);
      });

      this.canvas.nativeElement.addEventListener("touchcancel", event => {
        setMxMyByTouch(event);
        this.mapUp(event);
      });

      const setMxMyByTouch = event => {
        if (event.touches.length <= 0) {
          return;
        }

        if (this.g_canvasLeft == null) {
          setParentOffset();
        }

        this.g_clickX = event.touches[0].pageX - this.g_canvasLeft;
        this.g_clickY = event.touches[0].pageY - this.g_canvasTop;
      };

      const setParentOffset = () => {
        const rect = this.canvas.nativeElement.getBoundingClientRect();
        this.g_canvasLeft = rect.left;
        this.g_canvasTop = rect.top;
      };
    }
  }


  showArr(arr) {
    if (!arr) {
      return;
    }
    for (let i = 0; i < arr.length; i++) {
      arr[i].visible = true;
    }
  }


  hideArr(arr) {
    if (!arr) {
      return;
    }
    for (let i = 0; i < arr.length; i++) {
      arr[i].visible = false;
    }
  }


  IsPC() {
    if (window["ELECTRON"]) {
      return false; // 封装客户端标记
    }
    if (
      document.body.ontouchmove !== undefined &&
      document.body.ontouchmove !== undefined
    ) {
      return false;
    } else {
      return true;
    }
  }


  renderAfterResize() {
    this.g_canvasWidth = this.wrap.nativeElement.clientWidth;
    this.g_canvasHeight = this.wrap.nativeElement.clientHeight;
    this.update();
    this.handleScreenResize()
  }

  checkClickTarget(target) {
    if (!target) {
      return false;
    }
    const rect = target.getBoundingBox();
    if (this.checkPointInRect(this.g_clickX, this.g_clickY, rect)) {
      return true;
    }
    return false;
  }


  getWorlRect(target) {
    let rect = target.getBoundingBox();
    if (target.parent) {
      const pRect = this.getWorlRect(target.parent);
      rect.x += pRect.x;
      rect.y += pRect.y;
    }
    return rect;
  }


  checkPointInRect(x, y, rect) {
    if (x >= rect.x && x <= rect.x + rect.width) {
      if (y >= rect.y && y <= rect.y + rect.height) {
        return true;
      }
    }
    return false;
  }


  getPosByAngle(angle, len) {
    const radian = (angle * Math.PI) / 180;
    const x = Math.sin(radian) * len;
    const y = Math.cos(radian) * len;
    return { x, y };
  }


  getPosDistance(sx, sy, ex, ey) {
    const _x = ex - sx;
    const _y = ey - sy;
    const len = Math.sqrt(Math.pow(_x, 2) + Math.pow(_y, 2));
    return len;
  }


  subscribeMapDownEvent(id,callback){
    this.m_mapDownQueue[id] = callback
  }

  subscribeMapUpEvent(id,callback){
    this.m_mapUpQueue[id] = callback
  }

  subscribeMapMoveEvent(id,callback){
    this.m_mapMoveQueue[id] = callback
  }


  // 全局初始化入口，当资源加载完毕后执行
  initSystem() {

    this.g_canvasWidth = this.wrap.nativeElement.clientWidth;
    this.g_canvasHeight = this.wrap.nativeElement.clientHeight;

    const sx = this.g_canvasWidth / this.g_canvasBaseW;
    const sy = this.g_canvasHeight / this.g_canvasBaseH;
    const s = Math.min(sx, sy);

    this.g_mapScale = s;
    this.g_cartoon.mapScale = this.g_mapScale;
    this.g_cartoon.clientWidth = this.wrap.nativeElement.clientWidth;
    this.g_cartoon.clientHeight = this.wrap.nativeElement.clientHeight;

    this.m_renderArr = [];
    this.g_enableMapDown = true;

    this.g_ctx = this.canvas.nativeElement.getContext("2d");
    this.canvas.nativeElement.width = this.g_canvasWidth;
    this.canvas.nativeElement.height = this.g_canvasHeight;
    window["curCtx"] = this.g_ctx;

    // 初始化舞台
    this.initStage();
    this.update();
  }
  

  initStage() {
    const bgWidth = 1280;
    const bgHeight = 720;
    this.g_cartoon.stageWidth = bgWidth*this.g_mapScale;
    this.g_cartoon.stageHeight= bgHeight*this.g_mapScale;

    const imageColor = this.g_cartoon.createCartoonElement("background-color", "ShapeRect").ref;
    // console.log( "stageWidth: " + this.g_cartoon.stageWidth + " stageHeight" + this.g_cartoon.stageHeight)
    // console.log( "clientWidth: " + this.g_cartoon.clientWidth + " clientHeight" + this.g_cartoon.clientHeight)
    // console.log( "canvasWidth: " + this.g_canvasWidth + " canvasHeight" + this.g_canvasHeight)
    // console.log( "mapScale: " + this.g_mapScale )

    imageColor.x = this.g_cartoon.clientWidth / 2 ;
    imageColor.y = this.g_cartoon.clientHeight / 2 ;
    imageColor.setSize(this.g_cartoon.clientWidth, this.g_cartoon.clientHeight);
    imageColor.init();
    if(this.g_EnableStageRuler){
      imageColor.fillColor = '#BE3C38';
    }
    this.m_renderArr.push(imageColor);

    const image = this.g_cartoon.createCartoonElement("bg_1280_720_Ruler", "MySprite").ref;
    image.init(this.g_cartoon.images.get("bg_1280_720_Ruler"));
    image.x = this.g_canvasWidth / 2;
    image.y = this.g_canvasHeight /2;
    image.visible = this.g_EnableStageRuler
    this.g_cartoon.setOrigin( image.x-(bgWidth/2*this.g_mapScale), image.y-(bgHeight/2*this.g_mapScale)  )
    this.g_cartoon.setRelativeOrigin( -bgWidth/2, -bgHeight/2)
    image.setScaleXY(this.g_mapScale);
    this.m_renderArr.push(image);
    this.g_stage = image
  }

  render(ele){
    this.m_renderArr.push(ele);
  }

  sendServerEvent(key, data) {
    const c = (<any> window).courseware;
    c.sendEvent(key, JSON.stringify(data));
  }

  addServerListener(msg_key, callback) {
    const c = (<any> window).courseware;
    c.onEvent(msg_key, (data,next) => {
      callback(JSON.parse(data))
      next && next();
    });
  }

  randomArray_shuffle(array) {
    var input = array;
    for (var i = input.length-1; i >=0; i--) {
        var randomIndex = Math.floor(Math.random()*(i+1));
        var itemAtIndex = input[randomIndex];
        input[randomIndex] = input[i];
        input[i] = itemAtIndex;
    }
    return input;
  }


  paginationArray(pageNo, pageSize, array) {
    var offset = (pageNo - 1) * pageSize;
    return (offset + pageSize >= array.length) ? array.slice(offset, array.length) : array.slice(offset, offset + pageSize);
  }
















  












  // --------------------------------------------------
  // --------------  Template function  ---------------
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  // 
  //                .-~~~~~~~~~-._       _.-~~~~~~~~~-.
  //            __.'              ~.   .~              `.__
  //          .'//                  \./                  \\`.
  //        .'//                     |                     \\`.
  //      .'// .-~"""""""~~~~-._     |     _,-~~~~"""""""~-. \\`.
  //    .'//.-"                 `-.  |  .-'                 "-.\\`.
  //  .'//______.============-..   \ | /   ..-============.______\\`.
  //.'______________________________\|/______________________________`.
  //
  // --------------------------------------------------
  // showParticle( element_id )                 泡泡效果
  // --------------------------------------------------
  // showEndPatal()  /  stopEndPatal()  花瓣飘落结束动画 
  // --------------------------------------------------
  // showCorrectPatal()                 指定元素上面飘花
  // --------------------------------------------------
  // convertPercentToRadian()   将百分比转换为弧长 第一个参数是百分比，第二个参数是方向 true为逆时针，false为顺时针。用于ShapeCircle换圆弧
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  // --------------------------------------------------
  


  // 泡泡
  showParticle(card) {
    let myCard = this.g_cartoon.getCartoonElementRelativePosition(card.id)
    showPopParticle(this.g_cartoon.images.get("bubble"), { x: myCard.x , y: myCard.y }, this.g_stage);
  }

  // 选择正确动画
  showCorrectPatal(card_id, showTime) {
    this.m_elementPetalArr = [];
    this.m_showElementPetalFlag = true;
    this.addCorrectPetal(card_id);
    setTimeout(()=>{
      this.m_elementPetalArr = [];
      this.m_showElementPetalFlag = false;
    },showTime)
  }

  addCorrectPetal(card_id) {
    if (!this.m_showElementPetalFlag) {
      return;
    }

    let element = this.g_cartoon.getCartoonElement(card_id)
    const petal = new MySprite(this.g_ctx);

    const id = Math.ceil(Math.random() * 3);
    petal.init(this.g_cartoon.images.get(this.m_PetalImage + id));

    const randomS = (Math.random() * 0.4 + 0.6) * this.g_mapScale * 0.5;
    petal.setScaleXY(randomS);

    const randomR = Math.random() * 360;
    petal.rotation = randomR;

    const randomX = Math.random() * element.ref.width * this.g_mapScale;

    petal.x = element.ref.x - (element.ref.width * this.g_mapScale / 2) + randomX;
    petal.y = element.ref.y - (element.ref.height * this.g_mapScale / 2);

    const randomT = 1 + Math.random() * 2;
    petal["time"] = randomT;

    let randomTR = 360 * Math.random(); // - 180;
    if (Math.random() < 0.5) {
      randomTR *= -1;
    }
    petal["tr"] = randomTR;

    this.m_elementPetalArr.push(petal);
    moveItem(
      petal,
      petal.x,
      element.ref.y + (element.ref.height * this.g_mapScale / 2),
      petal["time"],
      () => {
        removeItemFromArr(this.m_elementPetalArr, petal);
      }
    );
    rotateItem(petal, petal["tr"], petal["time"]);
    setTimeout(() => {
      this.addCorrectPetal(card_id);
    }, 200);
  }





  // 结束动画花瓣飘落
  showEndPatal() {
    this.m_endPageArr = [];
    this.m_showPetalFlag = true;
    this.addPetal();
  }

  stopEndPatal() {
    this.m_endPageArr = [];
    this.m_showPetalFlag = false;
  }

  addPetal() {
    if (!this.m_showPetalFlag) {
      return;
    }
    const petal = this.getPetal();
    this.m_endPageArr.push(petal);
    moveItem(
      petal,
      petal.x,
      this.g_canvasHeight + petal.height * petal.scaleY,
      petal["time"],
      () => {
        removeItemFromArr(this.m_endPageArr, petal);
      }
    );
    rotateItem(petal, petal["tr"], petal["time"]);
    setTimeout(() => {
      this.addPetal();
    }, 100);
  }

  getPetal() {
    const petal = new MySprite(this.g_ctx);

    const id = Math.ceil(Math.random() * 3);
    petal.init(this.g_cartoon.images.get(this.m_PetalImage + id));

    const randomS = (Math.random() * 0.4 + 0.6) * this.g_mapScale;
    petal.setScaleXY(randomS);

    const randomR = Math.random() * 360;
    petal.rotation = randomR;

    const randomX = Math.random() * this.g_canvasWidth;
    petal.x = randomX;
    petal.y = (-petal.height / 2) * petal.scaleY;

    const randomT = 2 + Math.random() * 5;
    petal["time"] = randomT;

    let randomTR = 360 * Math.random(); // - 180;
    if (Math.random() < 0.5) {
      randomTR *= -1;
    }
    petal["tr"] = randomTR;

    return petal;
  }


  showJellyAnimation(element_id){
    let element = this.g_cartoon.getCartoonElement(element_id).ref
    tweenChange(element,{ scaleX: this.g_mapScale * 1.1 , scaleY: this.g_mapScale * 1.1 }, 0.1, ()=>{
      tweenChange(element,{ scaleX: this.g_mapScale * 0.9 , scaleY: this.g_mapScale * 0.9 }, 0.1, ()=>{
        tweenChange(element,{ scaleX: this.g_mapScale , scaleY: this.g_mapScale }, 0.1 )
      })
    })
  }

  showShakeAnimation(element_id){
    let element = this.g_cartoon.getCartoonElement(element_id).ref
    let originX = element.x
    tweenChange(element,{ x: originX - 10*this.g_mapScale }, 0.1, ()=>{
      tweenChange(element,{ x: originX + 10*this.g_mapScale }, 0.1, ()=>{
        tweenChange(element,{ x: originX }, 0.1 )
      })
    })
  }

  convertPercentToRadian(per, direction){
    let radian = 0;
    if(direction) { //逆时针
      if(per*360 > 270){
        radian = 361 - (per*360 - 270)
      }else{
        radian = 270 - per*360
      }
    }else{ //顺时针
      if(per*360 > 90){
        radian = per*360 - 90
      }else{
        radian = 270 + per*360
      }
    }
    return (radian * Math.PI) / 180;
  }

}
