import Dog from "../Objects/Dog";
import LimitBar from "../Objects/LimitBar";
import LocalStorage from "../Data/LocalStorage";
import PracticeLine from "../Objects/PracticeLine";
import Utils from "../Tools/Utils";
import { GlobalVariables } from "../Data/GlobalVariables";
import StageController from "./StageController";
import SoundController from "./SoundController";
import { showInterstitialAd } from "../../adsense-h5g-api/h5_games_ads";
import { InterstitialType } from "../../adsense-h5g-api/interstitial_type";

const { ccclass, property } = cc._decorator;

@ccclass
export default class GamePlay extends cc.Component {
  @property lineMinSegLength = 10;
  @property lineWidth = 10;

  @property(Array(cc.Prefab)) prefabToLoad: Array<cc.Prefab> =
    new Array<cc.Prefab>();
  @property(cc.Graphics) graphic: cc.Graphics = null;
  @property(cc.Label) timerLabel: cc.Label = null;
  @property(cc.Node) clock: cc.Node = null;
  @property(cc.Node) levelLabel: cc.Node = null;
  @property(cc.Node) starLabel: cc.Node = null;
  @property(cc.Node) coinLabel: cc.Node = null;
  @property(cc.Node) coinEarned: cc.Node = null;
  @property(cc.Node) levelComp: cc.Node = null;
  @property(cc.Node) adsBgr: cc.Node = null;
  @property(cc.Node) notiLabel: cc.Node = null;
  @property(cc.Node) rewardSprite: cc.Node = null;
  @property(cc.Node) shadow: cc.Node = null;
  @property(cc.AudioClip) soundClick: cc.AudioClip = null;
  @property([cc.SpriteFrame]) starWinSprites: cc.SpriteFrame[] = [];
  @property(cc.Prefab) fadePrefab: cc.Prefab = null;
  @property(Array(cc.Prefab)) skinPlayerPrefabs: Array<cc.Prefab> =
    new Array<cc.Prefab>();
  @property(Array(cc.Prefab)) skinEnemyPrefabs: Array<cc.Prefab> =
    new Array<cc.Prefab>();
  @property(Array(cc.SpriteFrame)) skinHives: Array<cc.SpriteFrame> =
    new Array<cc.SpriteFrame>();

  static instance: GamePlay;

  drew: boolean = false;
  draw: cc.Node = null;
  moved: boolean = false;
  rb: cc.RigidBody = null;
  rbCol: cc.PhysicsPolygonCollider = null;
  countdown: number = 7;
  startCount: boolean = false;
  currentLevel: number = 1;
  highestLevel: number;
  currentAllStar: number;
  currentCoin: number;
  currentSkinPlayer: string;
  currentSkinEnemy: string;

  isGameover: boolean = false;
  isCompleted: boolean = false;

  lastTouchPos: cc.Vec2 = cc.v2(0, 0);
  lastDrawnPos: cc.Vec2 = cc.v2(0, 0);
  leftEdge: cc.Vec2 = null;
  rightEdge: cc.Vec2 = null;

  linePoints: Array<cc.Vec2> = new Array<cc.Vec2>();
  leftEdgePoints: Array<cc.Vec2> = new Array<cc.Vec2>();
  rightEdgePoints: Array<cc.Vec2> = new Array<cc.Vec2>();

  blockedObjects: Array<cc.Node> = new Array<cc.Node>();
  randomCoin: number = Math.floor(Math.random() * (30 - 26)) + 25;

