/*
 * Handling binary matrix representations.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>

#include "batypes.h"
#include "batype.h"
#include "balibtyp.h"
#include "badebug.h"
#include "bamemory.h"

#include "bamatrix.h"

#include "babinary.h"
#include "batkreg.h"
#include "batkbin.h"
#include "bavector.h"

#include "babinmtx.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: babinmtx.c,v 1.44 2001/05/01 10:48:03 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

#ifndef _ORTHONORMAL_TOL
#define _ORTHONORMAL_TOL ((RwReal)0.01)
#endif /* _ORTHONORMAL_TOL */

#ifndef _IDENTITY_TOL
#define _IDENTITY_TOL ((RwReal)0.01)
#endif /* _IDENTITY_TOL */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Matrix Binary Format Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/**
 * \ingroup rwmatrix
 * \ref RwMatrixStreamGetSize determines the size in bytes of the binary
 * representation of the given matrix. This is used in the binary chunk
 * header to indicate the size of the chunk.
 *
 * \param  matrix   Pointer to matrix for which 
 * the size is required.
 *
 * \return Returns the chunk size of the matrix in bytes or zero if
 * there an error.
 *
 * \see RwMatrixStreamRead
 * \see RwMatrixStreamWrite
 */
RwUInt32
RwMatrixStreamGetSize(const RwMatrix * __RWUNUSEDRELEASE__ matrix )
{
    const RwUInt32 result = sizeof(rwStreamMatrix) + rwCHUNKHEADERSIZE;
    RWAPIFUNCTION(RWSTRING("RwMatrixStreamGetSize"));
    RWASSERT(matrix);

    RWRETURN(result);
}

/**
 * \ingroup rwmatrix
 * \ref RwMatrixStreamWrite writes the specified matrix to a binary
 * stream. Note that the stream will have been opened prior to this function
 * call.
 *
 * \param matrix  Pointer to the matrix to be written.
 * \param stream  Pointer to the stream.
 *
 * \return Returns pointer to the matrix if successful or NULL if there is
 * an error.
 *
 * \see RwMatrixStreamRead
 * \see RwMatrixStreamGetSize
 * \see RwStreamOpen
 * \see RwStreamClose
 *
 */
const RwMatrix *
RwMatrixStreamWrite(const RwMatrix *matrix, RwStream *stream)
{
    RwBool      status;
    rwStreamMatrix   mat;
    RwStream *check;
    RwMatrix source;

    RWAPIFUNCTION(RWSTRING("RwMatrixStreamWrite"));
    RWASSERT(matrix);
    RWASSERT(stream);

    rwMatrixInitialize(&source, 0);

    if (rwMATRIXTYPEORTHONORMAL ==
        rwMatrixTestFlags(matrix, rwMATRIXTYPEMASK) )
    {
        RwMatrixOrthoNormalize(&source, matrix);
    }
    else
    {
        RwMatrixCopy(&source, matrix);
    }

    check = RwStreamWriteChunkHeader(stream, 
                                     rwID_MATRIX,
                                     RwMatrixStreamGetSize(&source));

    status =  ( NULL !=  check);
    if (!status)
    {
        RWERROR((E_RW_WRITE));
        RWRETURN((const RwMatrix *)NULL);
    }

    check = RwStreamWriteChunkHeader(stream, rwID_STRUCT, sizeof(mat));
    
    status = ( NULL != check );
    if (!status)
    {
        RWERROR((E_RW_WRITE));
        RWRETURN((const RwMatrix *)NULL);
    }

    /* Fill it */
    mat.type = rwMatrixTestFlags(&source, rwMATRIXTYPEMASK);

    RwV3dAssign(&mat.right, &source.right);
    RwV3dAssign(&mat.up, &source.up);
    RwV3dAssign(&mat.at, &source.at);
    RwV3dAssign(&mat.pos, &source.pos);
    
    /* Convert it */
    RwMemRealToFloat32(&mat.right, sizeof(mat.right));
    RwMemRealToFloat32(&mat.up, sizeof(mat.up));
    RwMemRealToFloat32(&mat.at, sizeof(mat.at));
    RwMemRealToFloat32(&mat.pos, sizeof(mat.pos));
    RwMemLittleEndian(&mat, sizeof(mat));

    /* Write it */
    status = ( NULL !=  RwStreamWrite(stream, &mat, sizeof(mat)));
    if (!status)
    {
        RWERROR((E_RW_WRITE));
        matrix = (const RwMatrix *)NULL;
    }

    RWRETURN(matrix);
}

/**
 * \ingroup rwmatrix
 * \ref RwMatrixChunkInfoRead extracts Chunk Info data from a RenderWare
 * stream. The data is converted from its original format and inserted into
 * an RwMatrixChunkInfo structure. A pointer to this structure is returned
 * on success.
 *
 * \param stream  Pointer to the RenderWare stream object.
 * \param matrixChunkInfo  Pointer to the matrixChunkInfo structure to be filled by the data.
 * \param bytesRead  Pointer to an RwInt32 which will hold the number of the number of
 *               bytes read.
 *
 * \return Returns a pointer to the filled structure if successful. 
 * Returns NULL if an error occurred.
 * 
 * \see RwMatrixStreamRead
 * \see RwMatrixStreamWrite
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RwStreamFindChunk
 */

