#include "math3d.h"

void m3dFindNormal(M3DVector3f result, const M3DVector3f point1, 
                   const M3DVector3f point2, const M3DVector3f point3)
{
    M3DVector3f v1, v2;

    /* calculate two vectors from the three points, assumes 
     * counter clockwise winding
     */
    v1[0] = point1[0] - point2[0];
    v1[1] = point1[1] - point2[1];
    v1[2] = point1[2] - point2[2];

    v2[0] = point2[0] - point3[0];
    v2[1] = point2[1] - point3[1];
    v2[2] = point2[2] - point3[2];

    /* take the cross product of the two vectors to get the normal vector */
    m3dCrossProduct(result, v1, v2);
}

void m3dLoadIdentity44(M3DMatrix44f m) /* 4x4 float */
{
    /* don't be fooled, this is still column major */
    static M3DMatrix44f identity = { 1.0f, 0.0f, 0.0f, 0.0f,
                                     0.0f, 1.0f, 0.0f, 0.0f,
                                     0.0f, 0.0f, 1.0f, 0.0f,
                                     0.0f, 0.0f, 0.0f, 1.0f };

    memcpy(m, identity, sizeof(M3DMatrix44f));
}

/* creates a 4x4 rotation matrix, takes radians not degrees */
void m3dRotationMatrix44(M3DMatrix44f m, float angle, float x, float y, float z)
{
    float mag, s, c;
    float xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c;

    s = (float) (sin(angle));
    c = (float) (cos(angle));

    mag = (float) (sqrt(x*x + y*y + z*z));

    /* identity matrix */
    if (mag == 0.0f)
    {
        m3dLoadIdentity44(m);
        return;
    }

    /* rotation matrix is normalized */
    x /= mag;
    y /= mag;
    z /= mag;

    #define M(row,col) m[col*4+row]

    xx = x * x;
    yy = y * y;
    zz = z * z;
    xy = x * y;
    yz = y * z;
    zx = z * x;

    xs = x * s;
    ys = y * s;
    zs = z * s;
    one_c = 1.0f - c;

    M(0,0) = (one_c * xx) + c;
    M(0,1) = (one_c * xy) - zs;
    M(0,2) = (one_c * zx) + ys;
    M(0,3) = 0.0f;

    M(1,0) = (one_c * xy) + zs;
    M(1,1) = (one_c * yy) + c;
    M(1,2) = (one_c * yz) - xs;
    M(1,3) = 0.0f;

    M(2,0) = (one_c * zx) - ys;
    M(2,1) = (one_c * yz) + xs;
    M(2,2) = (one_c * zz) + c;
    M(2,3) = 0.0f;

    M(3,0) = 0.0f;
    M(3,1) = 0.0f;
    M(3,2) = 0.0f;
    M(3,3) = 1.0f;

    #undef M
}

void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, 
                         const M3DMatrix44f b)
{
    #define A(row,col)  a[(col<<2)+row]
    #define B(row,col)  b[(col<<2)+row]
    #define P(row,col)  product[(col<<2)+row]

    int i;
    for (i = 0; i < 4; i++)
    {
        float ai0 = A(i, 0), ai1 = A(i, 1), ai2 = A(i, 2), ai3 = A(i, 3);
        P(i, 0) = ai0 * B(0, 0) + ai1 * B(1, 0) + ai2 * B(2, 0) + ai3 * B(3, 0);
        P(i, 1) = ai0 * B(0, 1) + ai1 * B(1, 1) + ai2 * B(2, 1) + ai3 * B(3, 1);
        P(i, 2) = ai0 * B(0, 2) + ai1 * B(1, 2) + ai2 * B(2, 2) + ai3 * B(3, 2);
        P(i, 3) = ai0 * B(0, 3) + ai1 * B(1, 3) + ai2 * B(2, 3) + ai3 * B(3, 3);
    }
    
    #undef A
    #undef B
    #undef P
}

