//
// USER.CPP
// User Class Members
//
// Copyright Microsoft 1998-
//

// PRECOMP
#include "precomp.h"


//
// Local macros
//
#define ASSERT_LOCAL_USER() ASSERT(IsLocalUser() == TRUE);


//
// Init()
// This could fail...
//
BOOL WbUser::Init(POM_OBJECT hUser)
{
    ASSERT(hUser != NULL);

    m_hPageCurrent      = WB_PAGE_HANDLE_NULL;
    m_zoomed            = FALSE;
    m_hUser             = hUser;

    m_pRemotePointer = new DCWbGraphicPointer(this);
    if (!m_pRemotePointer)
    {
        ERROR_OUT(("WbUser::Init - failed to create m_pRemotePointer"));
        return(FALSE);
    }

    Refresh();
    return(TRUE);
}


//
//
// Function:    ~WbUser
//
// Purpose:     Destructor
//
//
WbUser::~WbUser(void)
{
        // don't leave any loose ends
        if ((g_pMain != NULL) && (g_pMain->GetLockOwner() == this))
        {
                g_pMain->SetLockOwner(NULL);
        g_pMain->UpdateWindowTitle();
        }

        // Free the remote pointer
        if (m_pRemotePointer != NULL)
        {
                delete m_pRemotePointer;
        m_pRemotePointer = NULL;
        }
}



//
//
// Function:    Refresh
//
// Purpose:     Read the user details and copy them to member variables.
//
//
void WbUser::Refresh(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::Refresh");

    ASSERT(m_pRemotePointer);

    // Set the flag indicating whether this is the local user
    POM_OBJECT hLocalUser;
    g_pwbCore->WBP_PersonHandleLocal(&hLocalUser);
    m_bLocalUser = (m_hUser == hLocalUser);

    // Read the external data
    WB_PERSON userDetails;
    UINT uiReturn = g_pwbCore->WBP_GetPersonData(m_hUser, &userDetails);
    if (uiReturn != 0)
    {
        DefaultExceptionHandler(WBFE_RC_WB, uiReturn);
            return;
    }

    // Get the user name
    lstrcpy(m_strName, userDetails.personName);

    // Get the sync flag
    m_bSynced  = (userDetails.synced != FALSE);

    // Get the current page
    m_hPageCurrent = userDetails.currentPage;

    // Get the current position in the page
    m_rectVisible.left   = userDetails.visibleRect.left;
    m_rectVisible.right  = userDetails.visibleRect.right;
    m_rectVisible.top    = userDetails.visibleRect.top;
    m_rectVisible.bottom = userDetails.visibleRect.bottom;

    // Get the pointer active flag. We go directly to the member variable
    // here since the SetActive member of the pointer class would re-write
    // the user information.
    m_pRemotePointer->m_bActive = (userDetails.pointerActive != 0);

    // Get the pointer page
    m_pRemotePointer->SetPage(userDetails.pointerPage);

    // Get the pointer position
    m_pRemotePointer->MoveTo(userDetails.pointerPos.x, userDetails.pointerPos.y);

    // Get the color
    m_color = g_ColorTable[userDetails.colorId % NUM_COLOR_ENTRIES];

    // Set the pointer color
    m_pRemotePointer->SetColor(m_color);
}



