/* obj.c
 *
 * OBJ Loader
 * inital code: http://www.gamedev.net/community/forums/topic.asp?topic_id=312335
 *
 * notes: the mtl file should be implemented using arrays
 *        more that one mtl needs to be supported some day
 *        only works for *quads* now
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "obj.h"

ObjModel* ObjLoadModel(char *memory, size_t size)
{
    char *p = NULL, *e = NULL;
    ObjModel *ret = (ObjModel *) calloc(1, sizeof(ObjModel));
    memset(ret, 0, sizeof(ObjModel));

    p = memory;
    e = memory + size;
    
    /* count the number of normals, texcoords, vertices, and faces, line by line */
    while (p != e)
    {
        if (memcmp(p, "vn", 2) == 0)      ret->nNormal++;
        else if (memcmp(p, "vt", 2) == 0) ret->nTexCoord++;
        else if (memcmp(p, "v",  1) == 0) ret->nVertex++;
        else if (memcmp(p, "f",  1) == 0) ret->nFace++;

        while (*p++ != (char) 0x0a);
    }

    /* allocate memory for arrays */
    ret->VertexArray   = (ObjVertex *)   malloc(sizeof(ObjVertex) * ret->nVertex);
    ret->NormalArray   = (ObjNormal *)   malloc(sizeof(ObjNormal) * ret->nNormal);
    ret->TexCoordArray = (ObjTexCoord *) malloc(sizeof(ObjTexCoord) * ret->nTexCoord);
    ret->FaceArray = (ObjFace *) malloc(sizeof(ObjFace) * ret->nFace);
    ret->mtllib = (char *) malloc(sizeof(char) * 21);
    ret->objectName = (char *) malloc(sizeof(char) * 21);
    ret->usemtl = (char *) malloc(sizeof(char) * 21);
    memset(ret->mtllib, 0, 21);
    memset(ret->objectName, 0, 21);
    memset(ret->usemtl, 0, 21);

    p = memory;
    
    int nV = 0, nN = 0, nT = 0, nF = 0;
    int nread = 0;
    
    while (p != e)
    {
        /* check for mtl file */
        if (memcmp(p, "mtllib", 6) == 0)
            sscanf(p, "mtllib %s", ret->mtllib);

        /* check if mtl file will be used*/
        if (memcmp(p, "usemtl", 6) == 0)
            sscanf(p, "usemtl %s", ret->usemtl);

        if (memcmp(p, "g", 1) == 0)
            sscanf(p, "g %s", ret->objectName);
    
        if (memcmp(p, "vn", 2) == 0)
        {
            nread = sscanf(p, "vn %f %f %f", &ret->NormalArray[nN].x,
                                     &ret->NormalArray[nN].y,
                                     &ret->NormalArray[nN].z);
            if (nread != 3)
                printf("vn: read only %d instead of 3\n", nread);
            nN++;
        }
        else if (memcmp(p, "vt", 2) == 0)
        {
            nread = sscanf(p, "vt %f %f", &ret->TexCoordArray[nT].u,
                                  &ret->TexCoordArray[nT].v);
            if (nread != 2)
                printf("vt: read only %d instead of 2\n", nread);              
            nT++;
        }
        else if (memcmp(p, "v", 1) == 0) /* or *p == 'v' */
        {
            nread = sscanf(p, "v %f %f %f", &ret->VertexArray[nV].x,
                                    &ret->VertexArray[nV].y,
                                    &ret->VertexArray[nV].z);
            if (nread != 3)
                printf("v: read only %d instead of 3\n", nread);
            nV++;
        }
        /* quad */
        else if (memcmp(p, "f", 1) == 0) /* or *p == 'f' */
        {
            nread = sscanf(p, "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d",
                &ret->FaceArray[nF].Vertex[0],
                &ret->FaceArray[nF].TexCoord[0],
                &ret->FaceArray[nF].Normal[0],
                &ret->FaceArray[nF].Vertex[1],
                &ret->FaceArray[nF].TexCoord[1],
                &ret->FaceArray[nF].Normal[1],
                &ret->FaceArray[nF].Vertex[2],
                &ret->FaceArray[nF].TexCoord[2],
                &ret->FaceArray[nF].Normal[2],
                &ret->FaceArray[nF].Vertex[3],
                &ret->FaceArray[nF].TexCoord[3],
                &ret->FaceArray[nF].Normal[3]);
            if (nread != 12)
                printf("f: read only %d instead of 12\n", nread);                                                            
            nF++;
        }
        /* seek to a newline */
        while (*p++ != (char) 0x0a);
    }

    /* sanity check */
    if ((ret->nVertex != nV) || (ret->nNormal != nN) || (ret->nTexCoord != nT)
          || (ret->nFace != nF))
    {
        fprintf(stdout, "obj loader: warning, the number of scanned items does not equal to number of read\n");
        if (ret->nVertex != nV)
            fprintf(stdout, "vertices: scanned %d, read %d\n", ret->nVertex, nV);
        if (ret->nNormal != nN)
            fprintf(stdout, "normals: scanned %d, read %d\n", ret->nNormal, nN);
        if (ret->nTexCoord != nT)
            fprintf(stdout, "texcoords: scanned %d, read %d\n", ret->nTexCoord, nT);
        if (ret->nFace != nF)
            fprintf(stdout, "faces: scanned %d, read %d\n", ret->nFace, nF);
    }
    
    /* load the mtl file */
    if (ret->mtllib != NULL && ret->usemtl)
    {
        /* append directory to filename */
        char *fname = (char *) malloc(sizeof(char) * 21);
        sprintf(fname, "./cube/%s", ret->mtllib);

        char *mtl_mem = NULL;
        size_t mtl_bytes = ObjLoadFile(fname, &mtl_mem);

        if (mtl_bytes)
        {
            ObjMtl *mtl = (ObjMtl *) calloc(1, sizeof(ObjMtl));
            memset(mtl, 0, sizeof(ObjMtl));

            /* allocate space for members */
            mtl->map_Ka = (char *) malloc(sizeof(char) * 21);
            mtl->map_Kd = (char *) malloc(sizeof(char) * 21);
            /* set, what about rest? */
            mtl->Ns = 0.0f;
            mtl->Ni = 0.0f;
            mtl->d = 0.0f;
            mtl->Tr = 0.0f;
            mtl->illum = 0;

            char *st = mtl_mem;
            char *ed = st + mtl_bytes;

            while (st != ed)
            {
                /* skip spaces */
                while (*st++ == (char) 0x20);

                if (memcmp(st, "Ns", 2) == 0)
                    sscanf(st, "Ns %f", &mtl->Ns);

                if (memcmp(st, "Ni", 2) == 0)
                    sscanf(st, "Ni %f", &mtl->Ni);

                if (memcmp(st, "d", 1) == 0)
                    sscanf(st, "d %f", &mtl->d);

                if (memcmp(st, "Tr", 2) == 0)
                    sscanf(st, "Tr %f", &mtl->Tr);

                if (memcmp(st, "Tf", 2) == 0)
                    sscanf(st, "Tf %f %f %f", &mtl->Tf.x, &mtl->Tf.y, &mtl->Tf.z);

                if (memcmp(st, "illum", 5) == 0)
                    sscanf(st, "illum %d", &mtl->illum);

                if (memcmp(st, "Ka", 2) == 0)
                    sscanf(st, "Ka %f %f %f", &mtl->Ka.x, &mtl->Ka.y, &mtl->Ka.z);

                if (memcmp(st, "Kd", 2) == 0)
                    sscanf(st, "Kd %f %f %f", &mtl->Kd.x, &mtl->Kd.y, &mtl->Kd.z);

                if (memcmp(st, "Ks", 2) == 0)
                    sscanf(st, "Ks %f %f %f", &mtl->Ks.x, &mtl->Ks.y, &mtl->Ks.z);

                if (memcmp(st, "Ke", 2) == 0)
                    sscanf(st, "Ke %f %f %f", &mtl->Ke.x, &mtl->Ke.y, &mtl->Ke.z);

                if (memcmp(st, "map_Ka", 6) == 0)
                    sscanf(st, "map_Ka %s", mtl->map_Ka);

                if (memcmp(st, "map_Kd", 6) == 0)
                    sscanf(st, "map_Kd %s", mtl->map_Kd);

                /* got to next line */
                while (*st++ != (char) 0x0a);
            }
            /* set the mtl */
            ret->mtl = mtl;
        }
        else
            ret->mtl = NULL;

        free(fname);
    }   

    return ret;
}

