struct ChunkMeshBuilder {
  public static func build(world: World, chunk: Chunk) -> Mesh<VertexPositionNormalColorTexcoord, UInt16> {
    var vertices = [VertexPositionNormalColorTexcoord]()
    var indices = [UInt16]()
    chunk.forEach { block, position in
      if case .solid(let color) = block.type {
        for side in [ Side.left, .right, .down, .up, .back, .front ] {
          let globalPos = chunk.origin &+ position
          if case .air = world.getBlock(at: globalPos.offset(by: side)).type {
            //FIXME: use 32 bit indices for really big chunks
            let orig = UInt16(truncatingIfNeeded: vertices.count)
            vertices.append(contentsOf: cubeVertices[side]!.map {
              .init(
                position: SIMD3(position) + $0.position,
                normal: $0.normal,
                color: SIMD4(Color<Float>(color).pow(2.2)),  //FIXME: Better sRGB approximation when I can be bothered
                texCoord: $0.texCoord)
            })
            indices.append(contentsOf: sideIndices.map { orig + $0 })
          }
        }
      }
    }

    return .init(vertices: vertices, indices: indices)
  }
}

fileprivate let cubeVertices: [Side: [VertexPositionNormalTexcoord]] = [
  .back: [
    .init(position: .init(0, 0, 1), normal: .back, texCoord: .init(0, 0)),
    .init(position: .init(1, 0, 1), normal: .back, texCoord: .init(1, 0)),
    .init(position: .init(0, 1, 1), normal: .back, texCoord: .init(0, 1)),
    .init(position: .init(1, 1, 1), normal: .back, texCoord: .init(1, 1))
  ], .right: [
    .init(position: .init(1, 0, 1), normal: .right, texCoord: .init(0, 0)),
    .init(position: .init(1, 0, 0), normal: .right, texCoord: .init(1, 0)),
    .init(position: .init(1, 1, 1), normal: .right, texCoord: .init(0, 1)),
    .init(position: .init(1, 1, 0), normal: .right, texCoord: .init(1, 1))
  ], .front: [
    .init(position: .init(1, 0, 0), normal: .forward, texCoord: .init(0, 0)),
    .init(position: .init(0, 0, 0), normal: .forward, texCoord: .init(1, 0)),
    .init(position: .init(1, 1, 0), normal: .forward, texCoord: .init(0, 1)),
    .init(position: .init(0, 1, 0), normal: .forward, texCoord: .init(1, 1))
  ], .left: [
    .init(position: .init(0, 0, 0), normal: .left, texCoord: .init(0, 0)),
    .init(position: .init(0, 0, 1), normal: .left, texCoord: .init(1, 0)),
    .init(position: .init(0, 1, 0), normal: .left, texCoord: .init(0, 1)),
    .init(position: .init(0, 1, 1), normal: .left, texCoord: .init(1, 1))
  ], .down: [
    .init(position: .init(0, 0, 0), normal: .down, texCoord: .init(0, 0)),
    .init(position: .init(1, 0, 0), normal: .down, texCoord: .init(1, 0)),
    .init(position: .init(0, 0, 1), normal: .down, texCoord: .init(0, 1)),
    .init(position: .init(1, 0, 1), normal: .down, texCoord: .init(1, 1))
  ], .up: [
    .init(position: .init(0, 1, 1), normal: .up, texCoord: .init(0, 0)),
    .init(position: .init(1, 1, 1), normal: .up, texCoord: .init(1, 0)),
    .init(position: .init(0, 1, 0), normal: .up, texCoord: .init(0, 1)),
    .init(position: .init(1, 1, 0), normal: .up, texCoord: .init(1, 1))
  ]
]

fileprivate let sideIndices: [UInt16] = [ 0,  1,  2,  2,  1,  3 ]

fileprivate enum Side {
  case left, right
  case down, up
  case back, front
}

fileprivate extension SIMD3 where Scalar: SignedInteger & FixedWidthInteger {
  func offset(by side: Side) -> Self {
    let ofs: Self = switch side {
    case .right: .right
    case .left:  .left
    case .up:    .up
    case .down:  .down
    case .back:  .back
    case .front: .forward
    }
    return self &+ ofs
  }
}