//
// Function:    Update
//
// Purpose:     Update the external copy of the user information
//
void WbUser::Update()
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::Update");

    // Can only update if we are the local user
    ASSERT_LOCAL_USER();

    ASSERT(m_pRemotePointer);

    // Get the local user details
    WB_PERSON userDetails;
    UINT uiReturn;

    uiReturn = g_pwbCore->WBP_GetPersonData(m_hUser, &userDetails);
    if (uiReturn != 0)
    {
        DefaultExceptionHandler(WBFE_RC_WB, uiReturn);
            return;
    }

    // Don't update the name

    // Set the sync flag
    userDetails.synced = (m_bSynced != FALSE);

    // Set the pointer active flag
    userDetails.pointerActive = (m_pRemotePointer->IsActive() != FALSE);

    // Set the page handle for the current page
    userDetails.currentPage = m_hPageCurrent;

    // Set the current position in the page
    userDetails.visibleRect.left   = (short)m_rectVisible.left;
    userDetails.visibleRect.right  = (short)m_rectVisible.right;
    userDetails.visibleRect.top    = (short)m_rectVisible.top;
    userDetails.visibleRect.bottom = (short)m_rectVisible.bottom;

    // Set the pointer page
    userDetails.pointerPage = m_pRemotePointer->Page();

    // Set the pointer position within the page
    POINT   pointerPos;

    m_pRemotePointer->GetPosition(&pointerPos);
    userDetails.pointerPos.x = (short)pointerPos.x;
    userDetails.pointerPos.y = (short)pointerPos.y;

    // Don't update the color

    // Write the user details back to the core
    uiReturn = g_pwbCore->WBP_SetLocalPersonData(&userDetails);
    if (uiReturn != 0)
    {
        // Throw exception
            DefaultExceptionHandler(WBFE_RC_WB, uiReturn);
        return;
    }
}


//
//
// Function:    PutSyncPosition
//
// Purpose:     Write the sync position from the current position of this
//              user.
//
//
void WbUser::PutSyncPosition(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::PutSyncPosition");

    // Set up a sync object
    WB_SYNC sync;

    sync.length = sizeof(WB_SYNC);

    sync.currentPage        = m_hPageCurrent;
    sync.visibleRect.top    = (short)m_rectVisible.top;         
    sync.visibleRect.left   = (short)m_rectVisible.left;        
    sync.visibleRect.bottom = (short)m_rectVisible.bottom;      
    sync.visibleRect.right  = (short)m_rectVisible.right;       
    sync.zoomed             = (TSHR_UINT16)m_zoomed;

    sync.dataOffset = (TSHR_UINT16)((BYTE *)&(sync.currentPage) - (BYTE *)&sync);

    UINT uiReturn = g_pwbCore->WBP_SyncPositionUpdate(&sync);
    if (uiReturn != 0)
    {
        // Throw an exception
        DefaultExceptionHandler(WBFE_RC_WB, uiReturn);
            return;
    }
}

//
//
// Function:    GetSyncPosition
//
// Purpose:     Get the position at which this user should be to
//              account for the current sync information. This function
//              assumes that there is a valid sync position available.
//
//
void WbUser::GetSyncPosition(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::GetSyncPosition");

    // Get the current sync position
    WB_SYNC sync;
    UINT uiReturn = g_pwbCore->WBP_SyncPositionGet(&sync);

    if (uiReturn != 0)
    {
        // Throw an exception
        DefaultExceptionHandler(WBFE_RC_WB, uiReturn);
            return;
    }

    TRACE_DEBUG(("Sync page handle = %d", sync.currentPage));

    // If the sync page is not valid, do nothing
    if (sync.currentPage != WB_PAGE_HANDLE_NULL)
    {
        // Get the current sync position
        m_hPageCurrent = sync.currentPage;

        // Now calculate the new visible rectangle
        RECT rectSyncUser;
        rectSyncUser.left   = sync.visibleRect.left;
        rectSyncUser.top    = sync.visibleRect.top;
        rectSyncUser.right  = sync.visibleRect.right;
        rectSyncUser.bottom = sync.visibleRect.bottom;

        // Check the y position of the visible rectangles
        if ((rectSyncUser.bottom - rectSyncUser.top) <= (m_rectVisible.bottom - m_rectVisible.top))
        {
            // The sync rectangle's height is smaller than our visible rectangle's
            if (rectSyncUser.top < m_rectVisible.top)
            {
                ::OffsetRect(&m_rectVisible, 0, rectSyncUser.top - m_rectVisible.top);
            }
            else if (rectSyncUser.bottom > m_rectVisible.bottom)
            {
                ::OffsetRect(&m_rectVisible, 0, rectSyncUser.bottom - m_rectVisible.bottom);
            }
        }
        else
        {
            // The sync rectangle is bigger than ours
            if (rectSyncUser.top > m_rectVisible.top)
            {
                ::OffsetRect(&m_rectVisible, 0, rectSyncUser.top - m_rectVisible.top);
            }
            else if (rectSyncUser.bottom < m_rectVisible.bottom)
            {
                ::OffsetRect(&m_rectVisible, 0, rectSyncUser.bottom - m_rectVisible.bottom);
            }
        }

        if ((rectSyncUser.right - rectSyncUser.left) <= (m_rectVisible.right - m_rectVisible.left))
        {
            // The sync rectangle's width is smaller than our visible rectangle's
            if (rectSyncUser.left < m_rectVisible.left)
            {
                ::OffsetRect(&m_rectVisible, rectSyncUser.left - m_rectVisible.left, 0);
            }
            else if (rectSyncUser.right > m_rectVisible.right)
            {
                ::OffsetRect(&m_rectVisible, rectSyncUser.right - m_rectVisible.right, 0);
            }
        }
        else
        {
            // The sync rectangle is bigger than ours
            if (rectSyncUser.left > m_rectVisible.left)
            {
                ::OffsetRect(&m_rectVisible, rectSyncUser.left - m_rectVisible.left, 0);
            }
            else if (rectSyncUser.right < m_rectVisible.right)
            {
                ::OffsetRect(&m_rectVisible, rectSyncUser.right - m_rectVisible.right, 0);
            }
        }

        m_zoomed = sync.zoomed;
    }
}

