import { Mesh } from './'
import { Node, MeshInstance } from '../components'
import { Material } from './material'

import { mat4 } from 'gl-matrix'

import Resource from '../resource'

let meshCache = {}

// Parse an assimp2json formatted mesh file
// Supports multiple geometries
async function assimp2json (gl, file, dat) {
  let cleaned = []
  let materials = []
  for (let mi in dat.meshes) {
    let mesh = dat.meshes[mi]
    let material

    if (mesh.materialindex != null && dat.materials && dat.materials.length) {
      // Ensure we don't re-create materials with the same index
      if (materials[mesh.materialindex]) {
        material = materials[mesh.materialindex]
      } else {
        // Load a new material
        material = new Material()
        let matdata = dat.materials[mesh.materialindex].properties

        // Parse material information
        for (let pi in matdata) {
          let property = matdata[pi]
          if (!property || !property.key) continue
          if (property.key === '?mat.name') material.name = property.value
          else if (property.key.indexOf('$clr.') === 0) {
            let dproperty = property.key.substr(5)
            switch (dproperty) {
              case 'specular':
              case 'diffuse':
              case 'shininess':
              case 'ambient':
              case 'reflective':
                material[dproperty] = property.value
                break
            }
          } else if (property.key.indexOf('$tex.file') === 0) {
            if (!material.textures) {
              material.textures = []
            }

            material.textures.push(property.value)
          }
        }

        materials[mesh.materialindex] = material
      }
    }

    cleaned.push({
      vertices: mesh.vertices,
      indices: [].concat.apply([], mesh.faces),
      uv: mesh.texturecoords ? mesh.texturecoords[0] : null,
      normals: mesh.normals ? mesh.normals : null,
      material
    })
  }

  // Load everything
  let loadComplete = []
  for (let i in cleaned) {
    let meshdata = cleaned[i]
    let mesh = Mesh.construct(gl, meshdata.vertices, meshdata.indices,
      meshdata.uv, meshdata.normals)

    // Initialize the material's texture if present
    if (meshdata.material) {
      mesh.material = meshdata.material

      // Ensure all textures get loaded before finishing
      if (meshdata.material.textures) {
        await meshdata.material.loadTextures(gl)
      }
    }

    loadComplete.push(mesh)
  }

  let finished = []
  let lastTransform = dat.rootnode.transformation
  function setChildren (parent, chMeshes, last) {
    if (!chMeshes.meshes) {
      if (chMeshes.children) {
        for (let j in chMeshes.children) {
          setChildren(null, chMeshes.children[j], chMeshes)
        }
      }
      return
    }

    // Transpose the mesh's transform
    let transform = chMeshes.transformation

    let meshIndex = chMeshes.meshes[0]
    let mesh = loadComplete[meshIndex]
    let meshInstance = new MeshInstance(mesh)

    meshInstance.mesh = mesh

    if (chMeshes.children) {
      for (let i in chMeshes.children) {
        if (!chMeshes.children[i].meshes) continue
        setChildren(meshInstance, chMeshes.children[i], chMeshes)
      }
    }

    meshInstance.name = chMeshes.name

    if (parent == null) {
      finished.push(meshInstance)
    } else {
      parent.children.push(meshInstance)
      meshInstance.parent = parent
    }

    mat4.transpose(transform, transform)
    meshInstance.worldTransform = transform
    meshInstance.updateTransform()
  }

  setChildren(null, dat.rootnode)

  if (!finished.length) throw new Error('Failed to build mesh.')

  // Cache the mesh
  meshCache[file] = finished

  let returnType
  if (finished.length > 1) {
    returnType = new Node()
    returnType.addChild(finished)
    mat4.transpose(lastTransform, lastTransform)
    returnType.worldTransform = lastTransform
    returnType.updateTransform()
  } else {
    returnType = finished[0]
  }

  return returnType
}

// Parse a collada mesh
async function collada (gl, file, dat) {
  // TODO...
}

async function loadMesh (gl, file) {
  file = '/assets/models/' + file + '.json'

  // Ensure each mesh file is loaded only once
  if (meshCache[file]) {
    if (meshCache[file].length <= 1) return meshCache[file][0]
    let cached = new Node()
    cached.addChild(meshCache[file])
    return cached
  }

  let dat = await Resource.GET({ type: 'json', url: file })

  // Recognize a assimp2json file format
  if (dat['__metadata__'] && dat['__metadata__'].format === 'assimp2json') {
    if (!dat.meshes) throw new Error('No geometries found in file ' + file)
    return assimp2json(gl, file, dat)
  }

  throw new Error('Unsupported mesh format.')
}

export default loadMesh
