// from https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80

import * as TWEEN from "@tweenjs/tween.js";
import * as THREE from "three";

import { createCamera } from "./components/camera.js";
import { createLights } from "./components/lights.js";
import { createScene } from "./components/scene.js";
import { loadLandmarks } from "./components/objects/landmarks.js";
import { createTerrain } from "./components/objects/terrain.js";
import { Loop } from "./systems/Loop.js";
import { Resizer } from "./systems/Resizer.js";
import { createControls } from "./systems/controls.js";
import { createRenderer } from "./systems/renderer.js";

const COLOR1 = "#dddddd";
const COLOR2 = "#0055aa";

class World {
  constructor(container, vue) {
    this.PLATFORM_BORDER = 5;
    this.PLATFORM_EDGE_FOR_UNKNOWNS = 10;
    this.PLATFORM_SIZE = 100; // note that the loadLandmarks calculations may still assume 100

    this.update = this.update.bind(this);

    this.vue = vue;

    // Instances of camera, scene, and renderer
    this.camera = createCamera();
    this.scene = createScene(COLOR2);
    this.renderer = createRenderer();

    // necessary for models, says https://threejs.org/docs/index.html#examples/en/loaders/GLTFLoader
    this.renderer.outputColorSpace = THREE.SRGBColorSpace;

    this.light = null;
    this.lights = [];
    this.bushes = [];

    // Initialize Loop
    this.loop = new Loop(this.camera, this.scene, this.renderer);

    container.append(this.renderer.domElement);

    // Orbit Controls
    const controls = createControls(this.camera, this.renderer.domElement);

    // Light Instance, with optional light helper
    const { light } = createLights(COLOR1);

    // Terrain Instance
    const terrain = createTerrain({
      color: COLOR1,
      height: this.PLATFORM_SIZE + this.PLATFORM_BORDER * 2,
      width:
        this.PLATFORM_SIZE +
        this.PLATFORM_BORDER * 2 +
        this.PLATFORM_EDGE_FOR_UNKNOWNS * 2,
    });

    this.loop.updatables.push(controls);
    this.loop.updatables.push(light);
    this.loop.updatables.push(terrain);

    this.scene.add(light, terrain);

    loadLandmarks(vue, this, this.scene, this.loop);

    requestAnimationFrame(this.update);

    // Responsive handler
    const resizer = new Resizer(container, this.camera, this.renderer);
    resizer.onResize = () => {
      this.render();
    };
  }

  update(time) {
    TWEEN.update(time);
    this.lights.forEach((light) => {
      light.updateMatrixWorld();
      light.target.updateMatrixWorld();
    });
    this.lights.forEach((bush) => {
      bush.updateMatrixWorld();
    });
    requestAnimationFrame(this.update);
  }

  render() {
    // draw a single frame
    this.renderer.render(this.scene, this.camera);
  }

  // Animation handlers
  start() {
    this.loop.start();
  }

  stop() {
    this.loop.stop();
  }

  setExposedWorldProperties(key, value) {
    this.vue.setWorldProperty(key, value);
  }
}

export { World };
