import { Color, Mesh, Matrix4, Frustum } from 'three'
import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js'
import swal from 'sweetalert'
import { AssetLoader } from './AssetLoader.js'
import { Setup } from './Setup.js'

class Game {
  constructor () {
    this.production = window.PRODUCTION
    if (this.production) {
      console.log('Running in production mode')
    } else {
      console.log('Running in development mode')
    }
    this.fov = {
      normal: 70,
      sprint: 80
    }
    this.al = new AssetLoader()
    this.toxelSize = 27
    this.dimension = null
    this.flying = false
    this.playerPos = [0, 0, 0]
    this.dimBg = {
      'minecraft:overworld': [165 / 255, 192 / 255, 254 / 255],
      'minecraft:the_end': [1 / 255, 20 / 255, 51 / 255],
      'minecraft:the_nether': [133 / 255, 40 / 255, 15 / 255],
      'minecraft:end': [1 / 255, 20 / 255, 51 / 255],
      'minecraft:nether': [133 / 255, 40 / 255, 15 / 255]
    }
    this.headHeight = 17
    this.gamemode = null
    this.mouse = false
  }

  async init () {
    await this.al.init()
    Setup(this)
    this.socket.on('blockUpdate', (block) => {
      this.world.setBlock(block[0], block[1] + 16, block[2], block[3])
    })
    this.socket.on('spawn', (yaw, pitch) => {
      console.log('Player spawned')
      this.ls.hide()
      this.camera.rotation.y = yaw
      this.camera.rotation.x = pitch
    })
    this.socket.on('players', (players) => {
      this.tl.update(players)
    })
    this.socket.on('dimension', (dim) => {
      this.dimension = dim
      console.log(`Player dimension has been changed: ${dim}`)
      this.world.resetWorld()
      if (this.dimBg[dim] === undefined) {
        dim = 'minecraft:overworld'
      }
      const bg = this.dimBg[dim]
      this.scene.background = new Color(...bg)
      this.distanceBasedFog.color.x = bg[0]
      this.distanceBasedFog.color.y = bg[1]
      this.distanceBasedFog.color.z = bg[2]
      this.distanceBasedFog.color.w = 1

      this.ls.show('Loading terrain...')
    })
    this.socket.on('mapChunk', (sections, biomes, x, z) => {
      this.world.computeSections(sections, biomes, x, z)
    })
    this.socket.on('game', (gameData) => {
      this.inv_bar.updateGamemode(gameData.gameMode)
    })
    this.socket.on('hp', (points) => {
      this.inv_bar.setHp(points)
    })
    this.socket.on('inventory', (inv) => {
      this.inv_bar.updateInv(inv)
    })
    this.socket.on('food', (points) => {
      this.inv_bar.setFood(points)
    })
    this.socket.on('msg', (msg) => {
      this.chat.log(msg)
    })
    this.socket.on('kicked', (reason) => {
      document.exitPointerLock()
      console.log(reason)
      reason = JSON.parse(reason)
      swal({
        title: "You've been kicked!",
        text:
          reason.extra !== undefined
            ? reason.extra[0].text
            : reason.text,
        icon: 'error',
        button: 'Rejoin'
      }).then(function () {
        document.location.reload()
      })
    })
    this.socket.on('xp', (xp) => {
      this.inv_bar.setXp(xp.level, xp.progress)
    })
    this.socket.on('move', (pos) => {
      this.playerPos = [pos.x - 0.5, pos.y, pos.z - 0.5]
      const to = {
        x: pos.x - 0.5,
        y: pos.y + this.headHeight,
        z: pos.z - 0.5
      }
      new TWEEN.Tween(this.camera.position)
        .to(to, 100)
        .easing(TWEEN.Easing.Quadratic.Out)
        .start()
    })
    this.socket.on('entities', (entities) => {
      this.ent.update(entities)
    })
    this.socket.on('diggingCompleted', () => {
      this.bb.done = true
    })
    this.socket.on('digTime', (time) => {
      this.bb.startDigging(time)
    })
    setInterval(() => {
      if (this.params.frustumtest) {
        const frustum = new Frustum()
        const cameraViewProjectionMatrix = new Matrix4()

        this.camera.updateMatrixWorld()
        this.camera.matrixWorldInverse.copy(this.camera.matrixWorld).invert()
        cameraViewProjectionMatrix.multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)
        frustum.setFromProjectionMatrix(cameraViewProjectionMatrix)

        this.scene.traverse((node) => {
          if (node instanceof Mesh) {
            if (frustum.intersectsObject(node)) {
              node.visible = true
            } else {
              node.visible = false
            }
          }
        })
      }
    }, 2000)
    return this.animate()
  }

  animate () {
    try {
      this.stats.begin()
      this.render()
      this.stats.end()
    } catch (e) {
      this.render()
    }

    window.requestAnimationFrame(() => {
      this.animate()
    })
  }

  render () {
    const width = window.innerWidth
    const height = window.innerHeight
    if (this.canvas.width !== width || this.canvas.height !== height) {
      this.canvas.width = width
      this.canvas.height = height
      this.renderer.setSize(width, height, false)
      this.camera.aspect = width / height
      this.camera.updateProjectionMatrix()
    }
    this.bb.updatePos(() => {
      if (this.bb.isDigging) {
        this.bb.stopDigging()
      }
      if (this.mouse && this.bb.done) {
        return this.bb.digRequest()
      }
    })
    this.world.updateChunksAroundPlayer(this.params.chunkdist)
    this.inv_bar.updateItems()
    this.distanceBasedFog.update()
    TWEEN.update()
    if (!this.production) {
      this.drawcalls.update(this.renderer.info.render.calls, 100)
    }
    if (this.eh.gameState === 'inventory') {
      this.pii.render()
    }

    this.renderer.render(this.scene, this.camera)
  }
}

window.onload = () => {
  const game = new Game()
  game.init()
}