import {
  SphereGeometry,
  Texture,
  Mesh,
  Object3D,
  TextureLoader,
  Color,
  DirectionalLight,
  ShaderMaterial,
  MeshPhongMaterial,
} from 'three';

import { plainText as vertexShader } from './shaders/earth.vert';
import { plainText as fragmentShader } from './shaders/earth.frag';
import { Atmosphere } from './atmosphere';

export class Globe extends Object3D {
  private sphere?: SphereGeometry;
  private clouds = new SphereGeometry(1.02, 64, 64);
  public atmosphere = new Atmosphere(1, 1.05);
  public earthMaterial?: ShaderMaterial;
  public earthMesh?: Mesh;

  private loader = new TextureLoader();

  constructor(private planetRadius: number) {
    super();
  }

  set radius(radius: number) {
    this.planetRadius = radius;
    this.atmosphere.planetRadius = radius;
    this.earthMaterial && this.atmosphere.setUniforms(this.earthMaterial);
    this.createMesh();
  }
  get radius() {
    return this.planetRadius;
  }

  public async loadTexture(name: string) {
    return new Promise<Texture>((resolve, reject) => {
      this.loader.load(`./${name}`, resolve, undefined, reject);
    });
  }

  public async loadArray(names: string[]) {
    return Promise.all(names.map((name) => this.loadTexture(name)));
  }

  public async initialize(light: DirectionalLight) {
    const textures = await this.loadArray([
      'earth/2k_earth_daymap.jpg',
      'earth/2k_earth_nightmap.jpg',
      'earth/2k_earth_clouds.jpg',
      'earth/2k_earth_normal_map.jpg',
      'earth/2k_earth_specular_map.jpg',
    ]);

    this.earthMaterial = new ShaderMaterial({
      fragmentShader,
      vertexShader,
      uniforms: {
        light: { value: light.position },
        texture0: { value: textures[0] },
        texture1: { value: textures[1] },
        normalmap: { value: textures[3] },
        specularMap: { value: textures[4] },
        shininess: { value: 1.0 },
        specular: { value: new Color('gray') },
      },
    });

    this.atmosphere.setUniforms(this.earthMaterial);
    this.atmosphere.setLight(light);
    this.createMesh();
    this.add(this.atmosphere);
  }

  public tick() {}

  private createMesh() {
    if (this.sphere) {
      this.sphere.dispose();
      this.earthMesh && this.remove(this.earthMesh);
    }
    this.sphere = new SphereGeometry(this.planetRadius, 128, 128);
    this.earthMesh = new Mesh(this.sphere, this.earthMaterial);
    this.add(this.earthMesh);
  }
}