GLint gltWriteTGA(const char *szFileName)
{
    FILE *pFile;              /* file pointer */
    TGAHEADER tgaHeader;      /* tga file header */
    unsigned long lImageSize; /* size in bytes of image */
    GLbyte *pBits = NULL;     /* pointer to bits */
    GLint iViewport[4];       /* viewport in pixels */
    GLenum lastBuffer;        /* storage for the current read buffer setting */

    /* get the viewport dimensions */
    glGetIntegerv(GL_VIEWPORT, iViewport);

    /* how big is the image going to be (targas are tightly packed) */
    lImageSize = iViewport[2] * 3 * iViewport[3];

    /* allocate block, if this doesn't work, go home */
    pBits = (GLbyte *) malloc(lImageSize);
    if (pBits == NULL)
    {
        perror("malloc");
        return 0;
    }

    /* read bits from color buffer */
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);

    /* get the current read buffer setting and save it, switch to
     * the front buffer and do the read operation, finally, restore
     * the read buffer state
     */
    glGetIntegerv(GL_READ_BUFFER, (GLint *) &lastBuffer);
    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT,
    GL_UNSIGNED_BYTE, pBits);
    glReadBuffer(lastBuffer);

    /* initialize the targa header */
    tgaHeader.identsize = 0;
    tgaHeader.colorMapType = 0;
    tgaHeader.imageType = 2;
    tgaHeader.colorMapStart = 0;
    tgaHeader.colorMapLength = 0;
    tgaHeader.colorMapBits = 0;
    tgaHeader.xstart = 0;
    tgaHeader.ystart = 0;
    tgaHeader.width = iViewport[2];
    tgaHeader.height = iViewport[3];
    tgaHeader.bits = 24;
    tgaHeader.descriptor = 0;

    /* attempt to open the file */
    pFile = fopen(szFileName, "wb");
    if (pFile == NULL)
    {
        perror("fopen");
        free(pBits); /* free buffer and return error */
        return 0;
    }
    
    /* write the header */
    fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);

    /* write the image data */
    fwrite(pBits, lImageSize, 1, pFile);

    /* free temporary buffer and close the file */
    free(pBits);
    fclose(pFile);

    return 1;
}

GLbyte *gltLoadTGA(const char *szFileName, GLint *iWidth, GLint *iHeight, 
                   GLint *iComponents, GLenum *eFormat)
{
    FILE *pFile;              /* file pointer */
    TGAHEADER tgaHeader;      /* TGA file header */
    unsigned long lImageSize; /* size in bytes of image */
    short sDepth;             /* pixel depth; */
    GLbyte *pBits = NULL;     /* pointer to bits */

    /* default/failed values */
    *iWidth = 0;
    *iHeight = 0;
    *eFormat = GL_BGR_EXT;
    *iComponents = GL_RGB8;

    /* attempt to open the file */
    pFile = fopen(szFileName, "rb");
    if (pFile == NULL)
    {
        perror("fopen");
        return 0;
    }

    /* read in header (binary) */
    fread(&tgaHeader, 18 /* sizeof(TGAHEADER) */, 1, pFile);

    /* get width, height, and depth of texture */
    *iWidth = tgaHeader.width;
    *iHeight = tgaHeader.height;
    sDepth = tgaHeader.bits / 8;

    /* put some validity checks here, very simply, i only understand
     * or care about 8, 24, or 32 bit targas
     */
    if (tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
        return NULL;

    /* calculate size of image buffer */
    lImageSize = tgaHeader.width * tgaHeader.height * sDepth;

    /* allocate memory and check for success */
    pBits = (GLbyte *) malloc(lImageSize * sizeof(GLbyte));
    if (pBits == NULL)
    {
        perror("malloc");
        return NULL;
    }

    /* read in the bits */
    /* check for read error, this should catch rle or other  */
    /* weird formats that i don't want to recognize */
    if (fread(pBits, lImageSize, 1, pFile) != 1)
    {
        perror("fread");
        free(pBits);
        return NULL;
    }
    
    /* set opengl format expected */
    switch (sDepth)
    {
        case 3: /* most likely case */
            *eFormat = GL_BGR_EXT;
            *iComponents = GL_RGB8;
            break;
        case 4:
            *eFormat = GL_BGRA_EXT;
            *iComponents = GL_RGBA8;
            break;
        case 1:
            *eFormat = GL_LUMINANCE;
            *iComponents = GL_LUMINANCE8;
            break;
    }

    /* done with file */
    fclose(pFile);

    /* return pointer to image data */
    return pBits;
}