/**************************************************************************\
*
* Module Name: logon.cxx
*
* Copyright (c) 1997 Microsoft Corporation
*
\**************************************************************************/

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

#include "logon.hxx"
#include "logobj.hxx"
#include "util.hxx"

static GLIRECT nullRect = {0};

/**************************************************************************\
* LOG_OBJECT
*
*
\**************************************************************************/

//#define DEEP_FRAME 1

LOG_OBJECT::LOG_OBJECT()
{
    pTex = NULL;

    // Set some default size values...

    fImageSize.width = fImageSize.height = OBJECT_WIDTH;
    bShowContext = FALSE; // Only show image to start
    fCurContextWidth = 0.0f;
    fMaxContextWidth = OBJECT_WIDTH / 2.0f;

#if DEEP_FRAME
    fFrameSize.width = fFrameSize.height = FRAME_SIZE;
#else
    fFrameSize.width = fFrameSize.height = 0.0f;
#endif
    bFramed = FALSE;
    // fFrameDepth is depth offset in z-direction.  Since the frame is usually
    // drawn further away from the viewer (down -z axis), it is negative.
#if DEEP_FRAME
    fFrameDepth = - 3.3f;
#else
    fFrameDepth = - FRAME_DEPTH;
#endif
    bSwapHints = FALSE;
    dest[0] = dest[1] = dest[2] = 0.0f;
    ResetMotion();

    rect = nullRect;

    // Calc initial coords

    CalcCoords();
}

LOG_OBJECT::~LOG_OBJECT()
{
}

void    
LOG_OBJECT::ShowFrame( BOOL bShow )
{
    if( bFramed == bShow )
        return;

    // new state
    bFramed = bShow;
    if( bFramed ) {
        // mf : could optimize by not redoing this if framesize constant...
        CalcFrameCoords();
    }
}

void    
LOG_OBJECT::ShowContext( BOOL bShow )
{
    if( bShowContext == bShow )
        return;

    // new state
    bShowContext = bShow;
    CalcCoords();
//mf: !!! readjust dest to reflect this ?
}

void    
LOG_OBJECT::SetDest( float x, float y, float z )
{
    dest[0] = x;
    dest[1] = y;
    dest[2] = z;
}

void    
LOG_OBJECT::SetDest( POINT3D *pDest )
{
    dest[0] = pDest->x;
    dest[1] = pDest->y;
    dest[2] = pDest->z;
}

void    
LOG_OBJECT::GetDest( POINT3D *pDest )
{
    pDest->x = dest[0];
    pDest->y = dest[1];
    pDest->z = dest[2];
}

void
LOG_OBJECT::OffsetDest( float x, float y, float z )
{
    dest[0] += x;
    dest[1] += y;
    dest[2] += z;
}

void    
LOG_OBJECT::SetTexture( TEXTURE *pTexture )
{
    pTex = pTexture;
    if( !pTex )
        return;

    // Set the size of the object based on the aspect ratio of the texture
    // The textures for all the objects should have the same dimensions.

    float aspectRatio = pTex->GetAspectRatio();  // width/height

    // The texture is assumed to have an image on the right, and context on
    // the left, of equal size

    // We'll keep the width consant, and vary height according to aspect

    fImageSize.width = OBJECT_WIDTH / 2.0f;
    fImageSize.height = OBJECT_WIDTH / aspectRatio;
    
    fMaxContextWidth = fImageSize.width;
    fCurContextWidth = fMaxContextWidth;

    CalcCoords();
}

void    
LOG_OBJECT::SetContextSize( float fPortion )
{
    // This sets the visible portion of the context ( 0.0 - 1.0 )

    fCurContextWidth = fPortion * fMaxContextWidth;

    // Recalc coords
    CalcCoords();
}

void    
LOG_OBJECT::SetImageSize( FSIZE *pSize )
{
    fImageSize = *pSize;
    float fPortion = fCurContextWidth / fMaxContextWidth;
    fMaxContextWidth = fImageSize.width;
    fCurContextWidth = fPortion * fMaxContextWidth;
    CalcCoords();
}

void    
LOG_OBJECT::Translate()
{
    glTranslatef(dest[0]+offset[0], dest[1]+offset[1], dest[2]+offset[2]);
}

void    
LOG_OBJECT::Rotate()
{
    if( ang != 0.0f )
    	glRotatef(ang, rotAxis[0], rotAxis[1], rotAxis[2]);
}

void    
LOG_OBJECT::Draw()
{
    Draw( FALSE );
}