  protected onLoad(): void {
    GamePlay.instance = this;
    cc.director.getPhysicsManager().enabled = true;
    cc.director.getCollisionManager().enabled = true;

    window.addEventListener("beforeunload", this.handleGameReload);
    window.addEventListener("unload", this.handleGameReload);

    // cc.director.getPhysicsManager().debugDrawFlags = 1;
    this.highestLevel = LocalStorage.getHighestLevel();
    this.currentLevel = LocalStorage.getCurrentLevel();
    this.currentCoin = LocalStorage.getAllCoin();
    this.currentSkinPlayer = LocalStorage.getCurrentSkinPlayer();
    this.currentSkinEnemy = LocalStorage.getCurrentSkinEnemy();
    this.coinLabel.getComponent(
      cc.Label
    ).string = `x${this.currentCoin.toString()}`;

    this.updateAllStarLabel();
    this.loadPrefab(GlobalVariables.levelToLoad);

    const dataBlocked = StageController.instance.blockedObjects;
    for (let i = 0; i < dataBlocked.length; i++) {
      this.blockedObjects.push(dataBlocked[i]);
    }

    this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchDown, this);
    this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
    this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchUp, this);
    this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchUp, this);
  }

  handleGameReload() {
    LocalStorage.setGameStarted(false);
  }

  onPlayerClickAds() {
    showInterstitialAd(InterstitialType.START, "doubleCoin");
  }

  onDestroy() {
    window.removeEventListener("beforeunload", this.handleGameReload);
    window.removeEventListener("unload", this.handleGameReload);
  }

  onShowNotiAds(status: string) {
    if (status == "ok") {
      this.notiLabel.getComponent(cc.Label).string =
        "Successfully doubled coins!";
      LocalStorage.setAllCoin(LocalStorage.getAllCoin() + this.randomCoin * 2);
      this.adsBgr.active = true;
      this.notiLabel.active = true;
      this.scheduleOnce(() => {
        this.adsBgr.active = false;
        this.notiLabel.active = false;
      }, 2);
      this.currentCoin = LocalStorage.getAllCoin();
      this.coinLabel.getComponent(cc.Label).string = `x${this.currentCoin}`;
    }

    if (status == "cancel") {
      this.showFailedNotiAds("Double coin failed");
    }

    if (status == "notReady") {
      this.showFailedNotiAds("Advertisement is preparing");
    }

    if (status == "frequencyCapped") {
      this.showFailedNotiAds("Ads frequency limit!");
    }
  }

  showFailedNotiAds(status: string) {
    this.notiLabel.getComponent(cc.Label).string = status;
    this.adsBgr.active = true;
    this.notiLabel.active = true;
    this.scheduleOnce(() => {
      this.adsBgr.active = false;
      this.notiLabel.active = false;
    }, 2);
  }

  updateAllStarLabel() {
    let sum = 0;
    for (const item of LocalStorage.getAllStars()) {
      if (item.hasOwnProperty("star")) {
        sum += item.star;
      }
    }
    this.starLabel.getComponent(cc.Label).string = `${sum}/90`;
  }

  loadPrefab(level) {
    const newNode = cc.instantiate(this.prefabToLoad[level]);
    newNode.parent = this.node;
    newNode.position = cc.Vec3.ZERO;
    this.levelLabel.getComponent(
      cc.Label
    ).string = `Level ${this.prefabToLoad[level].name}`;
    this.isCompleted = false;
    this.isGameover = false;
    this.updateSkin();
  }

  updateSkin() {
    for (let i = 0; i < this.skinPlayerPrefabs.length; i++) {
      const skin = this.skinPlayerPrefabs[i];
      if (skin.name == this.currentSkinPlayer) {
        const defaultAnim =
          this.node.children[0].getComponentsInChildren("Dog");
        for (let i = 0; i < defaultAnim.length; i++) {
          const anim = defaultAnim[i].getComponent(cc.Animation);

          let updateAnim = skin.data.getComponent(cc.Animation);

          anim.getClips()[0] = updateAnim.getClips()[0];
          anim.getClips()[1] = updateAnim.getClips()[1];
          anim.play(`${this.currentSkinPlayer}Idle`);
          anim.playOnLoad = true;
        }
      }
    }

    for (let i = 0; i < this.skinEnemyPrefabs.length; i++) {
      const skin = this.skinEnemyPrefabs[i];
      if (skin.name == this.currentSkinEnemy) {
        const hives = this.node.children[0].getComponentsInChildren("Hive");
        for (let i = 0; i < hives.length; i++) {
          const hive = hives[i];
          hive.beePrefab = skin;

          if (skin.name == "Bee") {
            hive.node.parent.getComponent(cc.Sprite).spriteFrame =
              this.skinHives[0];
          }
          if (skin.name == "Alien") {
            hive.node.parent.getComponent(cc.Sprite).spriteFrame =
              this.skinHives[1];
            hive.node.getComponent(cc.Sprite).spriteFrame = null;
          }
          if (skin.name == "Mosquito") {
            hive.node.parent.getComponent(cc.Sprite).spriteFrame =
              this.skinHives[2];
            hive.node.getComponent(cc.Sprite).spriteFrame = null;
          }
          if (skin.name == "SkinnyBee") {
            hive.node.parent.getComponent(cc.Sprite).spriteFrame =
              this.skinHives[3];
            hive.node.getComponent(cc.Sprite).spriteFrame = null;
          }
        }
      }
    }
  }

  onTouchDown(event: cc.Event.EventTouch) {
    let touchPos = event.getLocation();

    if (this.currentLevel == 0) {
      PracticeLine.instance.node.active = false;
    }

    this.draw = new cc.Node();
    this.draw.parent = this.node;
    this.draw.zIndex = 100;
    let graphic = this.draw.addComponent(cc.Graphics);
    graphic.lineCap = this.graphic.lineCap;
    graphic.lineJoin = this.graphic.lineJoin;
    this.rb = this.draw.addComponent(cc.RigidBody);
    this.rb.type = cc.RigidBodyType.Static;

    this.linePoints.length = 0;

    this.linePoints.push(touchPos);
    this.lastTouchPos = touchPos;
    this.lastDrawnPos = touchPos;
  }

  onTouchMove(event: cc.Event.EventTouch) {
    this.moved = true;
    let touchPos = event.getLocation();
    let posValid = true;

    for (let i = 0; i < this.blockedObjects.length; i++) {
      let node = this.blockedObjects[i];
      let newBoundBox = node.getBoundingBoxToWorld();
      if (newBoundBox.contains(touchPos)) {
        posValid = false;
      }
    }

    if (!posValid) return;

    if (
      cc.Vec2.distance(touchPos, this.lastTouchPos) >= this.lineMinSegLength &&
      cc.Vec2.distance(touchPos, this.lastDrawnPos) <=
        this.lineMinSegLength * 10
    ) {
      if (!this.drew) {
        let touchNormal = touchPos.sub(this.lastTouchPos).normalize();
        let leftEdgeNormal = Utils.RotateVec2CounterClockwise(touchNormal);
        let rightEdgeNormal = Utils.RotateVec2Clockwise(touchNormal);

        let leftEdgeLast = this.lastTouchPos.add(
          cc.v2(leftEdgeNormal).multiplyScalar(this.lineWidth / 1.5)
        );

        let rightEdgeLast = this.lastTouchPos.add(
          cc.v2(rightEdgeNormal).multiplyScalar(this.lineWidth / 1.5)
        );

        let leftEdgeCurrent = touchPos.add(
          cc.v2(leftEdgeNormal).multiplyScalar(this.lineWidth / 1.5)
        );

        let rightEdgeCurrent = touchPos.add(
          cc.v2(rightEdgeNormal).multiplyScalar(this.lineWidth / 1.5)
        );

        let newPolyCol = this.draw.addComponent(cc.PhysicsPolygonCollider);

        newPolyCol.points = [
          leftEdgeLast,
          leftEdgeCurrent,
          rightEdgeCurrent,
          rightEdgeLast,
        ];

        newPolyCol.restitution = 1;
        newPolyCol.apply();

        this.lastTouchPos = touchPos;
        this.lastDrawnPos = touchPos;
        this.linePoints.push(touchPos);

        LimitBar.instance.fillTheBar();
        if (LimitBar.instance.progress <= 0) {
          this.onTouchUp(event);
        } else {
          this.drawLine();
          SoundController.playNoLoopSound(SoundController.instance.sounds[12]);
        }
      }
    }
  }

  onTouchUp(event: cc.Event.EventTouch) {
    if (!this.drew && this.moved && this.linePoints.length > 1) {
      SoundController.playNoLoopSound(SoundController.instance.sounds[11]);
      SoundController.playLoopSound(SoundController.instance.sounds[1]);
      this.rb.type = cc.RigidBodyType.Dynamic;
      if (this.node.children[0].getChildByName("Stone")) {
        this.node.children[0]
          .getChildByName("Stone")
          .getComponent(cc.RigidBody).type = cc.RigidBodyType.Dynamic;
      }

      StageController.instance.onHunting();
      this.clock.active = true;
      this.startCount = true;
      this.drew = true;
    }
  }

  drawLine() {
    let lineDrawer = this.draw.getComponent(cc.Graphics);
    if (lineDrawer == null) lineDrawer = this.draw.addComponent(cc.Graphics);

    lineDrawer.strokeColor = this.graphic.strokeColor;
    lineDrawer.lineWidth = this.graphic.lineWidth;

    for (let i = 0; i < this.linePoints.length - 1; i++) {
      let p1 = this.linePoints[i];
      let p2 = this.linePoints[i + 1];
      let p1InLocal = lineDrawer.node.parent.convertToNodeSpaceAR(p1);
      let p2InLocal = lineDrawer.node.parent.convertToNodeSpaceAR(p2);
      lineDrawer.moveTo(p1InLocal.x, p1InLocal.y);
      lineDrawer.lineTo(p2InLocal.x, p2InLocal.y);
      lineDrawer.stroke();
    }
  }

  protected update(dt: number): void {
    if (!this.startCount) return;
    this.countdown -= dt;
    this.onStartCountdown();
  }

  onStartCountdown() {
    this.timerLabel.string = Math.floor(this.countdown).toString();

    if (this.countdown > 0 && this.isGameover) {
      this.startCount = false;

      this.scheduleOnce(() => {
        this.onReloadLevel();
      }, 2);
    }
    if (!Dog.instance.attacked && this.countdown <= 1) {
      this.startCount = false;
      this.onLevelCompleted(this.currentLevel);
    }
  }

  onReloadLevel() {
    this.drew = false;
    SoundController.stopSound(SoundController.instance.sounds[1]);
    cc.director.loadScene("gameplay");
  }

  onLevelCompleted(level) {
    SoundController.stopSound(SoundController.instance.sounds[1]);
    SoundController.playNoLoopSound(SoundController.instance.sounds[6]);
    SoundController.playNoLoopSound(SoundController.instance.sounds[13]);
    SoundController.playNoLoopSound(SoundController.instance.sounds[10]);

    this.coinEarned.getComponent(cc.Label).string = `x${this.randomCoin}`;

    this.isCompleted = true;
    StageController.instance.onCompleted();
    if (this.currentLevel + 1 == this.highestLevel) {
      LocalStorage.setHighestLevel((this.highestLevel += 1));
    }
    this.levelComp.active = true;
    this.shadow.active = true;
    let levelName = parseInt(this.prefabToLoad[level].name);

    if (LimitBar.instance.winStar == 3) {
      if (LocalStorage.getStarAtLevel(levelName) >= 3) return;
      LocalStorage.setStarAtLevel(levelName, 3);
    }
    if (LimitBar.instance.winStar == 2) {
      this.rewardSprite.getComponent(cc.Sprite).spriteFrame =
        this.starWinSprites[1];
      if (LocalStorage.getStarAtLevel(levelName) >= 2) return;
      LocalStorage.setStarAtLevel(levelName, 2);
    }
    if (LimitBar.instance.winStar == 1) {
      this.rewardSprite.getComponent(cc.Sprite).spriteFrame =
        this.starWinSprites[0];
      if (LocalStorage.getStarAtLevel(levelName) >= 1) return;
      LocalStorage.setStarAtLevel(levelName, 1);
    }
    this.updateAllStarLabel();
  }

  onClickCollect() {
    LocalStorage.setAllCoin((this.currentCoin += this.randomCoin));
    this.coinLabel.getComponent(
      cc.Label
    ).string = `x${LocalStorage.getAllCoin()}`;
    SoundController.playNoLoopSound(SoundController.instance.sounds[4]);
    SoundController.playNoLoopSound(SoundController.instance.sounds[5]);
    LocalStorage.setCurrentLevel((this.currentLevel += 1));
    this.currentLevel = LocalStorage.getCurrentLevel();
    if (this.currentLevel > 29) return cc.director.loadScene("start");
    this.drew = false;
    GlobalVariables.levelToLoad = this.currentLevel;
  }

  onNextLevel() {
    this.onClickCollect();
    this.scheduleOnce(() => {
      cc.director.loadScene("gameplay");
    }, 1);
  }

  onClickButtonBack() {
    cc.director.loadScene("start");
    cc.audioEngine.playEffect(this.soundClick, false);
    cc.audioEngine.stopAllEffects();
  }

  onClickButtonReplay() {
    cc.audioEngine.playEffect(this.soundClick, false);
    cc.director.loadScene("gameplay");
  }
}
