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

import {
  hideItem,
  Label,
  moveItem,
  MySprite, randomSortByArr, removeItemFromArr, ShapeRect, tweenChange,

} from './Unit';
import { res, resAudio } from './resources';

import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

import TWEEN from '@tweenjs/tween.js';
import { Button } from './Button';
import { AudioAnimation } from './AudioAnimation';
import { Card } from './Card';
import { getDefaultAnswer } from '../bean/ExercisesBean';




@Component({
  selector: 'app-play',
  templateUrl: './play.component.html',
  styleUrls: ['./play.component.css']
})
export class PlayComponent implements OnInit, OnDestroy {

  @ViewChild('canvas', { static: true }) canvas: ElementRef;
  @ViewChild('wrap', { static: true }) wrap: ElementRef;

  // 数据
  data;

  ctx;

  canvasWidth = 1280; // canvas实际宽度
  canvasHeight = 720; // canvas实际高度

  canvasBaseW = 1280; // canvas 资源预设宽度
  canvasBaseH = 720;  // canvas 资源预设高度

  mx; // 点击x坐标
  my; // 点击y坐标


  // 资源
  rawImages = new Map(res);
  rawAudios = new Map(resAudio);

  images = new Map();

  animationId: any;
  winResizeEventStream = new Subject();

  audioObj = {};

  renderArr;
  endRenderArr = [];
  mapScale = 1;

  canvasLeft;
  canvasTop;

  saveKey = 'LST-05';


  canTouch = true;

  answerArr = [];

  answerArr2 = [];

  mask: ShapeRect;

  btnStart: Button;

  btnRestart: Button;

  audioAni: AudioAnimation;

  cardArr = [];

  holePosArr = [];

  holeExistArr = [];

  cardExistArr = [];

  gameEndFlag = false;

  showCardTimeId;

  @HostListener('window:resize', ['$event'])
  onResize(event) {
    this.winResizeEventStream.next();
  }


  ngOnInit() {

    this.data = {};

    // 获取数据
    const getData = (<any>window).courseware.getData;
    getData((data) => {

      if (data && typeof data == 'object') {
        this.data = data;
      }
      // console.log('data:' , data);

      // 初始化 各事件监听
      this.initListener();

      // 若无数据 则为预览模式 需要填充一些默认数据用来显示
      this.initDefaultData();

      // 初始化 音频资源
      this.initAudio();
      // 初始化 图片资源
      this.initImg();
      // 开始预加载资源
      this.load();

    }, this.saveKey);

  }

  ngOnDestroy() {
    window['curCtx'] = null;
    window.cancelAnimationFrame(this.animationId);
  }


  load() {

    // 预加载资源
    this.loadResources().then(() => {
      window["air"].hideAirClassLoading(this.saveKey, this.data);
      this.init();
      this.update();
    });
  }


  init() {

    this.initCtx();
    this.initData();
    this.initView();
  }

  initCtx() {
    this.canvasWidth = this.wrap.nativeElement.clientWidth;
    this.canvasHeight = this.wrap.nativeElement.clientHeight;
    this.canvas.nativeElement.width = this.wrap.nativeElement.clientWidth;
    this.canvas.nativeElement.height = this.wrap.nativeElement.clientHeight;


    this.ctx = this.canvas.nativeElement.getContext('2d');
    this.canvas.nativeElement.width = this.canvasWidth;
    this.canvas.nativeElement.height = this.canvasHeight;

    window['curCtx'] = this.ctx;
  }






  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.winResizeEventStream
      .pipe(debounceTime(500))
      .subscribe(data => {
        this.renderAfterResize();
      });


    // ---------------------------------------------
    const setParentOffset = () => {
      const rect = this.canvas.nativeElement.getBoundingClientRect();
      this.canvasLeft = rect.left;
      this.canvasTop = rect.top;
    };
    const setMxMyByTouch = (event) => {
      if (event.touches.length <= 0) {
        return;
      }
      if (this.canvasLeft == null) {
        setParentOffset();
      }
      this.mx = event.touches[0].pageX - this.canvasLeft;
      this.my = event.touches[0].pageY - this.canvasTop;
    };