void    
LOG_OBJECT::Draw( BOOL bUpdateWinRect )
{
    // Draws object in its current position

    // An object either has its context showing on the right or not.  If not,
    // then origin is the centre of the image, else origin is on border of
    // image and context

    pTex->MakeCurrent(); // Set the objects texture
    glPushMatrix();
        Translate();
        Rotate();
        if( bUpdateWinRect ) {
            UpdateLocalTransforms( UPDATE_MODELVIEW_MATRIX_BIT );
            CalcWinRect( FALSE );
        }
        DrawFace(); 
        if( bFramed )
            DrawFrame();
    glPopMatrix();
    if( bSwapHints )
        AddSwapHintRect( &rect );
}

void
LOG_OBJECT::DrawFace()
{
    glNormal3f( 0.0f, 0.0f, 1.0f );
    glBegin( GL_QUADS );
        glTexCoord2fv( (float *) &texPoint[0] );
        glVertex2fv(   (float *) &point[0] );
        glTexCoord2fv( (float *) &texPoint[1] );
        glVertex2fv(   (float *) &point[1] );
        glTexCoord2fv( (float *) &texPoint[2] );
        glVertex2fv(   (float *) &point[2] );
        glTexCoord2fv( (float *) &texPoint[3] );
        glVertex2fv(   (float *) &point[3] );
    glEnd();
}

void
LOG_OBJECT::DrawFrame()
{
    glDisable( GL_TEXTURE_2D );
    glEnable( GL_LIGHTING );

    glBegin( GL_QUADS );

        glNormal3fv( (float *) &frameNormal[0] );
        glVertex3fv( (float *) &point[0] );
        glVertex3fv( (float *) &point[4] );
        glVertex3fv( (float *) &point[7] );
        glVertex3fv( (float *) &point[3] );

        glNormal3fv( (float *) &frameNormal[1] );
        glVertex3fv( (float *) &point[1] );
        glVertex3fv( (float *) &point[5] );
        glVertex3fv( (float *) &point[4] );
        glVertex3fv( (float *) &point[0] );

        glNormal3fv( (float *) &frameNormal[2] );
        glVertex3fv( (float *) &point[2] );
        glVertex3fv( (float *) &point[6] );
        glVertex3fv( (float *) &point[5] );
        glVertex3fv( (float *) &point[1] );

        glNormal3fv( (float *) &frameNormal[3] );
        glVertex3fv( (float *) &point[3] );
        glVertex3fv( (float *) &point[7] );
        glVertex3fv( (float *) &point[6] );
        glVertex3fv( (float *) &point[2] );

    glEnd();

    glDisable( GL_LIGHTING );
    glEnable( GL_TEXTURE_2D );
}

void    
LOG_OBJECT::CalcWinRect()
{
    CalcWinRect( TRUE );
}

void    
LOG_OBJECT::CalcWinRect( BOOL bTransform )
{
    POINT3D ptOut[8];
    int nPts = bFramed ? 8 : 4;

    if( bTransform ) {
        // Need to transorm object according to current translation & rotation
        glPushMatrix();
            Translate();
            Rotate();
            UpdateLocalTransforms( UPDATE_MODELVIEW_MATRIX_BIT );
        glPopMatrix();
    }

    TransformObjectToWindow( point, ptOut, nPts );

    // Update the rect member
    CalcMinMaxRect( ptOut, &rect, nPts );
}

void    
LOG_OBJECT::CalcCoords()
{
    POINT3D *pt;

    // Calculate object coords based on current state of the object.  If
    // bShowContext, then origin is in centre of object, else it's in centre
    // of image part of object.
    // Also calculate frame coords if applicable.

    // Points are ordered CCW from top right for each face

    pt = point;
    pt[0].z = pt[1].z = pt[2].z = pt[3].z = 0.0f;

    // Calc front face points

    if( ! bShowContext ) {
        POINT2D image =  { fImageSize.width / 2.0f, fImageSize.height / 2.0f };

        pt[0].x =  image.x;
        pt[0].y =  image.y;
        pt[1].x = -image.x;
        pt[1].y =  image.y;
        pt[2].x = -image.x;
        pt[2].y = -image.y;
        pt[3].x =  image.x;
        pt[3].y = -image.y;
    } else {
        POINT2D image =  { fImageSize.width, fImageSize.height / 2.0f };

        pt[0].x =  image.x;
        pt[0].y =  image.y;
        pt[1].x = -fCurContextWidth;
        pt[1].y =  image.y;
        pt[2].x = -fCurContextWidth;
        pt[2].y = -image.y;
        pt[3].x =  image.x;
        pt[3].y = -image.y;
    }

    // Calc frame points

    if( bFramed ) {
        CalcFrameCoords();
    }

    // Always need the texture coords
//mf: optimize...
    CalcTexCoords();
}