RwMatrixChunkInfo *
RwMatrixChunkInfoRead(RwStream *stream, 
                      RwMatrixChunkInfo *matrixChunkInfo,
                      RwInt32 *bytesRead)
{
    RwBool              status;
    RwUInt32            size;
    RwUInt32            readSize;

    RWAPIFUNCTION(RWSTRING("RwMatrixChunkInfoRead"));
    RWASSERT(stream);
    RWASSERT(matrixChunkInfo);

    status = RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                               &size, (RwUInt32 *)NULL);

    if (!status)
    {
        RWERROR((E_RW_READ));
        RWRETURN((rwStreamMatrix *)NULL);
    }

    RWASSERT(size < sizeof(RwMatrixChunkInfo));
    readSize = sizeof(RwMatrixChunkInfo);
    memset(matrixChunkInfo, 0, readSize);

    status = ( readSize == 
               RwStreamRead(stream, matrixChunkInfo, readSize) );
    
    if (!status)
    {
        RWERROR((E_RW_READ));
        RWRETURN((rwStreamMatrix *)NULL);
    }

    *bytesRead = size + (sizeof(RwInt32) * 3);
    /* move on to known place */
    RwStreamSkip(stream, size - readSize);

    RwMemNative(matrixChunkInfo, sizeof(RwMatrixChunkInfo));
    RwMemFloat32ToReal(&matrixChunkInfo->right,
                       sizeof(matrixChunkInfo->right));
    RwMemFloat32ToReal(&matrixChunkInfo->up, sizeof(matrixChunkInfo->up));
    RwMemFloat32ToReal(&matrixChunkInfo->at, sizeof(matrixChunkInfo->at));
    RwMemFloat32ToReal(&matrixChunkInfo->pos, sizeof(matrixChunkInfo->pos));

    RWRETURN(matrixChunkInfo);
}

/**
 * \ingroup rwmatrix
 * \ref RwMatrixStreamRead reads a matrix from a binary stream. If the 
 * matrix argument is NULL then a new matrix is created, otherwise the 
 * specified one is over-written. Note that prior to this function call a
 * binary matrix chunk must be found in the stream using the
 * \ref RwStreamFindChunk API function.
 *
 * \param stream  Pointer to the stream the matrix will be read from.  
 * \param matrix  Pointer to a matrix which will receive the data from the stream. 
 *       If one is not provided by the application (NULL is passed), 
 *       then one will be created.
 *
 * \return Returns pointer to the matrix if successful or NULL if there
 *        is an error.
 *
 * \see RwMatrixStreamWrite
 * \see RwMatrixStreamGetSize
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RwStreamFindChunk
 *
 * \verbatim
   The sequence to locate and read a matrix from a binary stream connected
   to a disk file is as follows: 
   
   RwStream *stream;
   RwMatrix *NewMatrix;
  
   stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "mybinary.xxx");
   if( stream )
   {
       if( RwStreamFindChunk(stream, rwID_MATRIX, NULL, NULL) )
       {
           NewMatrix = RwMatrixStreamRead(stream);
       }
  
       RwStreamClose(stream, NULL);
   }
   \endverbatim
 * 
 */
RwMatrix *
RwMatrixStreamRead(RwStream *stream, RwMatrix *matrix)
{
    RwBool              status;
    RwUInt32            size;
    RwUInt32            version;

    RWAPIFUNCTION(RWSTRING("RwMatrixStreamRead"));
    RWASSERT(stream);
    RWASSERT(matrix);

    status = RwStreamFindChunk(stream, rwID_STRUCT, &size, &version);

    if (status)
    {
        if ((version >= rwLIBRARYBASEVERSION) &&
            (version <= rwLIBRARYCURRENTVERSION))
        {
            rwStreamMatrix           mat;
            RwUInt32                 bytes;

            RWASSERT(size <= sizeof(mat));

            memset(&mat, 0, sizeof(mat));

            bytes = RwStreamRead(stream, &mat, size) ;

            status = ( size == bytes );
        
            if (status)
            {
                RwMemNative(&mat, sizeof(mat));

                RwMemFloat32ToReal(&mat.right, sizeof(mat.right));
                RwMemFloat32ToReal(&mat.up, sizeof(mat.up));
                RwMemFloat32ToReal(&mat.at, sizeof(mat.at));
                RwMemFloat32ToReal(&mat.pos, sizeof(mat.pos));

                if (!matrix)
                {
                    /* need to create it */
                    matrix = RwMatrixCreate();
                }

                if (matrix)
                {
                    /* Copy over the type and flags */
                    const RwUInt32 flags = ( mat.type & 
                                             (RwInt32) rwMATRIXTYPEMASK);

                    rwMatrixInitialize(matrix, 0);

                    *RwMatrixGetRight(matrix) = mat.right;
                    *RwMatrixGetUp(matrix) = mat.up;
                    *RwMatrixGetAt(matrix) = mat.at;
                    *RwMatrixGetPos(matrix) = mat.pos;

                    /* Copy the matrix type */
                    rwMatrixSetFlags(matrix, flags);

                    if ( rwMATRIXTYPEORTHONORMAL ==
                         (rwMATRIXTYPEORTHONORMAL & flags) )
                    {
                        RwMatrixOrthoNormalize(matrix, matrix);
                    }

                    /* Check flags */
                    RWASSERT( ( (rwMATRIXTYPEORTHONORMAL & flags) !=
                                (rwMATRIXTYPEORTHONORMAL) ) ||
                              rwMatrixIsOrthonormal(matrix, 
                                                    _ORTHONORMAL_TOL));

                    RWASSERT( ( (rwMATRIXINTERNALIDENTITY & flags) !=
                                (rwMATRIXINTERNALIDENTITY) ) ||
                              rwMatrixIsIdentity(matrix, _IDENTITY_TOL) );
                }
            }
            else
            {
                RWERROR((E_RW_READ));
                RWRETURN((RwMatrix *)NULL);
            }
        }
        else
        {
            RWERROR((E_RW_BADVERSION));
            matrix = (RwMatrix *)NULL;
        }
    }
    else
    {
        RWERROR((E_RW_READ));
        matrix = ((RwMatrix *)NULL);
    }

    RWRETURN(matrix);

}


