const { ccclass, property } = cc._decorator;

const TRANSITION_TIME = 2;

@ccclass
export default class Transition extends cc.Component {
  @property() time: number = 0;
  @property(cc.Material) materials = [];

  private _inited = false;
  private _texture1: cc.Texture2D;
  private _texture2: cc.Texture2D;
  private _sprite: cc.Sprite;
  private _spriteNode: cc.Node;
  private _camera: cc.Camera;
  private _cameraNode: cc.Node;
  private _spriteMaterial: cc.Material;
  private _onLoadFinished = () => {};

  loading = false;

  private materialIndex = 0;

  onLoad() {
    cc.game.addPersistRootNode(this.node);
  }

  start() {
    this.init();
    cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, this._onLoadFinished, this);
    cc.director.on('switchSceneByTransition', this.switchSceneByTransition.bind(this));
  }

  switchSceneByTransition(index) {
    this.materialIndex = Number(index || 0);
    this.updateSpriteMaterial();
    setTimeout(() => this.loadScene('Scene/Home/Home', 'Canvas/Main Camera', 'Canvas/Main Camera'));
  }

  init() {
    if (this._inited) return;
    this._inited = true;

    this.time = 0;

    this._texture1 = this.createTexture();
    this._texture2 = this.createTexture();

    let spriteNode = cc.find('TRANSITION_SPRITE', this.node);
    if (!spriteNode) {
      spriteNode = new cc.Node('TRANSITION_SPRITE');
      this._sprite = spriteNode.addComponent(cc.Sprite);
      spriteNode.parent = this.node;
    } else {
      this._sprite = spriteNode.getComponent(cc.Sprite);
    }
    let spriteFrame = new cc.SpriteFrame();
    spriteFrame.setTexture(this._texture1);
    this._sprite.spriteFrame = spriteFrame;

    spriteNode.active = false;
    spriteNode.scaleY = -1;
    this._spriteNode = spriteNode;

    let cameraNode = cc.find('TRANSITION_CAMERA', this.node);
    if (!cameraNode) {
      cameraNode = new cc.Node('TRANSITION_CAMERA');
      this._camera = cameraNode.addComponent(cc.Camera);
      cameraNode.parent = this.node;
    } else {
      this._camera = cameraNode.getComponent(cc.Camera);
    }
    cameraNode.active = false;
    this._cameraNode = cameraNode;

    // @ts-ignore
    this.node.groupIndex = cc.Node.BuiltinGroupIndex.DEBUG - 1;
    this._camera.cullingMask = 1 << this.node.groupIndex;

    this.updateSpriteMaterial();
  }

  updateSpriteMaterial() {
    if (!this._sprite) return;

    let newMaterial = cc.MaterialVariant.create(this.materials[this.materialIndex], this._sprite);
    newMaterial.setProperty('texture', this._texture1);
    newMaterial.setProperty('texture2', this._texture2);
    if (this.materialIndex == 6)
      newMaterial.setProperty('screenSize', new Float32Array([this._texture2.width, this._texture2.height]));

    this._sprite.setMaterial(0, newMaterial);
    this._spriteMaterial = newMaterial;
  }

  _onSceneLaunched() {
    window['camera'] = cc.Camera.cameras;
  }

  loadScene(sceneUrl, fromCameraPath, toCameraPath) {
    this.scheduleOnce(() => {
      cc.director.preloadScene(sceneUrl, null, () => {
        this._loadScene(
          sceneUrl,
          fromCameraPath,
          toCameraPath,
          () => {},
          () => {}
        );
      });
    });
    return true;
  }

  createTexture() {
    let texture = new cc.RenderTexture();
    // @ts-ignore
    texture.initWithSize(cc.visibleRect.width, cc.visibleRect.height, cc.gfx.RB_FMT_D24S8);
    return texture;
  }

  _loadScene(sceneUrl, fromCameraPath, toCameraPath, onSceneLoaded, onTransitionFinished) {
    this.init();
    this._spriteNode.active = true;
    this._cameraNode.active = true;
    let fromCameraNode = cc.find(fromCameraPath);
    let fromCamera = fromCameraNode && (fromCameraNode.getComponent(cc.Camera) as any);
    if (!fromCamera) {
      cc.warn(`Can not find fromCamera with path ${fromCameraPath}`);
      return;
    }
    let originTargetTexture1 = fromCamera.targetTexture;
    fromCamera.cullingMask &= ~this._camera.cullingMask;
    fromCamera.targetTexture = this._texture1;
    fromCamera.render(cc.director.getScene());
    fromCamera.targetTexture = originTargetTexture1;

    return cc.director.loadScene(sceneUrl, (arg) => {
      onSceneLoaded && onSceneLoaded(...arg);

      let toCameraNode = cc.find(toCameraPath);
      let toCamera = toCameraNode && (toCameraNode.getComponent(cc.Camera) as any);

      if (!toCamera) {
        cc.warn(`Can not find toCamera with path ${toCameraPath}`);
        return;
      }
      toCamera.cullingMask &= ~this._camera.cullingMask;
      let originTargetTexture2 = toCamera.targetTexture;
      toCamera.targetTexture = this._texture2;
      toCamera.render(cc.director.getScene());

      this._camera.depth = toCamera.depth;
      this._camera.clearFlags = toCamera.clearFlags;

      this._onLoadFinished = () => {
        toCamera.targetTexture = originTargetTexture2;

        this._spriteNode.active = false;
        this._cameraNode.active = false;
        onTransitionFinished && onTransitionFinished();
      };

      this.time = 0;
      this.loading = true;
    });
  }

  update(dt) {
    if (this.loading) {
      this.time += dt;
      if (this.time >= TRANSITION_TIME) {
        this.time = TRANSITION_TIME;
        this.loading = false;

        this._onLoadFinished && this._onLoadFinished();
        this._onLoadFinished = null;
      }
      this._spriteMaterial.setProperty('time', this.time / TRANSITION_TIME);
    }
  }
}
