import { SkyShader } from "../../lib/shaders/SkyShader";
import * as THREE from "three";
import { World } from "./World";
import { IUpdatable } from "../interfaces/IUpdatable";
import { default as CSM } from "three-csm";

export class Sky extends THREE.Object3D implements IUpdatable {
  public updateOrder: number = 5;

  public sunPosition: THREE.Vector3 = new THREE.Vector3();
  public csm: CSM;

  set theta(value: number) {
    this._theta = value;
    this.refreshSunPosition();
  }

  set phi(value: number) {
    this._phi = value;
    this.refreshSunPosition();
    this.refreshHemiIntensity();
  }

  private _phi: number = 50;
  private _theta: number = 145;

  private hemiLight: THREE.HemisphereLight;
  private maxHemiIntensity: number = 0.9;
  private minHemiIntensity: number = 0.3;

  private skyMesh: THREE.Mesh;
  private skyMaterial: THREE.ShaderMaterial;

  private world: World;

  constructor(world: World) {
    super();

    this.world = world;

    // Sky material
    this.skyMaterial = new THREE.ShaderMaterial({
      uniforms: THREE.UniformsUtils.clone(SkyShader.uniforms),
      fragmentShader: SkyShader.fragmentShader,
      vertexShader: SkyShader.vertexShader,
      side: THREE.BackSide,
    });

    // Mesh
    this.skyMesh = new THREE.Mesh(
      new THREE.SphereBufferGeometry(300, 24, 12),
      this.skyMaterial
    );
    this.attach(this.skyMesh);

    // Ambient light
    this.hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 1.0);
    this.refreshHemiIntensity();
    this.hemiLight.color.setHSL(0.59, 0.4, 0.6);
    this.hemiLight.groundColor.setHSL(0.095, 0.2, 0.75);
    this.hemiLight.position.set(0, 50, 0);
    this.world.graphicsWorld.add(this.hemiLight);

    // Legacy
    let splitsCallback = (amount, near, far) => {
      let arr = [];

      for (let i = amount - 1; i >= 0; i--) {
        arr.push(Math.pow(1 / 4, i));
      }

      return arr;
    };

    this.csm = new CSM({
      fov: 80,
      far: world.camera.far, // maxFar
      lightIntensity: 0.4,
      cascades: 0,
      shadowMapSize: 2048,
      camera: world.camera,
      parent: world.graphicsWorld,
      mode: "uniform",
      //customSplitsCallback: splitsCallback,
    });
    this.csm.fade = true;

    this.refreshSunPosition();

    world.graphicsWorld.add(this);
    world.registerUpdatable(this);
  }

  public update(timeScale: number): void {
    this.position.copy(this.world.camera.position);
    this.refreshSunPosition();

    this.csm.update(this.world.camera.matrix);
    this.csm.lightDirection = new THREE.Vector3(
      -this.sunPosition.x,
      -this.sunPosition.y,
      -this.sunPosition.z
    ).normalize();
  }

  public refreshSunPosition(): void {
    const sunDistance = 10;

    this.sunPosition.x =
      sunDistance *
      Math.sin((this._theta * Math.PI) / 180) *
      Math.cos((this._phi * Math.PI) / 180);
    this.sunPosition.y = sunDistance * Math.sin((this._phi * Math.PI) / 180);
    this.sunPosition.z =
      sunDistance *
      Math.cos((this._theta * Math.PI) / 180) *
      Math.cos((this._phi * Math.PI) / 180);

    this.skyMaterial.uniforms.sunPosition.value.copy(this.sunPosition);
    this.skyMaterial.uniforms.cameraPos.value.copy(this.world.camera.position);
  }

  public refreshHemiIntensity(): void {
    this.hemiLight.intensity =
      this.minHemiIntensity +
      Math.pow(1 - Math.abs(this._phi - 90) / 90, 0.25) *
        (this.maxHemiIntensity - this.minHemiIntensity);
  }

  public destroy() {
    this.csm.remove();
    this.csm = undefined;
    this.hemiLight = undefined;
    this.sunPosition = undefined;
    this._phi = undefined;
    this._theta = undefined;
    this.maxHemiIntensity = undefined;
    this.minHemiIntensity = undefined;
    this.skyMesh = undefined;
    this.skyMaterial = undefined;
    this.world = undefined;
    this.updateOrder = undefined;
  }
}
