class Material:
    """Represents a material

    It contains its constants and its texturess. It is also usable with OpenGL
    """
    def __init__(self, name):
        """ Creates an empty material

        :param name: name of the material:
        """
        self.name = name
        self.Ka = None
        self.Kd = None
        self.Ks = None
        self.relative_path_to_texture = None
        self.absolute_path_to_texture = None
        self.im = None
        self.id = None

    def init_texture(self):
        """ Initializes the OpenGL texture of the current material

        To be simple, calls glGenTextures and stores the given id
        """

        import OpenGL.GL as gl

        # Already initialized
        if self.id is not None:
            return

        # If no map_Kd, nothing to do
        if self.im is None:

            if self.absolute_path_to_texture is None:
                return

            try:
                import PIL.Image
                self.im = PIL.Image.open(self.absolute_path_to_texture)
            except ImportError:
                return

        try:
            ix, iy, image = self.im.size[0], self.im.size[1], self.im.tobytes("raw", "RGBA", 0, -1)
        except:
            ix, iy, image = self.im.size[0], self.im.size[1], self.im.tobytes("raw", "RGBX", 0, -1)

        self.id = gl.glGenTextures(1)

        gl.glBindTexture(gl.GL_TEXTURE_2D, self.id)
        gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT,1)

        gl.glTexImage2D(
            gl.GL_TEXTURE_2D, 0, 3, ix, iy, 0,
            gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image
        )

    def bind(self):
        """Binds the material to OpenGL
        """
        from OpenGL import GL as gl

        gl.glEnable(gl.GL_TEXTURE_2D)
        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
        gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.id)

    def unbind(self):
        """Disables the GL_TEXTURE_2D flag of OpenGL
        """
        from OpenGL import GL as gl

        gl.glDisable(gl.GL_TEXTURE_2D)

Material.DEFAULT_MATERIAL=Material('')
"""Material that is used when no material is specified
"""
Material.DEFAULT_MATERIAL.Ka = 1.0
Material.DEFAULT_MATERIAL.Kd = 0.0
Material.DEFAULT_MATERIAL.Ks = 0.0

try:
    import PIL.Image
    Material.DEFAULT_MATERIAL.im = PIL.Image.new("RGBA", (1,1), "white")
except ImportError:
    pass

class MeshPart:
    """A part of a 3D model that is bound to a single material
    """
    def __init__(self, parent):
        """Creates a mesh part

        :param parent: the global model with all the information
        """
        self.parent = parent
        self.material = None
        self.vertex_vbo = None
        self.tex_coord_vbo = None
        self.normal_vbo = None
        self.color_vbo = None
        self.faces = []

    def init_texture(self):
        """Initializes the material of the current parent
        """
        if self.material is not None:
            self.material.init_texture()

    def add_face(self, face):
        """Adds a face to this MeshPart

        :param face: face to add
        """
        self.faces.append(face)

    def generate_vbos(self):
        """Generates the vbo for this MeshPart

        Creates the arrays that are necessary for smooth rendering
        """

        from OpenGL.arrays import vbo
        from numpy import array

        # Build VBO
        v = []
        n = []
        t = []
        c = []

        for face in self.faces:
            v1 = self.parent.vertices[face.a.vertex]
            v2 = self.parent.vertices[face.b.vertex]
            v3 = self.parent.vertices[face.c.vertex]
            v += [[v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z]]

            if face.a.normal is not None:
                n1 = self.parent.normals[face.a.normal]
                n2 = self.parent.normals[face.b.normal]
                n3 = self.parent.normals[face.c.normal]
                n += [[n1.x, n1.y, n1.z], [n2.x, n2.y, n2.z], [n3.x, n3.y, n3.z]]

            if face.a.tex_coord is not None:
                t1 = self.parent.tex_coords[face.a.tex_coord]
                t2 = self.parent.tex_coords[face.b.tex_coord]
                t3 = self.parent.tex_coords[face.c.tex_coord]
                t += [[t1.x, t1.y], [t2.x, t2.y], [t3.x, t3.y]]

            if len(self.parent.colors) > 0: # face.a.color is not None:
                c1 = self.parent.colors[face.a.vertex]
                c2 = self.parent.colors[face.b.vertex]
                c3 = self.parent.colors[face.c.vertex]
                c += [[c1.x, c1.y, c1.z], [c2.x, c2.y, c2.z], [c3.x, c3.y, c3.z]]

        self.vertex_vbo  = vbo.VBO(array(v, 'f'))

        if len(n) > 0:
            self.normal_vbo  = vbo.VBO(array(n, 'f'))

        if len(t) > 0:
            self.tex_coord_vbo = vbo.VBO(array(t, 'f'))

        if len(c) > 0:
            self.color_vbo = vbo.VBO(array(c, 'f'))

    def draw(self):
        """Draws the current MeshPart

        Binds the material, and draws the model
        """
        if self.material is not None:
            self.material.bind()

        if self.vertex_vbo is not None:
            self.draw_from_vbos()
        else:
            self.draw_from_arrays()

        if self.material is not None:
            self.material.unbind()

    def draw_from_vbos(self):
        """Simply calls the OpenGL drawArrays function

        Sets the correct vertex arrays and draws the part
        """

        import OpenGL.GL as gl

        self.vertex_vbo.bind()
        gl.glEnableClientState(gl.GL_VERTEX_ARRAY);
        gl.glVertexPointerf(self.vertex_vbo)
        self.vertex_vbo.unbind()

        if self.normal_vbo is not None:
            self.normal_vbo.bind()
            gl.glEnableClientState(gl.GL_NORMAL_ARRAY)
            gl.glNormalPointerf(self.normal_vbo)
            self.normal_vbo.unbind()

        if self.tex_coord_vbo is not None:

            if self.material is not None:
                self.material.bind()

            self.tex_coord_vbo.bind()
            gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
            gl.glTexCoordPointerf(self.tex_coord_vbo)
            self.tex_coord_vbo.unbind()

        if self.color_vbo is not None:
            self.color_vbo.bind()
            gl.glEnableClientState(gl.GL_COLOR_ARRAY)
            gl.glColorPointerf(self.color_vbo)
            self.color_vbo.unbind()

        gl.glDrawArrays(gl.GL_TRIANGLES, 0, len(self.vertex_vbo.data) * 9)

        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_NORMAL_ARRAY)
        gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)


    def draw_from_arrays(self):
        pass