void    
LOG_OBJECT::CalcFrameCoords()
{
    POINT3D *frontPt = point;
    POINT3D *pt = point + 4;

    pt[0].z = pt[1].z = pt[2].z = pt[3].z = fFrameDepth;

    pt[0].x =  frontPt[0].x + fFrameSize.width;
    pt[0].y =  frontPt[0].y + fFrameSize.height;
    pt[1].x =  frontPt[1].x - fFrameSize.width;
    pt[1].y =  frontPt[1].y + fFrameSize.height;
    pt[2].x =  frontPt[2].x - fFrameSize.width;
    pt[2].y =  frontPt[2].y - fFrameSize.height;
    pt[3].x =  frontPt[3].x + fFrameSize.width;
    pt[3].y =  frontPt[3].y - fFrameSize.height;

    // May as well do the normals too
    CalcFrameNormals();
}

void    
LOG_OBJECT::CalcFrameNormals()
{
    POINT3D *pNorm = frameNormal;

    float fDepth = fFrameDepth;
    float fFramex = fFrameSize.width;
    float fFramey = fFrameSize.height;

    // Calc the face normals for the sides of the frame.  The faces are
    // ordered CCW from the right face.

    float norm1 = (float) fabs( fDepth );

    pNorm[0].x = norm1;
    pNorm[0].y = 0.0f;
    pNorm[0].x = fFramex;

    pNorm[1].x = 0.0f;
    pNorm[1].y = norm1;
    pNorm[1].x = fFramey;

    pNorm[0].x = -norm1;
    pNorm[0].y = 0.0f;
    pNorm[0].x = fFramex;

    pNorm[1].x = 0.0f;
    pNorm[1].y = -norm1;
    pNorm[1].x = fFramey;

    mtk_NormalizePoints( pNorm, 4 );
}

void    
LOG_OBJECT::CalcTexCoords()
{
    // Calculate texture coords based on current state of the object.

    // The texture spans across the front face, and includes the image on right,
    // and context on left.  The 't' coords are constant, but the 's' coord
    // varies, depending on if or how much context is shown.

    // Again, points are ordered CCW from top right for each face

    // Calc ratio of image over total width of object
    float ratio = fImageSize.width / (fImageSize.width + fMaxContextWidth);

    float sStart = 1.0f - ratio;

    if( bShowContext ) {
        sStart -= (fCurContextWidth / fMaxContextWidth) * (1.0f - ratio);
    }

    texPoint[0].s = texPoint[3].s = 1.0f;
    texPoint[1].s = texPoint[2].s = sStart;

    texPoint[0].t = texPoint[1].t = 1.0f;
    texPoint[2].t = texPoint[3].t = 0.0f;
}

void    
LOG_OBJECT::GetRect( GLIRECT *pRect )
{
    *pRect = rect;
}

#if 0
void    
LOG_OBJECT::GetPos( POINT3D *curpos )
{
    float *fpos = (float *) curpos;

    for( int i = 0; i < 3; i ++ )
        fpos[i] = dest[i] + offset[i];
}
#endif

/**************************************************************************\
* NextFlyIteration
*
* Calcs next offset and angle for a 'fly'sequence
*
\**************************************************************************/

BOOL
LOG_OBJECT::NextFlyIteration()
{
    int i;

	if( !iter ) {
        // Done iterating
        return FALSE;
    }

    // Calculate new iterations
    for (i = 0; i < 3; i++) {
        offset[i] = Clamp( iter,  offset[i] );
    }
	ang = Clamp( iter,  ang);
	iter--;

    return TRUE;
}

/**************************************************************************\
* ResetMotion
*
* Resets all position and motion params to 0
*
\**************************************************************************/

void
LOG_OBJECT::ResetMotion()
{
    offset[0] = offset[1] = offset[2] = 0.0f;
    rotAxis[0] = rotAxis[1] = rotAxis[2] = 0.0f;
    ang = 0.0f;
    iter = 0;
}

/**************************************************************************\
*
* Ranomnly chooses motion params for a fly sequence
*
* The motion is 'damped', since near the end of the fly sequence the object
* is constrained to 0 rotation and the dest[] position
*
\**************************************************************************/


void
LOG_OBJECT::SetDampedFlyMotion( float deviation )
{
    SetDampedFlyMotion( deviation, NULL );
}

void
LOG_OBJECT::SetDampedFlyMotion( float deviation, POINT3D *pOffset )
{
    if( pOffset ) {
        // Use supplied offset
        offset[0] = pOffset->x;
       	offset[1] = pOffset->y;
       	offset[2] = pOffset->z;
    } else {
        // Calc a random offset
        offset[0] = MyRand();
       	offset[1] = MyRand();
       	offset[2] = MyRand();
    }

	ang = 260.0f * MyRand();

   	rotAxis[0] = MyRand();
   	rotAxis[1] = MyRand();
	rotAxis[2] = MyRand();
   	iter = (int) (deviation * MyRand() + 60.0f);
}