//
//
// Function:    Sync
//
// Purpose:     Sync the local user. The page and point passed as parameters
//              are used as the current sync position only if there is no
//              current sync position determined by another user.
//
//
void WbUser::Sync(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::Sync");

    ASSERT_LOCAL_USER();
    ASSERT(m_pRemotePointer);

    // Determine whether any other users are currently synced
    WbUser* pUser = WB_GetFirstUser();
    while (pUser != NULL)
    {
        // If this user is synced, we are done
        if (pUser->IsSynced())
        {
            break;
        }

        // Try the next user
        pUser = WB_GetNextUser(pUser);
    }

    // If we found a synced user, and we don't have the contents lock
    if (   (pUser != NULL)
        && (!WB_GotContentsLock()))
    {
        // Get the sync position from the core
        GetSyncPosition();
    }
    else
    {
        // Set the sync position from our own position
        PutSyncPosition();
    }

    // Update the synced member flag
    m_bSynced = TRUE;

    // Write the user details back to the core
    Update();
}

//
//
// Function:    Unsync
//
// Purpose:     Unsynchronize the users page from other synced users
//
//
void WbUser::Unsync(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::Unsync");

    ASSERT_LOCAL_USER();

    // Update the local member
    m_bSynced = FALSE;

    // Update the external details
    Update();
}


//
//
// Function:    PutPointer
//
// Purpose:     Turn on the user's remote pointer
//
//
void WbUser::PutPointer(WB_PAGE_HANDLE hPage, POINT point)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::PutPointer");

    // Check that we are the local user (we cannot do the update if we are not)
    ASSERT_LOCAL_USER();

    ASSERT(m_pRemotePointer);
    m_pRemotePointer->SetActive(hPage, point);
}

//
//
// Function:    RemovePointer
//
// Purpose:     Turn off the user's remote pointer
//
//
void WbUser::RemovePointer(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::RemovePointer");

    // Check that we are the local user (we cannot do the update if we are not)
    ASSERT_LOCAL_USER();

    // Update the remote pointer members
    ASSERT(m_pRemotePointer);
    m_pRemotePointer->m_bActive = FALSE;
    m_pRemotePointer->m_hPage = WB_PAGE_HANDLE_NULL;

    // Update the external user information
    Update();
}


//
// Function:    IsUsingPointer()
//
BOOL WbUser::IsUsingPointer(void) const
{
    ASSERT(m_pRemotePointer);
    return(m_pRemotePointer->IsActive());
}



//
// Function:    PointerPage()
//
WB_PAGE_HANDLE WbUser::PointerPage(void) const
{
    ASSERT(m_pRemotePointer);
    return(m_pRemotePointer->Page());
}



//
// Function:    GetPointerPosition()
//
void WbUser::GetPointerPosition(LPPOINT lpptPos)
{
    ASSERT(m_pRemotePointer);
    m_pRemotePointer->GetPosition(lpptPos);
}

