import moderngl
import numpy
from headless import HeadlessTestCase

from moderngl_window.opengl.vao import VAO, BufferInfo, VAOError


class VaoTestCase(HeadlessTestCase):

    def createProgram(self):
        return self.ctx.program(
            vertex_shader="""
            #version 330

            in vec3 position;
            in vec3 normal;
            out vec3 v_normal;

            void main() {
                gl_Position = vec4(position, 1.0);
                v_normal = normalize(normal);
            }
            """,
            fragment_shader="""
            #version 330

            out vec4 fragColor;
            in vec3 v_normal;

            void main() {
                float col = length(v_normal);
                fragColor = vec4(col);
            }
            """,
        )

    def test_create(self):
        mesh = VAO("test", mode=moderngl.LINES)
        mesh.buffer(
            numpy.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0], dtype="f4"), "3f", "position"
        )
        mesh.buffer(
            numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype="f4"), "3f", "normal"
        )
        mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype="f4"), "2f", "uv")

        # Ensure basic properties are correct
        self.assertEqual(mesh.name, "test")
        self.assertEqual(mesh.vertex_count, 2)
        self.assertEqual(mesh.mode, moderngl.LINES)

        # Ensure buffers are present
        self.assertIsInstance(mesh.get_buffer_by_name("position"), BufferInfo)
        self.assertIsInstance(mesh.get_buffer_by_name("normal"), BufferInfo)
        self.assertIsInstance(mesh.get_buffer_by_name("uv"), BufferInfo)
        self.assertEqual(mesh.get_buffer_by_name("something"), None)

        # Create a progam using a subset of the buffers
        prog = self.createProgram()
        vao = mesh.instance(prog)
        self.assertIsInstance(vao, moderngl.VertexArray)

        # Render directly with VAO and VertexArray instance
        mesh.render(prog)
        vao.render()

    def test_illegal_draw_mode(self):
        """Create vao with illegal draw mode"""
        with self.assertRaises(VAOError):
            VAO(mode=1337)

    def test_add_illegal_buffer(self):
        """Attempt to add illegal buffer"""
        vao = VAO()
        with self.assertRaises(VAOError):
            vao.buffer("stuff", "1f", "in_position")

    def test_normalized_types(self):
        """Ensure VAO wrapper can handle normalized types"""
        # prog = self.createProgram()
        mesh = VAO("test", mode=moderngl.LINES)
        mesh.buffer(
            numpy.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0], dtype="i4"), "3ni", "position"
        )
        mesh.buffer(
            numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype="f4"), "3nf", "normal"
        )
        mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype="f4"), "2f", "uv")

        # Uncomment in moderngl 5.6+
        # mesh.instance(prog)

    def test_divisors(self):
        """Test defining buffers with different divisor types"""
        mesh = VAO("test", mode=moderngl.LINES)
        mesh.buffer(
            numpy.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0], dtype="f4"), "3f/v", "position"
        )
        mesh.buffer(
            numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype="f4"), "3f/r", "normal"
        )
        mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype="f4"), "2f/i", "uv")

        buffer1 = mesh.get_buffer_by_name("position")
        buffer2 = mesh.get_buffer_by_name("normal")
        buffer3 = mesh.get_buffer_by_name("uv")

        attributes = ["position", "normal", "uv"]
        self.assertEqual(
            buffer1.content(attributes), (buffer1.buffer, "3f/v", "position")
        )
        self.assertEqual(
            buffer2.content(attributes), (buffer2.buffer, "3f/r", "normal")
        )
        self.assertEqual(buffer3.content(attributes), (buffer3.buffer, "2f/i", "uv"))
