import Engine from './engine'
import Camera from './engine/camera'
import loadMesh from './engine/mesh/loader'

import { randomInt } from './engine/utility'

import { Environment } from './engine/environment'
import { LODTerrain } from './engine/components/terrain/lod'
import { Skybox } from './engine/components/skybox'
import { WaterRenderer, WaterTile } from './engine/components/water'
import { Particle, ParticleTexture, ParticleSystem, ParticleRenderer } from './engine/components/particles'
import { SimplexHeightMap } from './engine/components/terrain/heightmap'
import { Material, Texture } from './engine/mesh/material'
import { GUIRenderer, GUIImage, Dim4 } from './engine/gui'
import { FontRenderer, GUIText, Font } from './engine/gui/font'
import { VoxelWorld, VoxelGenerator } from './engine/voxel'

import { CubePlanet, PlanetGenerator } from './engine/components/planet'

let game = Engine
let env = new Environment()
let gui = new GUIRenderer()
let fnt = new FontRenderer()
let prt = new ParticleRenderer()

async function pipeline () {
  let entity = await loadMesh(game.gl, 'test')
  let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false)
  let skyboxShader = await game.shaders.createShaderFromFiles(game.gl, 'skybox', false)

  entity.setRotation([0.0, 0.0, -90.0])

  // Initialize water
  // let waterRenderer = new WaterRenderer()
  // let water = new WaterTile([100.0, 0.0, 100.0], 100.0)
  // await waterRenderer.initialize(game)
  // await waterRenderer.useDUDVMap(game.gl, 'dudv')
  // await waterRenderer.useNormalMap(game.gl, 'normalmap')
  let arialFont = await Font.fromFile('arial')
  await arialFont.loadTextures(game.gl)

  // Initialize GUI
  await gui.initialize(game)
  await fnt.initialize(game)

  // Initialize particles
  await prt.initialize(game)
  let particleSystem = new ParticleSystem(prt)

  // Particle texture atlas
  let particleTexture = new ParticleTexture(await Texture.fromFile(game.gl, 'particleAtlas.png'), 4)

  let itms = [
    new GUIImage(await Texture.fromFile(game.gl, 'noisy.png', false, game.gl.LINEAR),
      new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0))
  ]
  // Nesting test
  itms[0].addChild(new GUIText('', arialFont, 0.8, new Dim4(0.2, 0.0, 0.0, 0.0), new Dim4(1.0, 0.0, 0.3, 0.0), false))
  itms[0].children[0].color = [0.0, 0.2, 1.0]

  // Create a height map based on OpenSimplex noise
  let hmap = new SimplexHeightMap(1, 1, 256, 50)

  // Create a terrain instance
  // let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4)

  // Terrain material
  let material = new Material(['grass-1024.jpg'])
  await material.loadTextures(game.gl)

  // test code
  for (let i in entity.children) {
    entity.children[i].mesh.material = material
  }

  // Set generator and material for terrain
  // terrain.setGenerator(hmap)
  // terrain.setMaterial(material)

  // Create and initialize the camera
  let cam = new Camera([-1300.0, 1325.0, -1300.0], [0.8, -0.6, 0.0])
  cam.updateProjection(game.gl)

  // Create skybox
  let skybox = new Skybox('skybox', cam.farPlane / 2)

  // Load textures and generate a mesh
  await skybox.initialize(game.gl)

  // Voxel test
  // let voxgen = new VoxelGenerator(hmap, material)
  // let block = new VoxelWorld(voxgen)

  // Planet test
  let planet = new CubePlanet([0.0, 0.0, 0.0], new PlanetGenerator(16, 1000, hmap))

  // Update function for camera and terrain
  let fpsTimer = 0
  game.addUpdateFunction(function (dt) {
    if (game.input.isDown('w')) {
      cam.processKeyboard(0, dt)
    } else if (game.input.isDown('s')) {
      cam.processKeyboard(1, dt)
    }

    if (game.input.isDown('a')) {
      cam.processKeyboard(2, dt)
    } else if (game.input.isDown('d')) {
      cam.processKeyboard(3, dt)
    }

    // Panning
    if (game.input.mouseMoved && game.input.mouse.btn0) {
      cam.processMouseMove(game.input.mouseOffset)
    }

    // Particles
    particleSystem.update(dt, -50)

    if (game.input.isDown('y')) {
      let velocity = 20
      for (let i = 0; i < 360; i += 15) {
        let rad1 = i * Math.PI / 180
        let x1 = (Math.cos(rad1) * velocity) + randomInt(-5, 5)
        let y1 = (Math.sin(rad1) * velocity) + randomInt(-5, 5)
        particleSystem.add(new Particle(particleTexture, [0.0, 0.0, 0.0], [x1, randomInt(-velocity, velocity), y1], 0.2, 2, randomInt(0, 360), randomInt(0.1, 1)))
      }
    }

    // Update detail levels
    // terrain.update(game.gl, cam)
    // terrain.updateLODMesh(game.gl)

    // Ripple water
    // waterRenderer.update(dt)

    // Update voxel chunk
    // voxgen.update(dt)
    // block.update(game.gl, cam, dt)

    // Set text to FPS
    fpsTimer++
    if (fpsTimer === 10) {
      itms[0].children[0].setText(game.fps + ' fps')
      fpsTimer = 0
    }

    // Update planet LOD
    planet.update(cam, dt)
  })

  function drawEverything (gl) {
    game.prepare()

    // Draw the skybox
    skyboxShader.use(gl)
    skybox.draw(gl, skyboxShader, cam)

    // Use terrain shader
    terrainShader.use(gl)
    // Set environment variables in shader
    env.draw(gl, terrainShader)
    // Set the viewport uniforms
    cam.draw(gl, terrainShader)
    // Draw terrain
    // terrain.draw(gl, terrainShader)

    // entity.draw(gl, terrainShader)

    // block.draw(gl, terrainShader)

    material.apply(gl, terrainShader)
    planet.draw(gl, terrainShader)
  }

  // Render function for the triangle
  game.addRenderFunction(function (gl) {
    // waterRenderer.reflect(gl, cam, drawEverything)
    // waterRenderer.refract(gl, cam, drawEverything)

    drawEverything(gl)

    // waterRenderer.draw(gl, [water], cam, env.sun)

    // Draw particles
    particleSystem.draw(gl, cam)

    // Draw GUIs
    gui.draw(gl, cam, itms)
    fnt.draw(gl, cam, itms)
  })

  game.startGameLoop()
}

// Start the game, catch any errors thrown
pipeline().catch(function (e) {
  console.error(e)
})