//
//
// Function:    SetPage
//
// Purpose:     Set the user's current page
//
//
void WbUser::SetPage(WB_PAGE_HANDLE hPage)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::Page");

    // Check that we are the local user (we cannot do the update if we are not)
    ASSERT_LOCAL_USER();

    // Only make the update if it is a change
    if (m_hPageCurrent != hPage)
    {
        // Update the local member
        m_hPageCurrent = hPage;

        // Update the external information
        Update();
    }
}


//
//
// Function:    CurrentPosition
//
// Purpose:     Set the user's current position
//
//
void WbUser::SetVisibleRect(LPCRECT lprcVisible)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WbUser::SetVisibleRect");

    // Check that we are the local user (we cannot do the update if we are not)
    ASSERT_LOCAL_USER();

    // Only make the update if it is a change
    if (!::EqualRect(&m_rectVisible, lprcVisible))
    {
        // Update the local member
        m_rectVisible = *lprcVisible;

        // Update the external information
        Update();
    }
}


//
//
// Function:    operator==
//
// Purpose:     Return TRUE if the specified user is the same as this user
//
//
BOOL WbUser::operator==(const WbUser& user) const
{
    return (m_hUser == user.m_hUser);
}

//
//
// Function:    operator!=
//
// Purpose:     Return FALSE if the specified user is the same as this user
//
//
BOOL WbUser::operator!=(const WbUser& user) const
{
  return (!((*this) == user));
}

//
//
// Function:    operator=
//
// Purpose:     Copy the specified user to this one
//
//
WbUser& WbUser::operator=(const WbUser& user)
{
    // Save the new handles
    m_hUser   = user.m_hUser;

    // Read the details
    Refresh();

    return (*this);
}

//
//
// Function:    HasContentsLock
//
// Purpose:     Check whether this user has the whiteboard contents lock
//
//
BOOL WbUser::HasContentsLock(void) const
{
    // call the core to find out if we have the lock
    return (WB_LockUser() == this);
}



//
//
// Function:    WbUserList::Clear
//
// Purpose:     Clear the user handle map, removing all user objects
//
//
void WbUserList::Clear(void)
{
    // Delete all the user objects in the user map
    WbUser* pUser;
    POM_OBJECT hUser;

    ASSERT(g_pUsers);
        POSITION position = g_pUsers->GetHeadPosition();

        while (position)
        {
                POSITION posSav = position;
                pUser = (WbUser*)g_pUsers->GetNext(position);
                
                if (pUser != NULL)
                {
                delete pUser;
                }

                g_pUsers->RemoveAt(posSav);
        }

    // Remove all the map entries
    EmptyList();
}


//
//
// Function:    ~WbUserList
//
// Purpose:     Destructor
//
//
WbUserList::~WbUserList(void)
{
    // Delete all the user objects in the user map
    Clear();
}



//
//
// Function:    LockUser
//
// Purpose:     Return a user object showing who has the lock
//
//
WbUser* WB_LockUser(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WB_LockUser");

    // Get the lock status from the core (cannot fail)
    POM_OBJECT    hLockUser;
    WB_LOCK_TYPE   lockType;

    lockType = g_pwbCore->WBP_LockStatus(&hLockUser);

    // Build a result
    WbUser* pUserResult = NULL;
    if (lockType != WB_LOCK_TYPE_NONE)
    {
        pUserResult = WB_GetUser(hLockUser);
    }

    return pUserResult;
}


//
//
// Function:    Locked
//
// Purpose:     Return TRUE if another user has a lock (contents or page).
//              NOTE that the page order lock implies the contents are
//              locked.
//
//
BOOL WB_Locked(void)
{
    POM_OBJECT  pLockUser;

    return (   (g_pwbCore->WBP_LockStatus(&pLockUser) != WB_LOCK_TYPE_NONE)
          && (WB_LocalUser() != WB_LockUser()));
}