size_t ObjLoadFile(char *szFileName, char **memory)
{
    size_t bytes = 0;
    FILE *file = fopen(szFileName, "rt");

    if (file != NULL)
    {
        fseek(file, 0, SEEK_END);
        size_t end = ftell(file);
        fseek(file, 0, SEEK_SET);
       
        *memory = (char *) malloc(end);
        bytes = fread(*memory, sizeof(char), end, file);

        fclose(file);
    }
    return bytes;
}

/* trivial, meh */
void ObjList(ObjModel *model)
{
    int i;
    printf("%d vertices\n", model->nVertex);
    for (i = 0; i < model->nVertex; i++)
        printf("v %f %f %f\n", model->VertexArray[i].x, model->VertexArray[i].y,
              model->VertexArray[i].z);
              
    printf("%d normal vectors\n", model->nNormal);
    for (i = 0; i < model->nNormal; i++)
        printf("v %f %f %f\n", model->NormalArray[i].x, model->NormalArray[i].y,
              model->NormalArray[i].z);
              
    printf("%d texture coordinates\n", model->nTexCoord);
    for (i = 0; i < model->nTexCoord; i++)
        printf("v %f %f\n", model->TexCoordArray[i].u, model->TexCoordArray[i].v);
        
    printf("%d faces\n", model->nFace);                        
    for (i = 0; i < model->nFace; i++)
    {
        printf("f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n",
                model->FaceArray[i].Vertex[0],
                model->FaceArray[i].TexCoord[0],
                model->FaceArray[i].Normal[0],
                model->FaceArray[i].Vertex[1],
                model->FaceArray[i].TexCoord[1],
                model->FaceArray[i].Normal[1],
                model->FaceArray[i].Vertex[2],
                model->FaceArray[i].TexCoord[2],
                model->FaceArray[i].Normal[2],
                model->FaceArray[i].Vertex[3],
                model->FaceArray[i].TexCoord[3],
                model->FaceArray[i].Normal[3]);
    }                
}

void ObjFree(ObjModel *model)
{
    free(model->NormalArray);
    free(model->TexCoordArray);
    free(model->FaceArray);
    free(model->VertexArray);
    free(model->mtllib);
    free(model->objectName);
    free(model->usemtl);

    free(model->mtl->map_Ka);
    free(model->mtl->map_Kd);
    free(model->mtl);

    free(model);
}