    const setMxMyByMouse = (event) => {
      this.mx = event.offsetX;
      this.my = event.offsetY;
    };
    // ---------------------------------------------


    let firstTouch = true;

    const touchDownFunc = (e) => {
      if (firstTouch) {
        firstTouch = false;
        removeMouseListener();
      }
      setMxMyByTouch(e);
      this.mapDown(e);
    };
    const touchMoveFunc = (e) => {
      setMxMyByTouch(e);
      this.mapMove(e);
    };
    const touchUpFunc = (e) => {
      setMxMyByTouch(e);
      this.mapUp(e);
    };

    const mouseDownFunc = (e) => {
      if (firstTouch) {
        firstTouch = false;
        removeTouchListener();
      }
      setMxMyByMouse(e);
      this.mapDown(e);
    };
    const mouseMoveFunc = (e) => {
      setMxMyByMouse(e);
      this.mapMove(e);
    };
    const mouseUpFunc = (e) => {
      setMxMyByMouse(e);
      this.mapUp(e);
    };


    const element = this.canvas.nativeElement;

    const addTouchListener = () => {
      element.addEventListener('touchstart', touchDownFunc);
      element.addEventListener('touchmove', touchMoveFunc);
      element.addEventListener('touchend', touchUpFunc);
      element.addEventListener('touchcancel', touchUpFunc);
    };
    const removeTouchListener = () => {
      element.removeEventListener('touchstart', touchDownFunc);
      element.removeEventListener('touchmove', touchMoveFunc);
      element.removeEventListener('touchend', touchUpFunc);
      element.removeEventListener('touchcancel', touchUpFunc);
    };

    const addMouseListener = () => {
      element.addEventListener('mousedown', mouseDownFunc);
      element.addEventListener('mousemove', mouseMoveFunc);
      element.addEventListener('mouseup', mouseUpFunc);
    };
    const removeMouseListener = () => {
      element.removeEventListener('mousedown', mouseDownFunc);
      element.removeEventListener('mousemove', mouseMoveFunc);
      element.removeEventListener('mouseup', mouseUpFunc);
    };