//
//
// Function:    ContentsLocked
//
// Purpose:     Return TRUE if another user has the contents lock
//
//
BOOL WB_ContentsLocked(void)
{
    POM_OBJECT  pLockUser;

    return (   (g_pwbCore->WBP_LockStatus(&pLockUser) == WB_LOCK_TYPE_CONTENTS)
          && (WB_LocalUser() != WB_LockUser()));
}


//
//
// Function:    GotLock
//
// Purpose:     Return TRUE if the local user has a lock
//
//
BOOL WB_GotLock(void)
{
    POM_OBJECT  pLockUser;

    return (   (g_pwbCore->WBP_LockStatus(&pLockUser) != WB_LOCK_TYPE_NONE)
          && (WB_LocalUser() == WB_LockUser()));
}

//
//
// Function:    GotContentsLock
//
// Purpose:     Return TRUE if the local user has the contents lock
//
//
BOOL WB_GotContentsLock(void)
{
    POM_OBJECT  pLockUser;

    return (   (g_pwbCore->WBP_LockStatus(&pLockUser) == WB_LOCK_TYPE_CONTENTS)
          && (WB_LocalUser() == WB_LockUser()));
}



//
//
// Function:    PresentationMode
//
// Purpose:     Return TRUE if the whiteboard is in presentation mode, i.e.
//              another user has the contents lock, and is synced.
//
//
BOOL WB_PresentationMode(void)
{
    return (   (WB_ContentsLocked())
          && (WB_LockUser() != NULL)
          && (WB_LockUser()->IsSynced()));
}



//
//
// Function:    GetUser
//
// Purpose:     Return a pointer to a user object from a user handle
//
//
WbUser* WB_GetUser(POM_OBJECT hUser)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WB_GetFirstUser");

    // Set up a return value
    WbUser* pUserResult = NULL;

    // if the user handle is null, we return a null object pointer
    if (hUser != NULL)
    {
        // Look the user up in the internal map
        ASSERT(g_pUsers);
                POSITION position = g_pUsers->GetHeadPosition();

                BOOL bFound = FALSE;
                while (position)
                {
                        pUserResult = (WbUser*)g_pUsers->GetNext(position);
                        if (hUser == pUserResult->Handle())
                        {
                                return pUserResult;
                        }
                }

        // The user is not yet in our map
        pUserResult = new WbUser();
        if (!pUserResult)
        {
            ERROR_OUT(("Couldn't allocate user object for 0x%08x", hUser));
        }
        else
        {
            if (!pUserResult->Init(hUser))
            {
                ERROR_OUT(("Couldn't init user object for 0x%08x", hUser));
                delete pUserResult;
                pUserResult = NULL;
            }
            else
            {
                // Add the new user to the internal map
                g_pUsers->AddTail(pUserResult);
            }
        }
    }

    return pUserResult;
}

//
//
// Function:    GetFirstUser
//
// Purpose:     Return the first user in the call
//
//
WbUser* WB_GetFirstUser(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WB_GetFirstUser");

    // Get the handle of the first user (cannot fail)
    POM_OBJECT hUser;
    g_pwbCore->WBP_PersonHandleFirst(&hUser);

    // Get a pointer to the user object for this handle
    WbUser* pUser = WB_GetUser(hUser);

    return pUser;
}

//
//
// Function:    GetNextUser
//
// Purpose:     Return the next user in the call
//
//
WbUser* WB_GetNextUser(const WbUser* pUser)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WB_GetNextUser");
    ASSERT(pUser != NULL);

    // Get the handle of the next user
    POM_OBJECT hNextUser;
    UINT uiReturn = g_pwbCore->WBP_PersonHandleNext(pUser->Handle(),
                                           &hNextUser);

    WbUser* pUserResult = NULL;
    if (uiReturn == 0)
    {
        pUserResult = WB_GetUser(hNextUser);
    }

    return pUserResult;
}

//
//
// Function:    LocalUser
//
// Purpose:     Return an object representing the local user
//
//
WbUser* WB_LocalUser(void)
{
    MLZ_EntryOut(ZONE_FUNCTION, "WB_LocalUser");

    // Get the local user handle (cannot fail)
    POM_OBJECT hUser;
    g_pwbCore->WBP_PersonHandleLocal(&hUser);

    return WB_GetUser(hUser);
}