package li.cil.oc.client.renderer.tileentity

import li.cil.oc.Settings
import li.cil.oc.common.tileentity.Cable
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer
import net.minecraft.client.renderer.{Tessellator, GLAllocation}
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.ForgeDirection
import org.lwjgl.opengl.GL11

object CableRenderer extends TileEntitySpecialRenderer {
  private val texture = new ResourceLocation(Settings.resourceDomain, "textures/blocks/cable.png")

  private val displayLists = GLAllocation.generateDisplayLists(64)

  def compileLists() {
    val t = Tessellator.instance
    val lb = 0.375
    val ub = 0.625
    val s = 0.25
    val t1 = Array(ub, ub, lb, lb)
    val t2 = Array(lb, ub, ub, lb)
    val uv1 = Array(0, 0, 0.5, 0.5)
    val uv2 = Array(0.5, 0, 0, 0.5)
    val uo = 4.0 / 8.0
    val vs = 6.0 / 4.0
    val translations = Array(Array(lb, 0, 0), Array(0, lb, 0), Array(0, 0, lb))
    val offsets = Array(Array(s, 0, 0), Array(0, s, 0), Array(0, 0, s))
    val normals = Array(
      Array(ForgeDirection.EAST, ForgeDirection.WEST, ForgeDirection.SOUTH, ForgeDirection.NORTH),
      Array(ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.WEST, ForgeDirection.EAST),
      Array(ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.WEST, ForgeDirection.EAST),
      Array(ForgeDirection.WEST, ForgeDirection.EAST, ForgeDirection.DOWN, ForgeDirection.UP),
      Array(ForgeDirection.SOUTH, ForgeDirection.NORTH, ForgeDirection.DOWN, ForgeDirection.UP),
      Array(ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.NORTH, ForgeDirection.SOUTH)
    )
    def normal(side: ForgeDirection, n: Int) {
      val v = normals(side.ordinal())(n)
      t.setNormal(v.offsetX, v.offsetY, v.offsetZ)
    }
    def uv(i: Int) = (i + 4) % 4

    for (mask <- 0 to 0xFF >> 2) {
      GL11.glNewList(displayLists + mask, GL11.GL_COMPILE)
      for (side <- ForgeDirection.VALID_DIRECTIONS) {
        val connects = (side.flag & mask) != 0
        val z = if (connects) 0 else lb
        val uc = if (ForgeDirection.VALID_DIRECTIONS.
          filter(_ != side).filter(_ != side.getOpposite).
          exists(s => (s.flag & mask) != 0)) uo
        else 0

        t.startDrawingQuads()
        t.setNormal(side.offsetX, side.offsetY, -side.offsetZ)
        val (tx, ty, tz, u, v) = side match {
          case ForgeDirection.WEST => (Array.fill(4)(z), t2, t1, uv1.reverse, uv2)
          case ForgeDirection.EAST => (Array.fill(4)(1 - z), t1, t2, uv2, uv1)
          case ForgeDirection.DOWN => (t1, Array.fill(4)(z), t2, uv1.reverse, uv2)
          case ForgeDirection.UP => (t2, Array.fill(4)(1 - z), t1, uv2, uv1)
          case ForgeDirection.NORTH => (t2, t1, Array.fill(4)(z), uv2, uv1)
          case ForgeDirection.SOUTH => (t1, t2, Array.fill(4)(1 - z), uv1.reverse, uv2)
          case _ => throw new AssertionError()
        }
        t.addVertexWithUV(tx(0), ty(0), tz(0), u(0) + uc, v(0))
        t.addVertexWithUV(tx(1), ty(1), tz(1), u(1) + uc, v(1))
        t.addVertexWithUV(tx(2), ty(2), tz(2), u(2) + uc, v(2))
        t.addVertexWithUV(tx(3), ty(3), tz(3), u(3) + uc, v(3))
        t.draw()

        if (connects) {
          val (axis, sign, uv1, uv2, uv3, uv4) = side match {
            case ForgeDirection.WEST => (0, -1, 1, 1, 1, 1)
            case ForgeDirection.EAST => (0, 1, 3, 3, 1, 1)
            case ForgeDirection.DOWN => (1, -1, 1, 3, 2, 0)
            case ForgeDirection.UP => (1, 1, 2, 0, 3, 1)
            case ForgeDirection.NORTH => (2, -1, 0, 2, 1, 1)
            case ForgeDirection.SOUTH => (2, 1, 1, 1, 0, 2)
            case _ => throw new AssertionError()
          }
          val tl = translations(axis)
          val o1 = offsets((axis + sign + 3) % 3)
          val o2 = offsets((axis - sign + 3) % 3)

          t.startDrawingQuads()
          normal(side, 0)
          t.addVertexWithUV(tx(0) - sign * tl(0), ty(0) - sign * tl(1), tz(0) - sign * tl(2), u(uv(0 + uv1)) + uo, v(uv(0 + uv1)) * vs)
          t.addVertexWithUV(tx(1) - sign * tl(0), ty(1) - sign * tl(1), tz(1) - sign * tl(2), u(uv(1 + uv1)) + uo, v(uv(1 + uv1)) * vs)
          t.addVertexWithUV(tx(2) + o1(0), ty(2) + o1(1), tz(2) + o1(2), u(uv(2 + uv1)) + uo, v(uv(2 + uv1)) * vs)
          t.addVertexWithUV(tx(3) + o1(0), ty(3) + o1(1), tz(3) + o1(2), u(uv(3 + uv1)) + uo, v(uv(3 + uv1)) * vs)
          t.draw()

          t.startDrawingQuads()
          normal(side, 1)
          t.addVertexWithUV(tx(0) - o1(0), ty(0) - o1(1), tz(0) - o1(2), u(uv(0 + uv2)) + uo, v(uv(0 + uv2)) * vs)
          t.addVertexWithUV(tx(1) - o1(0), ty(1) - o1(1), tz(1) - o1(2), u(uv(1 + uv2)) + uo, v(uv(1 + uv2)) * vs)
          t.addVertexWithUV(tx(2) - sign * tl(0), ty(2) - sign * tl(1), tz(2) - sign * tl(2), u(uv(2 + uv2)) + uo, v(uv(2 + uv2)) * vs)
          t.addVertexWithUV(tx(3) - sign * tl(0), ty(3) - sign * tl(1), tz(3) - sign * tl(2), u(uv(3 + uv2)) + uo, v(uv(3 + uv2)) * vs)
          t.draw()

          t.startDrawingQuads()
          normal(side, 2)
          t.addVertexWithUV(tx(0) - sign * tl(0), ty(0) - sign * tl(1), tz(0) - sign * tl(2), u(uv(0 + uv3)) + uo, v(uv(0 + uv3)) * vs)
          t.addVertexWithUV(tx(1) - o2(0), ty(1) - o2(1), tz(1) - o2(2), u(uv(1 + uv3)) + uo, v(uv(1 + uv3)) * vs)
          t.addVertexWithUV(tx(2) - o2(0), ty(2) - o2(1), tz(2) - o2(2), u(uv(2 + uv3)) + uo, v(uv(2 + uv3)) * vs)
          t.addVertexWithUV(tx(3) - sign * tl(0), ty(3) - sign * tl(1), tz(3) - sign * tl(2), u(uv(3 + uv3)) + uo, v(uv(3 + uv3)) * vs)
          t.draw()

          t.startDrawingQuads()
          normal(side, 3)
          t.addVertexWithUV(tx(0) + o2(0), ty(0) + o2(1), tz(0) + o2(2), u(uv(0 + uv4)) + uo, v(uv(0 + uv4)) * vs)
          t.addVertexWithUV(tx(1) - sign * tl(0), ty(1) - sign * tl(1), tz(1) - sign * tl(2), u(uv(1 + uv4)) + uo, v(uv(1 + uv4)) * vs)
          t.addVertexWithUV(tx(2) - sign * tl(0), ty(2) - sign * tl(1), tz(2) - sign * tl(2), u(uv(2 + uv4)) + uo, v(uv(2 + uv4)) * vs)
          t.addVertexWithUV(tx(3) + o2(0), ty(3) + o2(1), tz(3) + o2(2), u(uv(3 + uv4)) + uo, v(uv(3 + uv4)) * vs)
          t.draw()
        }
      }

      GL11.glEndList()
    }
  }

  compileLists()

  def renderCable(neighbors: Int) {
    bindTexture(texture)
    GL11.glCallList(displayLists + neighbors)
  }

  def renderTileEntityAt(t: TileEntity, x: Double, y: Double, z: Double, f: Float) {
    val cable = t.asInstanceOf[Cable]

    GL11.glTranslated(x, y, z)
    renderCable(cable.neighbors)
    GL11.glTranslated(-x, -y, -z)
  }
}