    addMouseListener();
    addTouchListener();
  }


  playAudio(key, now = false, callback = null) {

    const audio = this.audioObj[key];
    if (audio) {
      if (now) {
        audio.pause();
        audio.currentTime = 0;
      }

      if (callback) {
        audio.onended = () => {
          callback();
        };
      }
      audio.play();
    }
  }



  loadResources() {
    const pr = [];
    this.rawImages.forEach((value, key) => {// 预加载图片

      const p = this.preload(value)
        .then(img => {
          this.images.set(key, img);
        })
        .catch(err => console.log(err));

      pr.push(p);
    });

    this.rawAudios.forEach((value, key) => {// 预加载音频

      const a = this.preloadAudio(value)
        .then(() => {
          // this.images.set(key, img);
        })
        .catch(err => console.log(err));

      pr.push(a);
    });
    return Promise.all(pr);
  }

  preload(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      // img.crossOrigin = "anonymous";
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = url;
    });
  }

  preloadAudio(url) {
    return new Promise((resolve, reject) => {
      const audio = new Audio();
      audio.oncanplay = (a) => {
        resolve();
      };
      audio.onerror = () => {
        reject();
      };
      audio.src = url;
      audio.load();
    });
  }


  renderAfterResize() {
    this.canvasWidth = this.wrap.nativeElement.clientWidth;
    this.canvasHeight = this.wrap.nativeElement.clientHeight;
    this.init();
  }





  checkClickTarget(target) {

    const rect = target.getBoundingBox();

    if (this.checkPointInRect(this.mx, this.my, 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;
  }





  addUrlToAudioObj(key, url = null, vlomue = 1, loop = false, callback = null) {

    const audioObj = this.audioObj;

    if (url == null) {
      url = key;
    }

    this.rawAudios.set(key, url);

    const audio = new Audio();
    audio.src = url;
    audio.load();
    audio.loop = loop;
    audio.volume = vlomue;

    audioObj[key] = audio;

    if (callback) {
      audio.onended = () => {
        callback();
      };
    }
  }

  addUrlToImages(url) {
    this.rawImages.set(url, url);
  }






  // ======================================================编写区域==========================================================================





  /**
   * 添加默认数据 便于无数据时的展示
   */
  initDefaultData() {
    if(!this.data.rightAnswer){
      this.data.rightAnswer = getDefaultAnswer();
      this.data.rightAnswer.isRight = '1';
      this.data.rightAnswer.val = 'Hello';
    }

    if(!this.data.answerArr){
      this.data.answerArr = [];

      for(let i = 0; i < 3; ++ i){
        let answer = getDefaultAnswer();
        answer.val = "" + i;
        this.data.answerArr.push(answer);
      }
    }
  }


  /**
   * 添加预加载图片
   */
  initImg() {

    if (this.data.rightAnswer && this.data.rightAnswer.picUrl) {
      this.addUrlToImages(this.data.rightAnswer.picUrl);
    }

    for (let i = 0; i < this.data.answerArr.length; ++i) {
      let answer = this.data.answerArr[i];

      if (answer.picUrl) {
        this.addUrlToImages(answer.picUrl);
      }
    }

  }

  /**
   * 添加预加载音频
   */
  initAudio() {

    this.rawAudios.forEach((item, key) => {
      // 音效
      this.addUrlToAudioObj(key, this.rawAudios.get(key));
    })

    if (this.data.audioUrl) {
      this.addUrlToAudioObj(this.data.audioUrl);
    }

    if (this.data.rightAnswer && this.data.rightAnswer.audioUrl) {
      this.addUrlToAudioObj(this.data.rightAnswer.audioUrl);
    }

    for (let i = 0; i < this.data.answerArr.length; ++i) {
      let answer = this.data.answerArr[i];

      if (answer.audioUrl) {
        this.addUrlToAudioObj(answer.audioUrl);
      }
    }
  }



  /**
   * 初始化数据
   */
  initData() {

    const sx = this.canvasWidth / this.canvasBaseW;
    const sy = this.canvasHeight / this.canvasBaseH;
    const s = Math.min(sx, sy);
    this.mapScale = s;

    this.renderArr = [];

    this.endRenderArr = [];

    this.canTouch = false;

    this.answerArr = [];

    this.cardArr = [];

    this.holePosArr = [];

    this.gameEndFlag = false;

    let num = 2 + Math.ceil(Math.random() * 2);

    for(let i = 0; i < num; ++ i){
      let answer = getDefaultAnswer();
      answer.audioUrl = this.data.rightAnswer.audioUrl;
      answer.contentType = this.data.rightAnswer.contentType;
      answer.isRight = "1";
      answer.picUrl = this.data.rightAnswer.picUrl;
      answer.val = this.data.rightAnswer.val;
      this.answerArr.push(answer);
    }

    this.answerArr.push(...this.data.answerArr);

    this.answerArr2 = [];

    this.holeExistArr = [];

    this.cardExistArr = [];
    
    if (this.showCardTimeId) {

      clearTimeout(this.showCardTimeId);

    }


  }


  createPic(key) {
    const pic = new MySprite();
    pic.init(this.images.get(key));
    pic.setScaleXY(this.mapScale);
    return pic;
  }

  getMask() {
    const mask = new ShapeRect();
    mask.setSize(this.canvasWidth, this.canvasHeight);
    mask.fillColor = '#000000';
    mask.alpha = 0.7;
    this.mask = mask;

  }

  /**
   * 初始化试图
   */
  initView() {

    this.initBg();

    this.initPic();

    this.initBtn();

    this.initCard();

    this.canTouch = true;
  }

  initBg() {

    const bg = this.createPic('bg');
    bg.x = this.canvasWidth / 2;
    bg.y = this.canvasHeight / 2;
    bg.setScaleXY(Math.max(this.canvasWidth / bg.width, this.canvasHeight / bg.height));

    this.renderArr.push(bg);
  }

  initPic() {
    const audioAni = new AudioAnimation(this.images);
    audioAni.setXY(this.canvasWidth - 120 * this.mapScale, this.canvasHeight - 120 * this.mapScale);
    audioAni.initPic(this.renderArr, true);
    audioAni.bg.setScaleXY(this.mapScale);
    audioAni.bg['renderZ'] = 100;
    this.audioAni = audioAni;


    const bg_behind = this.createPic('bg_behind');
    bg_behind.x = this.canvasWidth / 2;
    bg_behind.y = this.canvasHeight - 170 * this.mapScale;
    this.renderArr.push(bg_behind);
    bg_behind['renderZ'] = 10;


    const bg_front = this.createPic('bg_front');
    bg_front.x = this.canvasWidth / 2 - 10 * this.mapScale;
    bg_front.y = this.canvasHeight - 30 * this.mapScale;
    this.renderArr.push(bg_front);
    bg_front['renderZ'] = 20;
  }

  initBtn() {

    this.getMask();

    this.endRenderArr.push(this.mask);

    const btnStart = new Button(this.images, this.audioObj, this.mapScale, this.mapScale);
    btnStart.x = this.canvasWidth / 2;
    btnStart.y = this.canvasHeight / 2;
    btnStart.outY = btnStart.y;
    btnStart.init('btn_start');
    btnStart.outX = this.canvasWidth + btnStart.width;
    this.btnStart = btnStart;
    this.endRenderArr.push(btnStart.btnPic);

    const btnRestart = new Button(this.images, this.audioObj, this.mapScale, this.mapScale);
    btnRestart.y = this.canvasHeight / 2;
    btnRestart.x = this.canvasWidth + 300 * this.mapScale;
    btnRestart.outY = btnRestart.y;
    btnRestart.init('btn_restart');
    btnRestart.outX = this.canvasWidth + btnRestart.width;
    this.btnRestart = btnRestart;
    this.endRenderArr.push(btnRestart.btnPic);

  }



  mapDown(event) {

    if (!this.canTouch) {
      return;
    }

    if (this.checkClickTarget(this.btnStart.btnPic)) {

      hideItem(this.mask, 0.5);

      this.btnStart.click(() => {

        this.initGame();
      });

      return;
    }

    if (this.checkClickTarget(this.btnRestart.btnPic)) {
      this.gameEndFlag = false;


      hideItem(this.mask, 0.5);

      this.btnRestart.click(() => {

        this.initGame();
      })

      this.playAudio('click');

      return;
    }

    if (this.audioAni && this.checkClickTarget(this.audioAni.bg)) {

      if (this.audioAni.playing) {
        this.audioAni.pauseAudio();
      }
      else {
        this.audioAni.playAudio(this.audioObj[this.data.audioUrl]);
      }
      return;
    }

    for (let i = 0; i < this.cardArr.length; i++) {
      let card = this.cardArr[i];

      if (this.checkClickTarget(card.bg) || this.checkClickTarget(card.bgPic) || this.checkClickTarget(card.bgWord)) {
        this.clickCard(card);
        return;
      }
    }


  }

  mapMove(event) {

  }

  mapUp(event) {

  }



  update() {

    // ----------------------------------------------------------
    this.animationId = window.requestAnimationFrame(this.update.bind(this));
    // 清除画布内容
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
    // tween 更新动画
    TWEEN.update();
    // ----------------------------------------------------------



    this.updateArr(this.renderArr);

    this.updateArr(this.endRenderArr);
  }

  sortRenderZ(arr) {
    arr.sort((a, b) => {
      if (!a.renderZ) a.renderZ = 0;
      if (!b.renderZ) b.renderZ = 0;

      return a.renderZ - b.renderZ;
    });

  }

  initCard() {

    this.cleanOldCard();

    this.cardArr = [];
    this.holePosArr = [];

    this.answerArr2 = [];

    const rightNum = Math.ceil(Math.random() * 3);
    const idArr = randomSortByArr([1, 2, 3]);
    idArr.length = rightNum;

    let card = new Card();
    card.init(this.images, this.mapScale);
    card.bg.x = 380 * this.mapScale;
    card.bg.y = this.canvasHeight - 70 * this.mapScale;
    card.bg['renderZ'] = 8;
    card['id'] = 0;
    this.renderArr.push(card.bg);
    this.holePosArr.push({ x: card.bg.x, y: card.bg.y, z: 8 });
    this.cardArr.push(card);


    card = new Card();
    card.init(this.images, this.mapScale);
    card.bg.x = 820 * this.mapScale;
    card.bg.y = this.canvasHeight - 50 * this.mapScale;
    card.bg['renderZ'] = 9;
    card['id'] = 1;
    this.renderArr.push(card.bg);
    this.holePosArr.push({ x: card.bg.x, y: card.bg.y, z: 9 });
    this.cardArr.push(card);
    card.showNext(this.answerArr[1]);

    card = new Card();
    card.init(this.images, this.mapScale);
    card.bg.x = 100 * this.mapScale;
    card.bg.y = this.canvasHeight + 110 * this.mapScale;
    card.bg['renderZ'] = 19;
    card['id'] = 1;
    this.renderArr.push(card.bg);
    this.holePosArr.push({ x: card.bg.x, y: card.bg.y, z: 19 });
    this.cardArr.push(card);

    card = new Card();
    card.init(this.images, this.mapScale);
    card.bg.x = 570 * this.mapScale;
    card.bg.y = this.canvasHeight + 125 * this.mapScale;
    card.bg['renderZ'] = 19;
    card['id'] = 1;
    this.renderArr.push(card.bg);
    this.holePosArr.push({ x: card.bg.x, y: card.bg.y, z: 19 });
    this.cardArr.push(card);

    card = new Card();
    card.init(this.images, this.mapScale);
    card.bg.x = 1050 * this.mapScale;
    card.bg.y = this.canvasHeight;
    card.bg['renderZ'] = 19;
    card['id'] = 1;
    this.renderArr.push(card.bg);
    this.holePosArr.push({ x: card.bg.x, y: card.bg.y, z: 19 });
    this.cardArr.push(card);

    this.sortRenderZ(this.renderArr);
  }

  cleanOldCard() {
    if (this.cardArr && this.cardArr.length > 0) {
      for (let i = 0; i < this.cardArr.length; i++) {
        clearTimeout(this.cardArr[i].timeId);
        if (this.cardArr[i].tween) {
          this.cardArr[i].tween.stop();
        }

      }
    }


    if (this.cardArr) {
      for (let i = 0; i < this.cardArr.length; i++) {
        removeItemFromArr(this.renderArr, this.cardArr[i]);
      }
    }

  }


  initGame() {

    this.gameEndFlag = false;

    this.answerArr.forEach((val) => {
      val.endFlag = false;
    })

    const exAudio = this.audioObj[this.data.audioUrl];
    this.audioAni.playAudio(exAudio, true);

    this.showCard();

  }

  getAnswer(callback) {

    let answer = this.getRandomItemFromArr(this.answerArr);
    
    if(!answer){
      
      setTimeout(()=>{
        this.getAnswer(callback);
      }, 10);
      
    }
    else{
      let index = this.answerArr2.indexOf(answer);
      
      if(index == -1 && !answer.endFlag){

        this.answerArr2.push(answer);

        callback(answer);
      }
      else{
        setTimeout(()=>{
          this.getAnswer(callback);
        }, 10);
      }
    }
  }

  getRandomItemFromArr(arr) {

    if (!arr) {
      return;
    }

    let length = arr.length;

    let index = Math.ceil(Math.random() * 100000000) % length;

    return arr[index];
  }

  showCard() {

    if (this.gameEndFlag) {
      return;
    }
    
    if (this.holeExistArr.length >= 3) {
      return;
    }

    this.getAnswer((answer) => {
      
      const holeArr = [0, 1, 2, 3 , 4];
      for (let i = 0; i < this.holeExistArr.length; i++) {
        removeItemFromArr(holeArr, this.holeExistArr[i]);
      }
      let tmp = Math.floor(holeArr.length * Math.random());
      const holeIndex = holeArr[tmp];
      const pos = this.holePosArr[holeIndex];


      const cardIndexArr = [0, 1, 2, 3 , 4];
      for (let i = 0; i < this.cardExistArr.length; i++) {
        removeItemFromArr(cardIndexArr, this.cardExistArr[i]);
      }
     
      for (let i = 0; i < cardIndexArr.length; i++) {
        const cardIndex = cardIndexArr[i];
        if (cardIndex) {
          const card = this.cardArr[cardIndex];
          
          if (card.canHit) {
            cardIndexArr.splice(i, 1);
            i--;
          }
        }

      }
      
      if (cardIndexArr.length == 0) {
        return;
      }

      tmp = Math.floor(cardIndexArr.length * Math.random());
      const cardIndex = cardIndexArr[tmp];
      const card = this.cardArr[cardIndex];

      card.bg.x = pos.x;
      card.bg.y = pos.y;
      card.holeId = holeIndex;
      card.id = cardIndex;
      card.baseY = pos.y;
      card.targetY = pos.y - card.bg.height - 10 * this.mapScale;
      card.bg.renderZ = pos.z;

      this.sortRenderZ(this.renderArr);

      card.showNext(answer);
      
      this.cardUp(card);

      this.holeExistArr.push(holeIndex);
      this.cardExistArr.push(cardIndex);

      this.showCardTimeId = setTimeout(() => {
        // console.log(' in timeout')
        this.showCard();
      
      }, 500 + 1500 * Math.random());
      
  
    });

  }

  cardUp(card) {

    card.canHit = true;

    const tween = moveItem(card.bg, card.bg.x, card.targetY, 0.5 + 0.2 * Math.random(), () => {

      card.timeId = setTimeout(() => {

        card.timeId = null;
        this.cardDown(card);

      }, 300 + 1200 * Math.random());

    }, TWEEN.Easing.Cubic.Out);

    card.tween = tween;


  }

  cardDown(card, time = 0.8) {

    const tween = moveItem(card.bg, card.bg.x, card.baseY, time, () => {


      card.canHit = false;

      if (!card.curAnswer.endFlag) {
       
      } else {
        this.checkGameEnd();
      }

      removeItemFromArr(this.cardExistArr, card.id);

      removeItemFromArr(this.holeExistArr, card.holeId);

      removeItemFromArr(this.answerArr2, card.curAnswer);

      if (this.holeExistArr.length <= 1) {
        this.showCard();
      }


    }, TWEEN.Easing.Cubic.Out);

    card.tween = tween;


  }

  clickCard(card) {
    if (!card.canHit) {
      return;
    }

    if (card.tween) {
      card.tween.stop();
    }
    if (card.timeId) {
      clearTimeout(card.timeId);
      card.timeId = null;
    }

    if (card.curAnswer.isRight == '1') {

      this.playAudio('right', true, () => {
        this.playAudio(card.curAnswer.audioUrl, true);
      });

      card.curAnswer.endFlag = true;

      
    } else {
      this.playAudio('wrong', true);
    }

    card.canHit = false;
    this.cardDown(card, 0.35);

  }

  checkGameEnd() {
    for (let i = 0; i < this.answerArr.length; i++) {
      if (this.answerArr[i].isRight == '1' && !this.answerArr[i].endFlag) {
        return;
      }
    }
    console.log('play end...')
    this.showEnd();

  }

  showEnd() {

    this.audioAni.pauseAudio();

    for (let i = 0; i < this.cardArr.length; i++) {
      //hideItem(this.cardArr[i].bg, 0.3);
      this.cardArr[i].bg.visible = false;
    }

    this.gameEndFlag = true;
    this.canTouch = false;

    tweenChange(this.mask, { alpha: 0.7 }, 0.5, () => {

      moveItem(this.btnRestart.btnPic, this.canvasWidth / 2, this.btnRestart.y, 0.5, () => {
        this.canTouch = true;


        this.holeExistArr = [];
        this.cardExistArr = [];

        this.initCard();

      }, TWEEN.Easing.Cubic.Out);

    });

  }
}
