import java.awt.event.ActionEvent
import java.awt._

import javax.swing.{JFrame, JPanel, Timer}

import scala.math.{Pi, atan, cos, sin, sqrt}

object RotatingCube extends App {

  class RotatingCube extends JPanel {
    private val vertices: Vector[Array[Double]] =
      Vector(Array(-1, -1, -1), Array(-1, -1, 1), Array(-1, 1, -1),
        Array(-1, 1, 1), Array(1, -1, -1), Array(1, -1, 1), Array(1, 1, -1), Array(1, 1, 1))

    private val edges: Vector[(Int, Int)] =
      Vector((0, 1), (1, 3), (3, 2), (2, 0), (4, 5), (5, 7),
        (7, 6), (6, 4), (0, 4), (1, 5), (2, 6), (3, 7))

    setPreferredSize(new Dimension(640, 640))
    setBackground(Color.white)
    scale(100)
    rotateCube(Pi / 4, atan(sqrt(2)))

    new Timer(17, (_: ActionEvent) => {
      rotateCube(Pi / 180, 0)
      repaint()
    }).start()

    override def paintComponent(gg: Graphics): Unit = {
      def drawCube(g: Graphics2D): Unit = {
        g.translate(getWidth / 2, getHeight / 2)
        for {edge <- edges
             xy1: Array[Double] = vertices(edge._1)
             xy2: Array[Double] = vertices(edge._2)
             } {
          g.drawLine(xy1(0).toInt, xy1(1).toInt, xy2(0).toInt, xy2(1).toInt)
          g.fillOval(xy1(0).toInt -4, xy1(1).toInt - 4, 8, 8)
          g.setColor(Color.black)
        }
      }

      super.paintComponent(gg)
      val g: Graphics2D = gg.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      drawCube(g)
    }

    private def scale(s: Double): Unit = {
      for {node <- vertices
           i <- node.indices
           } node(i) *= s
    }

    private def rotateCube(angleX: Double, angleY: Double): Unit = {
      def sinCos(x: Double) = (sin(x), cos(x))

      val ((sinX, cosX), (sinY, cosY)) = (sinCos(angleX), sinCos(angleY))

      for {
        node <- vertices
        x: Double = node(0)
        y: Double = node(1)
      } {
        def f(p: Double, q: Double)(a: Double, b: Double) = a * p + b * q

        def fx(a: Double, b: Double) = f(cosX, sinX)(a, b)

        def fy(a: Double, b: Double) = f(cosY, sinY)(a, b)

        node(0) = fx(x, -node(2))
        val z = fx(node(2), x)
        node(1) = fy(y, -z)
        node(2) = fy(z, y)
      }
    }

  }

  new JFrame("Rotating Cube") {
    add(new RotatingCube(), BorderLayout.CENTER)
    pack()
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE)
    setLocationRelativeTo(null)
    setResizable(false)
    setVisible(true)
  }

}