from time import clock
import os.path

from pyglet.gl import *

from punyverse.texture import load_texture

FACE_TRIANGLES = 0
FACE_QUADS = 1


def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
    for m, text in model.materials.iteritems():
        if text.texture:
            load_texture(os.path.join(model.root, text.texture))

    display = glGenLists(1)

    glNewList(display, GL_COMPILE)
    glPushMatrix()
    glPushAttrib(GL_CURRENT_BIT)

    pitch, yaw, roll = rotation
    glPushAttrib(GL_TRANSFORM_BIT)
    glRotatef(pitch, 1, 0, 0)
    glRotatef(yaw, 0, 1, 0)
    glRotatef(roll, 0, 0, 1)
    glPopAttrib()

    vertices = model.vertices
    textures = model.textures
    normals = model.normals

    for g in model.groups:
        material = g.material

        tex_id = load_texture(os.path.join(model.root, material.texture)) if (material and material.texture) else 0

        if tex_id:
            glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, tex_id)
        else:
            glBindTexture(GL_TEXTURE_2D, 0)
            glDisable(GL_TEXTURE_2D)

        if material:
            fv4 = GLfloat * 4

            if material.Ka:
                kx, ky, kz = material.Ka
                glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fv4(kx, ky, kz, 1))
            if material.Kd:
                kx, ky, kz = material.Kd
                glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fv4(kx, ky, kz, 1))
            if material.Ks:
                kx, ky, kz = material.Ks
                glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(kx, ky, kz, 1))
            glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.shininess)

        type = -1

        def point(f, vertices, normals, textures, n):
            if f.norms:
                glNormal3f(*normals[f.norms[n]])
            if tex_id:
                glTexCoord2f(*textures[f.texs[n]][:2])

            x, y, z = vertices[f.verts[n]]
            glVertex3f(x * sx, y * sy, z * sz)

        for f in g.faces:
            if type != f.type:
                if type != -1:
                    glEnd()
                glBegin(GL_TRIANGLES)
            type = f.type

            point(f, vertices, normals, textures, 0)
            point(f, vertices, normals, textures, 1)
            point(f, vertices, normals, textures, 2)

            if type == FACE_QUADS:
                point(f, vertices, normals, textures, 2)
                point(f, vertices, normals, textures, 3)
                point(f, vertices, normals, textures, 0)
    glEnd()

    glPopAttrib()
    glPopMatrix()

    glEndList()
    return display

def load_model(path):
    path = os.path.join(os.path.dirname(__file__), "assets", "models", path)
    root = os.path.dirname(path)

    vertices = []
    normals = []
    textures = []
    groups = []
    materials = {}

    # Allez au diable, Python
    current_group = [None]
    current_material = [None]

    def dispatch(p, commands):
        with open(p, 'r') as file:
            for line in file:
                line = line.strip()
                if not line or line[0] == '#':
                    continue # Empty or comment
                words = line.split()
                type = words[0]
                if type in commands:
                    commands[type](words)

    def newmtl(words):
        material = Material(words[1])
        materials[words[1]] = material
        current_material[0] = material

    def Ka(words):
        current_material[0].Ka = (float(words[1]), float(words[2]), float(words[3]))

    def Kd(words):
        current_material[0].Kd = (float(words[1]), float(words[2]), float(words[3]))

    def Ks(words):
        current_material[0].Ks = (float(words[1]), float(words[2]), float(words[3]))

    def Ns(words):
        current_material[0].shininess = min(float(words[1]), 125) # Seems to sometimes be > 125. TODO: find out why

    def map_Kd(words):
        current_material[0].texture = words[-1]

    def v(words):
        vertices.append((float(words[1]), float(words[2]), float(words[3])))

    def vn(words):
        normals.append((float(words[1]), float(words[2]), float(words[3])))

    def vt(words):
        l = len(words)
        x, y, z = 0, 0, 0
        if l >= 2:
            x = float(words[1])
        if l >= 3:
            # OBJ origin is at upper left, OpenGL origin is at lower left
            y = 1 - float(words[2])
        if l >= 4:
            z = float(words[3])
        textures.append((x, y, z))

    def f(words):
        l = len(words)
        type = -1
        face_vertices = []
        face_normals = []
        face_textures = []

        vertex_count = l - 1
        if vertex_count == 3:
            type = FACE_TRIANGLES
        else:
            type = FACE_QUADS

        raw_faces = []
        current_value = -1
        vindices = []
        nindices = []
        tindices = []

        for i in xrange(1, vertex_count + 1):
            raw_faces = words[i].split('/')
            l = len(raw_faces)

            current_value = int(raw_faces[0])

            vindices.append(current_value - 1)
            face_vertices.append(vertices[current_value - 1])

            if l == 1:
                continue

            if l >= 2 and raw_faces[1]:
                current_value = int(raw_faces[1])
                if current_value <= len(textures):
                    tindices.append(current_value - 1)
                    face_textures.append(textures[current_value - 1])
            if l >= 3 and raw_faces[2]:
                current_value = int(raw_faces[2])
                nindices.append(current_value - 1)
                face_normals.append(normals[current_value - 1])

        if not groups:
            group = Group()
            groups.append(group)
        else:
            group = groups[-1]
        group.vertices += face_vertices
        group.normals += face_normals
        group.textures += face_textures

        idx_count = group.idx_count
        group.indices += (idx_count + 1, idx_count + 2, idx_count + 3)
        group.idx_count += 3

        group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures))

    mtllib = lambda words: dispatch(os.path.join(root, words[1]), obj)

    def usemtl(words):
        mat = words[1]
        if mat in materials:
            groups[-1].material = materials[mat]
        else:
            print "Warning: material %s undefined." % mat

    g = lambda words: groups.append(Group(words[1]))

    obj = {
        'v': v,
        'vn': vn,
        'vt': vt,
        'f': f,
        'mtllib': mtllib,
        'usemtl': usemtl,
        'g': g,
        'o': g, # TODO: o is not really g
        'newmtl': newmtl,
        'Ka': Ka,
        'Kd': Kd,
        'Ks': Ks,
        'Ns': Ns,
        'map_Kd': map_Kd
    }

    dispatch(path, obj)

    return WavefrontObject(root, vertices, normals, textures, groups, materials)


class WavefrontObject(object):
    def __init__(self, root, vertices=None, normals=None, textures=None, groups=None, materials=None):
        self.root = root
        self.vertices = vertices or []
        self.normals = normals or []
        self.textures = textures or []
        self.groups = groups or []
        self.materials = materials or []


class Group(object):
    def __init__(self, name=None):
        if not name:
            name = clock()
        self.name = name
        self.material = None
        self.faces = []
        self.indices = []
        self.vertices = []
        self.normals = []
        self.textures = []
        self.idx_count = 0


class Face(object):
    def __init__(self, type, verts, norms, texs, vertices, normals, textures):
        self.type = type
        self.verts = verts
        self.norms = norms
        self.texs = texs
        self.vertices = vertices
        self.normals = normals
        self.textures = textures


class Material(object):
    def __init__(self, name, texture=None, Ka=0, Kd=0, Ks=0, shininess=0.0):
        self.name = name
        self.texture = texture
        self.Ka = Ka
        self.Kd = Kd
        self.Ks = Ks
        self.shininess = shininess