import * as RE from 'rogue-engine';
import * as THREE from 'three';

import RapierCollider from '../Colliders/RapierCollider';
import RAPIER from '@dimforge/rapier3d-compat';
import RapierBody from '../RapierBody.re';
import RogueRapier from '@RE/RogueEngine/rogue-rapier/Lib/RogueRapier';

export default class RapierBodyWireframe extends RE.Component {
  static isEditorComponent = true;

  selectedObjects: THREE.Object3D[] = [];
  colliders: (RapierCollider | RapierBody)[] = [];

  lines = new THREE.LineSegments(
    new THREE.BufferGeometry(),
    new THREE.LineBasicMaterial({ color: new THREE.Color("#00FF00") })
  );

  world: RAPIER.World;
  initializedPhysics = false;

  private handleOnComponentAdded = { stop: () => { } };
  private handleOnComponentRemoved = { stop: () => { } };

  private handleOnPlay = { stop: () => { } };

  private resetHandler = (component: RE.Component) => {
    component instanceof RapierCollider && this.setupImpostors();
  }

  async initPhysics() {
    await RAPIER.init();
  }

  start() {
    this.initializedPhysics = false;

    this.lines.geometry.computeBoundingSphere();
    this.lines.frustumCulled = false;

    this.initPhysics().then(() => {
      this.world = new RAPIER.World({x: 0, y: 0, z: 0});
      RogueRapier.world = this.world;
      this.initializedPhysics = true;
    });

    RE.App.currentScene.remove(this.lines);

    this.lines.userData.isEditorObject = true;
    RE.App.currentScene.add(this.lines);

    this.handleOnComponentAdded.stop();
    this.handleOnComponentRemoved.stop();
    this.handleOnPlay.stop();

    this.handleOnComponentAdded = RE.onComponentAdded(this.resetHandler);
    this.handleOnComponentRemoved = RE.onComponentRemoved(this.resetHandler);

    this.handleOnPlay = RE.Runtime.onPlay(() => {
      this.handleOnComponentAdded.stop();
      this.handleOnComponentRemoved.stop();
    });
  }

  resetComponents() {
    this.selectedObjects.forEach(selected => {
      if (!selected) return;
      selected.traverse(object => {
        const objComponents = RE.components[object.uuid];

        if (!objComponents) return;

        objComponents.forEach(component => {
          if (component instanceof RapierBody || component instanceof RapierCollider) {
            component.initialized = false;
          }
        });
      });
    });
  }

  afterUpdate() {
    this.lines.visible = false;
    if (!this.initializedPhysics) return;

    const selectedObjects = window["rogue-editor"].Project.selectedObjects as THREE.Object3D[];

    if (!this.arraysAreEqual(selectedObjects, this.selectedObjects)) {
      this.selectedObjects = selectedObjects.slice(0);
      this.resetComponents();
      this.setupImpostors();
    }

    if (!this.world || (this.world && this.world.bodies.len() === 0)) {
      return;
    }

    this.updateImpostors();
  }

  private updateImpostors() {
    this.lines.visible = true;
    this.world.step();

    const flagForRemoval: (RapierCollider | RapierBody)[] = [];

    this.colliders.forEach(component => {
      if (component instanceof RapierCollider && component.object3d && component.bodyComponent) {
        if (!component.enabled) {
          component.initialized = false;
          flagForRemoval.push(component);
          return;
        }
        const pos = component.bodyComponent.object3d.position;
        const rot = component.bodyComponent.object3d.quaternion;
        component.body.setTranslation(new RAPIER.Vector3(pos.x, pos.y, pos.z), false);
        component.body.setRotation(new RAPIER.Quaternion(rot.x, rot.y, rot.z, rot.w), false);
        component.setColliderRot();
        component.setColliderPos();
      }
    });

    flagForRemoval.forEach(component => this.colliders.splice(this.colliders.indexOf(component), 1))

    let buffers = this.world.debugRender();

    this.lines.geometry.setAttribute(
      "position",
      new THREE.BufferAttribute(buffers.vertices, 3),
    );

    this.lines.geometry.setAttribute(
      "color",
      new THREE.BufferAttribute(buffers.colors, 4),
    );
  }

  private async cleanupImpostors() {
    RogueRapier.world && RogueRapier.world.bodies.forEach(body => RogueRapier.world.removeRigidBody(body));
    this.lines.visible = false;
    RE.App.currentScene.remove(this.lines);

    this.colliders = [];
  }

  private async setupImpostors() {
    await this.cleanupImpostors();

    this.selectedObjects.forEach(selected => {
      selected.traverse(object => {
        const objComponents = RE.components[object.uuid];

        if (!objComponents) return;

        objComponents.forEach(component => {
          if (component instanceof RapierBody) {
            component.init();
            this.colliders.push(component);
          }

          if (component instanceof RapierCollider) {
            const bodyComponent = component.getBodyComponent(component.object3d);

            if (bodyComponent) {
              bodyComponent.init();
            }

            component.init();
            // component.collider && 
            // component.collider.setSensor(true);
            this.colliders.push(component);
          }
        });
      });
    });

    RE.App.currentScene.add(this.lines);
  }

  private arraysAreEqual(array1: any[], array2: any[]) {
    if (array1.length !== array2.length) return false;

    return array1.every((element, i) => {
      return array2[i] === element;
    });
  }

  onBeforeRemoved() {
    RE.App.currentScene.remove(this.lines);
    this.handleOnComponentAdded.stop();
    this.handleOnComponentRemoved.stop();
    this.handleOnPlay.stop();
    this.cleanupImpostors();
  }
}

RE.registerComponent(RapierBodyWireframe);
