/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  pfKI Functions                                                          //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "pfKI.h"
#include "plgDispatch.h"
#include "hsResMgr.h"

#include "../pfGameGUIMgr/pfGameGUIMgr.h"
#include "../pfGameGUIMgr/pfGUITagDefs.h"
#include "../pfGameGUIMgr/pfGUIDialogMod.h"
#include "../pfGameGUIMgr/pfGUIControlHandlers.h"
#include "../pfGameGUIMgr/pfGUIDialogHandlers.h"
#include "../pfGameGUIMgr/pfGUIEditBoxMod.h"
#include "../pfGameGUIMgr/pfGUIListBoxMod.h"
#include "../pfGameGUIMgr/pfGUIButtonMod.h"
#include "../pfGameGUIMgr/pfGUIListElement.h"
#include "../pfGameGUIMgr/pfGUITextBoxMod.h"
#include "../pfGameGUIMgr/pfGUIRadioGroupCtrl.h"

#include "../plGImage/plDynamicTextMap.h"

#include "../plNetClient/plNetClientMgr.h"
#include "../plNetClient/plNetKI.h"
#include "../pnNetCommon/plNetMsg.h"
#include "../plNetTransport/plNetTransportMember.h"
#include "../pfMessage/pfKIMsg.h"
#include "../plMessage/plMemberUpdateMsg.h"
#include "../pnMessage/plTimeMsg.h"


#include "../pnMessage/plRemoteAvatarInfoMsg.h"

#define kKITempID_ListOfLists       25
#define kKITempID_MsgDestRadio      26
#define kKITempID_SendBtn           27

#define kKITempID_CurrPlayerText    30
#define kKITempID_PlayerList        31
#define kKITempID_ChatModeBtn       32

#define kKITempID_BlackBarDlg       33
#define kKITempID_BarKIButtons      34

#define kDesiredKIVersion           0

#define kMaxNumChatItems            42


//// Static Class Stuff //////////////////////////////////////////////////////

pfKI    *pfKI::fInstance = nil;


//// Dialog Proc Definitions /////////////////////////////////////////////////

class plKIYesNoBox : public pfGUIDialogProc
{
    protected:

        pfGUICtrlProcObject *fCBProc;
        UInt32              fNoCBValue, fYesCBValue;

    public:

        plKIYesNoBox() { fCBProc = nil; fNoCBValue = 0; fYesCBValue = 0; }

        virtual void    DoSomething( pfGUIControlMod *ctrl )
        {
            UInt32  cbValue = 0;

            if( ctrl->GetTagID() == kKIYesBtn )
            {
                cbValue = fYesCBValue;
            }
            else if( ctrl->GetTagID() == kKINoBtn )
            {
                cbValue = fNoCBValue;
            }
            fDialog->Hide();

            // Call da callback
            if( fCBProc != nil )
                fCBProc->UserCallback( cbValue );
        }

        void    SetMessage( const char *msg )
        {
            pfGUITextBoxMod *ctrl = pfGUITextBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKIStaticText ) );
            ctrl->SetText( msg );
        }

        void    Ask( const char *msg, pfGUICtrlProcObject *callbackProc, UInt32 noCBValue, UInt32 yesCBValue )
        {
            SetMessage( msg );
            fCBProc = callbackProc;
            fNoCBValue = noCBValue;
            fYesCBValue = yesCBValue;
            fDialog->Show();
        }
};

//// BlackBar Proc ///////////////////////////////////////////////////////////

class plBlackBarProc : public pfGUIDialogProc
{
    protected:
        
        pfGUIDialogMod  *fKIMiniDlg, *fKIMainDlg;
        pfGUIDialogProc *fOrigProc;

        static bool                 fExpected;
        static pfGUIRadioGroupCtrl  *fKIButtons;

    public:

        plBlackBarProc( pfGUIDialogMod *miniKI, pfGUIDialogMod *mainKI, pfGUIDialogProc *origProc ) 
        {
            fOrigProc = origProc;
            if( fOrigProc != nil )
                fOrigProc->IncRef();

            fKIMiniDlg = miniKI;
            fKIMainDlg = mainKI;
            fExpected = false;
        }

        virtual ~plBlackBarProc() 
        {
            if( fOrigProc != nil && fOrigProc->DecRef() )
                delete fOrigProc;
        }

        virtual void    OnShow( void )
        {
            fKIButtons = pfGUIRadioGroupCtrl::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_BarKIButtons ) );
            if( fKIButtons != nil )
            {
                fKIButtons->SetValue( -1 );
                fKIMiniDlg->Hide();
                fKIMainDlg->Hide();
            }
            if( fOrigProc != nil ) fOrigProc->OnShow();
        }

        virtual void    OnHide( void ) { if( fOrigProc != nil ) fOrigProc->OnHide(); }
        virtual void    OnInit( void ) { if( fOrigProc != nil ) fOrigProc->OnInit(); }

        virtual void    DoSomething( pfGUIControlMod *ctrl )
        {
            if( ctrl->GetTagID() == kKITempID_BarKIButtons )
            {
                fExpected = true;
                if( fKIButtons->GetValue() == 0 )   fKIMiniDlg->Show();     else    fKIMiniDlg->Hide();
                if( fKIButtons->GetValue() == 1 )   fKIMainDlg->Show();     else    fKIMainDlg->Hide();
                fExpected = false;
            }
            else if( fOrigProc != nil )
                fOrigProc->DoSomething( ctrl );
        }

        static void     ClearKIButtons( void )
        {
            if( fKIButtons != nil && !fExpected )
                fKIButtons->SetValue( -1 );
        }
};

bool                plBlackBarProc::fExpected = false;
pfGUIRadioGroupCtrl *plBlackBarProc::fKIButtons = nil;


//// KIMain Proc /////////////////////////////////////////////////////////////

class plKIMainProc : public pfGUIDialogProc
{
    protected:  

        pfGUIListBoxMod *fOther;
        plKIYesNoBox    *fYesNoDlg;
        pfGUIDialogMod  *fMiniDlg;

        plKIFolder      *fKIFolder;

    public:
        plKIAddEditBox *fAddRemoveHandler;

        plKIMainProc( pfGUIControlMod *other, plKIYesNoBox *yesNo, pfGUIDialogMod *miniDlg );

        virtual void    DoSomething( pfGUIControlMod *ctrl );
        virtual void    OnHide( void );
        virtual void    UserCallback( UInt32 userValue );       
        virtual void    OnInit( void );
        virtual void    OnShow( void );

        void    GrabVaultFolder( void );
        void    UpdateTextList( void );
        void    UpdateTextPreview( void );

        void    AddNewItem( const char *title, const char *text );
        void    EditCurrItem( const char *newText );

        void    SetCurrentAvatar( plKey avKey );
};

class plKIAddEditBox : public pfGUIDialogProc
{
    protected:
        pfGUIListBoxMod *fList;
        pfGUIDialogMod  *fParentDlg;
        plKIMainProc    *fMainProc;

    public:
        hsBool  fEditing;

        plKIAddEditBox( pfGUIListBoxMod *list, pfGUIDialogMod *p, plKIMainProc *mainProc )
        {
            fList = list; 
            fParentDlg = p; 
            fEditing = false; 
            fMainProc = mainProc;
        }

        virtual void    DoSomething( pfGUIControlMod *ctrl )
        {
            if( ctrl->GetTagID() != kKINoBtn )
            {
                // Get the string and add it to the list
                pfGUIEditBoxMod *edit = pfGUIEditBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITestEditBox ) );
                if( ctrl != (pfGUIControlMod *)edit || !edit->WasEscaped() )
                {
                    if( fEditing )
                        fMainProc->EditCurrItem( edit->GetBuffer() );
                    else
                        fMainProc->AddNewItem( "TestTitle", edit->GetBuffer() );
                }
            }
            fEditing = false;

            // Both controls should close the dialog
            fDialog->Hide();
        }

        virtual void    OnShow( void )
        {
            pfGUIEditBoxMod *edit = pfGUIEditBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITestEditBox ) );
            if( fEditing )
            {
                if( fList->GetSelection() != -1 )
                {
                    pfGUIListElement *el = fList->GetElement( fList->GetSelection() );
                    if( el->GetType() == pfGUIListElement::kText )
                        edit->SetText( ( (pfGUIListText *)el )->GetText() );
                }
            }
            else
                edit->ClearBuffer();
            fDialog->SetFocus( edit );
        }
};

class pfKITextItemElement : public pfGUIListText
{
    protected:
        
        plKITextNoteElement *fDataItem;

    public:
        pfKITextItemElement( plKITextNoteElement *source ) : pfGUIListText()
        {
            fDataItem = source;
        }

        virtual const char  *GetText( void ) { return fDataItem->GetText(); }
        virtual void        SetText( const char *text ) { fDataItem->SetText( text ); }

        plKITextNoteElement *GetSource( void ) { return fDataItem; }
};

class pfKIListItemElement : public pfGUIListText
{
    protected:

        plKIFolder  *fFolder;

    public:
        pfKIListItemElement( plKIFolder *folder) : pfGUIListText(), fFolder( folder ) {}

        virtual const char  *GetText( void ) { return fFolder->GetName(); }
        virtual void        SetText( const char *text ) { }

        plKIFolder  *GetFolder( void ) { return fFolder; }
};

plKIMainProc::plKIMainProc( pfGUIControlMod *other, plKIYesNoBox *yesNo, pfGUIDialogMod *miniDlg ) 
{
    fOther = (pfGUIListBoxMod *)other;
    fYesNoDlg = yesNo; 
    fMiniDlg = miniDlg;
    fKIFolder = nil;
}

void    plKIMainProc::DoSomething( pfGUIControlMod *ctrl )
{
    if( ctrl->GetTagID() == kKIAddButton )
    {
        pfGUIDialogMod  *dlg = pfGameGUIMgr::GetInstance()->GetDialogFromTag( kKIEntryDlg );
        dlg->Show();
    }
    else if( ctrl->GetTagID() == kKIEditButton )
    {
        // Edit btn
        if( fOther->GetSelection() != -1 )
        {
            pfGUIDialogMod  *dlg = pfGameGUIMgr::GetInstance()->GetDialogFromTag( kKIEntryDlg );
            fAddRemoveHandler->fEditing = true;
            dlg->Show();
        }
    }
    else if( ctrl->GetTagID() == kKIRemoveButton )
    {
        // Remove btn - remove all selected items
        if( fOther->GetSelection() != -1 )
            fYesNoDlg->Ask( "Are you sure you wish to remove this item?", this, 0, 1 );
    }
    else if( ctrl->GetTagID() == kKITestEditBox )   // Yeah, yeah, i know
    {
        // List box. Gets sent/called when selection changes.
        UpdateTextPreview();
    }
    else if( ctrl->GetTagID() == kKITempID_ListOfLists )
    {
        // Change lists
        pfGUIListBoxMod *list = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_ListOfLists ) );   // Temp tag ID
        if( list->GetSelection() != -1 )
        {
            pfKIListItemElement *whichList = (pfKIListItemElement *)list->GetElement( list->GetSelection() );
            fKIFolder = whichList->GetFolder();
        }
        else
            fKIFolder = nil;
        UpdateTextList();
    }
}

void    plKIMainProc::UpdateTextPreview( void )
{
    pfGUIControlMod *editBtn = fDialog->GetControlFromTag( kKIEditButton );
    pfGUIControlMod *removeBtn = fDialog->GetControlFromTag( kKIRemoveButton );
    pfGUIControlMod *sendBtn = fDialog->GetControlFromTag( kKITempID_SendBtn );

    pfGUITextBoxMod *text = pfGUITextBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITestControl2 ) );
    if( fOther->GetSelection() != -1 && fOther->GetElement( fOther->GetSelection() )->GetType() == pfGUIListElement::kText )
    {
        pfGUIListText *element = (pfGUIListText *)fOther->GetElement( fOther->GetSelection() );
        text->SetText( element->GetText() );

        sendBtn->SetVisible( true );
        editBtn->SetVisible( true );
        removeBtn->SetVisible( true );
    }
    else
    {
        text->SetText( "" );
        sendBtn->SetVisible( false );
        editBtn->SetVisible( false );
        removeBtn->SetVisible( false );
    }
}

void    plKIMainProc::AddNewItem( const char *title, const char *text )
{
    if( fKIFolder != nil )
    {
        plKITextNoteElement *item = new plKITextNoteElement();
        item->SetTitle( title );
        item->SetText( text );
        fKIFolder->AddElement(item);
    }
}

void    plKIMainProc::EditCurrItem( const char *newText )
{
    pfKITextItemElement *listEl = (pfKITextItemElement *)fOther->GetElement( fOther->GetSelection() );
    listEl->GetSource()->SetText( newText );
}

void    plKIMainProc::OnHide( void )
{
    plBlackBarProc::ClearKIButtons();
}

void    plKIMainProc::UserCallback( UInt32 userValue )
{
    if( userValue == 1 )
    {
        // Yes/no callback for removing an element
        if( fOther->GetSelection() != -1 )
        {
            pfKITextItemElement *listEl = (pfKITextItemElement *)fOther->GetElement( fOther->GetSelection() );

            // This will result in a callback to update our list
            fKIFolder->RemoveElement( listEl->GetSource() );
        }
    }
}

void    plKIMainProc::OnShow( void )
{
    GrabVaultFolder();
    UpdateTextList();
}

void    plKIMainProc::GrabVaultFolder( void ) 
{
    Int16 sel = -1;


    plKI *kiVault = plNetClientMgr::GetInstance()->GetPlayerKI();
    fKIFolder = kiVault->FindFolder( plKIFolder::MatchesName( "INBOX" ) );

    // Populate list-of-lists
    pfGUIListBoxMod *listsList = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_ListOfLists ) );  // Temp tag ID
    listsList->LockList();
    listsList->ClearAllElements();

    plKIFolderVec * folders = kiVault->GetFolders();
    for( plKIFolderVec::const_iterator folderIter = folders->begin(); folderIter != folders->end(); ++folderIter )
    {
        UInt16 id = listsList->AddElement( new pfKIListItemElement( *folderIter ) );
        if( *folderIter == fKIFolder )
            sel = id;
    }
    listsList->SetSelection( sel );
    listsList->UnlockList();
}

void    plKIMainProc::UpdateTextList( void )
{
    pfGUIControlMod *addBtn = fDialog->GetControlFromTag( kKIAddButton );
    int     i;
    
    
    if( fKIFolder == nil )
    {
        fOther->ClearAllElements();
        UpdateTextPreview();
        addBtn->SetVisible( false );
        return;
    }

    addBtn->SetVisible( true );
    plKIElementVec *items = fKIFolder->GetElements();

    fOther->LockList();
    fOther->ClearAllElements();

    for( i = 0; i < items->size(); i++ )
    {
        plKITextNoteElement *note = plKITextNoteElement::ConvertNoRef( (*items)[ i ] );
        hsAssert( note != nil, "What the *#($& is a non-text item doing in the text item list??" );
        fOther->AddElement( new pfKITextItemElement( note ) );
    }
    fOther->UnlockList();

    UpdateTextPreview();
}

void    plKIMainProc::OnInit( void )
{
}

void    plKIMainProc::SetCurrentAvatar( plKey avKey )
{
    static char str[ 512 ];


    pfGUITextBoxMod *text = pfGUITextBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_CurrPlayerText ) );
    const char *avName = plNetClientMgr::GetInstance()->GetPlayerName( avKey );
    if( text != nil )
    {
        if( avName == nil )
            text->SetText( "Selected:" );
        else
        {
            sprintf( str, "%s", avName );
            text->SetText( str );
        }
    }
}

//// MiniKI Proc /////////////////////////////////////////////////////////////

#define kHackFlagLocalMsg   0x800
class pfKIChatElement : public pfGUIListElement
{
    protected:
        
        plKITextNoteElement *fDataItem;
        hsColorRGBA         fTextColor;
        char                *fString;
        UInt32              fFlags;

    public:

        pfKIChatElement( plKITextNoteElement *source ) : pfGUIListElement( 0 )
        {
            fDataItem = source;
            fFlags = 0;
            if( strcmp( source->GetTitle(), plNetClientMgr::GetInstance()->GetPlayerName() ) == 0 )
                fFlags |= kHackFlagLocalMsg;

            fString = new char[ strlen( source->GetTitle() ) + strlen( source->GetText() ) + 3 ];
            sprintf( fString, "%s: %s", source->GetTitle(), source->GetText() );
        }

        pfKIChatElement( const char *user, const char *msg, UInt32 flags ) : pfGUIListElement( 0 )
        {
            fDataItem = nil;
            fFlags = flags;

            if( strcmp( user, plNetClientMgr::GetInstance()->GetPlayerName() ) == 0 )
                fFlags |= kHackFlagLocalMsg;

            if( fFlags & pfKIMsg::kAdminMsg )
                fTextColor.Set( 0, 0, 1, 1 );
            else if( fFlags & pfKIMsg::kPrivateMsg )
                fTextColor.Set( 1, 0, 0, 1 );
            else
                fTextColor.Set( 1, 1, 1, 1 );

            if( user == nil )
                user = " ";
            if( msg == nil )
                msg = " ";
            fString = new char[ strlen( user ) + strlen( msg ) + 3 ];
            sprintf( fString, "%s: %s", user, msg );
        }

        virtual ~pfKIChatElement() { delete [] fString; }

        virtual void    Draw( plDynamicTextMap *textGen, UInt16 x, UInt16 y, UInt16 maxWidth, UInt16 maxHeight )
        {
            if( fFlags & kHackFlagLocalMsg )
                fTextColor.a = fColors->fSelForeColor.a;
            else
                fTextColor.a = fColors->fForeColor.a;

            textGen->SetTextColor( fTextColor, fColors->fTransparent && fColors->fBackColor.a == 0.f );

            textGen->DrawWrappedString( x + 2, y, fString, maxWidth - 4, maxHeight );
        }

        virtual void    GetSize( plDynamicTextMap *textGen, UInt16 *width, UInt16 *height )
        {
            *width = textGen->GetVisibleWidth() - 4;
            textGen->CalcWrappedStringSize( fString, width, height );
            if( height != nil )
                *height += 0;
            *width += 4;
        }

        virtual int     CompareTo( pfGUIListElement *rightSide )
        {
            return -2;
        }

        plKITextNoteElement *GetSource( void ) { return fDataItem; }
};

class pfKIListPlayerItem : public pfGUIListText
{
    protected:
        
        plKey fPlayerKey;

    public:
        pfKIListPlayerItem( plKey key, hsBool inRange = false ) : pfGUIListText(), fPlayerKey( key ) 
        {   
            static char str[ 256 ];


            if( key == nil )
                SetText( "<Everyone>" );
            else
            {
                const char *name = plNetClientMgr::GetInstance()->GetPlayerName( key );
                if( inRange )
                {
                    sprintf( str, ">%s<", name != nil ? name : key->GetName() );
                    SetText( str );
                }
                else
                    SetText( name != nil ? name : key->GetName() );
            }
            ISetJustify( true );
        }
        plKey   GetPlayerKey( void ) { return fPlayerKey; }
};

class plKIMiniProc : public pfGUIDialogProc
{
    protected:
        
        pfGUIDialogMod  *fMainDlg;
        pfGUIListBoxMod *fChatList;
        plKIFolder      *fChatVaultFolder;
        hsBool          fChatting, fInited;
        float           fFadeOutTimer, fFadeOutDelay;
        float           fForeAlpha, fSelForeAlpha;

        hsBool          fLocalClientIsAdmin;

    public:

        plKIMiniProc( pfGUIDialogMod *main ) 
        {
            fMainDlg = main; 
            fChatting = false; 
            fFadeOutDelay = 20.f;
            fFadeOutTimer = 0.f;
            fInited = false;
            fLocalClientIsAdmin = false;
        }

        void    SetLocalClientAsAdmin( hsBool yes ) { fLocalClientIsAdmin = yes; }

        virtual void    OnInit( void )
        {
            fForeAlpha = fDialog->GetColorScheme()->fForeColor.a;
            fSelForeAlpha = fDialog->GetColorScheme()->fSelForeColor.a;
            fInited = true;
        }

        virtual void    DoSomething( pfGUIControlMod *ctrl )
        {
            if( ctrl->GetTagID() == kKIYesBtn )
            {
                fDialog->Hide();
                fMainDlg->Show();
            }
            else if( ctrl->GetTagID() == kKITestControl2 )  // I.E. chat box
            {
                pfGUIEditBoxMod *edit = pfGUIEditBoxMod::ConvertNoRef( ctrl );
                pfGUIControlMod *label = fDialog->GetControlFromTag( kKIStaticText );

                if( !edit->WasEscaped() )
                    SendChatItem( edit->GetBuffer() );

                EnterChatMode( false );
            }
            else if( ctrl->GetTagID() == kKITempID_PlayerList )
            {
                pfGUIListBoxMod *list = pfGUIListBoxMod::ConvertNoRef( ctrl );
                if( list != nil )   // it BETTER be
                {
                    if( list->GetSelection() == -1 && list->GetNumElements() > 0 )
                        list->SetSelection( 0 );
                }
            }
        }

        virtual void    OnShow( void )
        {
            // Get our chat list
            fChatList = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITestEditBox ) );

            EnterChatMode( false );

            // Start with the chat not showing
            fFadeOutTimer = 0.f;
            IncFadeOutTimer( fFadeOutDelay + 2.f );

            GrabChatList();
            UpdateChatList();
            RefreshUserList();
        }

        virtual void    OnHide( void )
        {
            plBlackBarProc::ClearKIButtons();
        }

        virtual void    OnCtrlFocusChange( pfGUIControlMod *oldCtrl, pfGUIControlMod *newCtrl )
        {
            if( oldCtrl != nil && oldCtrl->GetTagID() == kKITestControl2 && 
                ( newCtrl == nil || newCtrl->GetTagID() != kKITempID_ChatModeBtn ) )
            {
                // We were chatting and lost focus, so hide the chatting controls
                EnterChatMode( false );
            }
        }

        void    SetFadeOutDelay( hsScalar secs ) { fFadeOutDelay = secs; }

        void    EnterChatMode( hsBool enteringNotLeaving )
        {
            pfGUIEditBoxMod *edit = pfGUIEditBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITestControl2 ) );
            pfGUIControlMod *label = fDialog->GetControlFromTag( kKIStaticText );

            if( enteringNotLeaving )
            {
                if( !fDialog->IsVisible() )
                    fDialog->Show();

                edit->ClearBuffer();
                edit->SetVisible( true );
                label->SetVisible( true );
                fDialog->SetFocus( edit );
                IncFadeOutTimer( -fFadeOutTimer );
            }
            else
            {
                edit->SetVisible( false );
                label->SetVisible( false );
            }
            fChatting = enteringNotLeaving;
        }

        hsBool  IsChatting( void ) const { return fChatting; }

        void    GrabChatList( void )
        {
/*          plKI *kiVault = plNetKI::GetInstance();
            if( kiVault != nil )
                fChatVaultList = kiVault->FindList( &plKIList::MatchesListDefID( plKITextChatMsgsIRcvdList::Index() ) );
            else
                fChatVaultList = nil;
*/      }

        plKIFolder  *GetChatFolder( void ) const { return fChatVaultFolder; }

        void    RefreshUserList( void )
        {
            int     i;


            if( !fDialog->IsVisible() )
                return;

            pfGUIListBoxMod *userList = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_PlayerList ) );
            if( userList != nil )
            {
                plKey   currKey = nil;

                if( userList->GetNumElements() > 0 && userList->GetSelection() != -1 )
                    currKey = ( (pfKIListPlayerItem *)userList->GetElement( userList->GetSelection() ) )->GetPlayerKey();

                userList->LockList();
                userList->ClearAllElements();

                if( plNetClientMgr::GetInstance() != nil )
                {
                    userList->AddElement( new pfKIListPlayerItem( nil ) ); 

                    plNetTransportMember **members = nil;
                    plNetClientMgr::GetInstance()->TransportMgr().GetMemberListDistSorted( members );
                    if( members != nil )
                    {
                        for( i = 0; i < plNetClientMgr::GetInstance()->TransportMgr().GetNumMembers(); i++ )
                        {
                            plNetTransportMember *mbr = members[ i ];

                            if( mbr != nil && mbr->GetAvatarKey() != nil )
                            {
                                hsBool  inRange = ( i < plNetListenList::kMaxListenListSize || plNetListenList::kMaxListenListSize==-1) && 
                                                  ( mbr->GetDistSq() < plNetListenList::kMaxListenDistSq );

                                userList->AddElement( new pfKIListPlayerItem( mbr->GetAvatarKey(), inRange ) ); 
                            }
                        }

                        delete [] members;
                    }
                }

                if( currKey == nil )
                {
                    if( userList->GetNumElements() > 0 )
                        userList->SetSelection( 0 );
                }
                else
                {
                    for( i = 0; i < userList->GetNumElements(); i++ )
                    {
                        if( ( (pfKIListPlayerItem *)userList->GetElement( i ) )->GetPlayerKey() == currKey )
                        {
                            userList->SetSelection( i );
                            break;
                        }
                    }
                    if( i == userList->GetNumElements() && userList->GetNumElements() > 0 )
                        userList->SetSelection( 0 );
                }

                userList->UnlockList();
            }
        }

        virtual void    HandleExtendedEvent( pfGUIControlMod *ctrl, UInt32 event )
        {
            // The only controls that will trigger a HandleExtendedEvent() are the ones that we want
            // to have force the text to show
            if( pfGUIListBoxMod::ConvertNoRef( ctrl ) == nil || event != pfGUIListBoxMod::kListCleared )
                IncFadeOutTimer( -fFadeOutTimer );
        }

        void    IncFadeOutTimer( float delSeconds )
        {
            if( !fInited )
                return;

            if( fDialog->GetFocus() && fDialog->GetFocus()->GetTagID() == kKITestControl2 )
                delSeconds = -fFadeOutTimer;

            bool        didntChange = ( fFadeOutTimer <= fFadeOutDelay && fFadeOutTimer + delSeconds <= fFadeOutDelay )
                                    || ( fFadeOutTimer > fFadeOutDelay + 1.f && delSeconds >= 0.f );

            pfGUIColorScheme *colors = fDialog->GetColorScheme();

            fFadeOutTimer += delSeconds;
            if( fFadeOutTimer > fFadeOutDelay )
            {
                if( fFadeOutTimer > fFadeOutDelay + 1.f )
                {
                    colors->fForeColor.a = 0.f;
                    colors->fSelForeColor.a = 0.f;
                }
                else
                {
                    colors->fForeColor.a = fForeAlpha * ( fFadeOutDelay + 1.f - fFadeOutTimer );
                    colors->fSelForeColor.a = fSelForeAlpha * ( fFadeOutDelay + 1.f - fFadeOutTimer );
                }
            }
            else
            {
                colors->fForeColor.a = fForeAlpha;
                colors->fSelForeColor.a = fSelForeAlpha;
            }

            if( !didntChange )
                fDialog->RefreshAllControls();
        }

        void    UpdateChatList( void )
        {
            if( !fDialog->IsVisible() )
                return;

            fChatList->LockList();  // Makes updates faster
//          fChatList->ClearAllElements();

/*          if( fChatVaultList != nil )
            {
                int i;
                plKIElementVec *items = fChatVaultList->GetElements();

                for( i = 0; i < items->size(); i++ )
                {
                    plKITextNoteElement *note = plKITextNoteElement::ConvertNoRef( (*items)[ i ] );
                    hsAssert( note != nil, "What the *#($& is a non-text item doing in the text item list??" );
                    fChatList->AddElement( new pfKIChatElement( note ) );
                }
            }

*/
            fChatList->UnlockList();
            fChatList->ScrollToBegin();
        }

/*      void    ReceivedChatItem( plKIElement *element )
        {
            if( fChatList == nil )
                return;

            plKITextNoteElement *note = plKITextNoteElement::ConvertNoRef( element );
            if( note != nil )
                fChatList->AddElement( new pfKIChatElement( note ) );
            fChatList->ScrollToBegin();
        }
*/
        void    ReceivedChatItem( const char *user, const char *msg, UInt32 flags )
        {
            if( fChatList == nil )
                return;

            if( !fDialog->IsVisible() )
                fDialog->Show();

            fChatList->AddElement( new pfKIChatElement( user, msg, flags ) );
            if( fChatList->GetNumElements() > kMaxNumChatItems )
                fChatList->RemoveElement( 0 );

            fChatList->ScrollToBegin();
            IncFadeOutTimer( -fFadeOutTimer );
        }

        void    SendChatItem( const char *text )
        {
//          if( fChatVaultList != nil )
            {
                const char *userName = plNetClientMgr::GetInstance()->GetPlayerName();
/*              plKITextNoteElement *item = new plKITextNoteElement();
                item->SetTitle( plNetClientMgr::GetInstance()->GetPlayerName() );
                item->SetText( text );
                fChatVaultList->AddElement( item );
*/

                pfGUIListBoxMod     *userList = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_PlayerList ) );
                pfKIListPlayerItem  *currPlayer = nil;
                int                 mbrIndex = -1;
                UInt32              msgFlags;

                if( userList != nil && userList->GetNumElements() > 0 && userList->GetSelection() != -1 )
                    currPlayer = (pfKIListPlayerItem *)userList->GetElement( userList->GetSelection() );

                if( currPlayer != nil && currPlayer->GetPlayerKey() != nil )
                    mbrIndex = plNetClientMgr::GetInstance()->TransportMgr().FindMember( currPlayer->GetPlayerKey() );

                pfKIMsg *msg = new pfKIMsg( pfKIMsg::kHACKChatMsg );
                msg->SetString( text );
                msg->SetUser( userName );
                if( fLocalClientIsAdmin )
                    msg->SetFlags( pfKIMsg::kAdminMsg );

                if( mbrIndex != -1 )
                {
                    // Send to one player
                    msg->SetFlags( msg->GetFlags() | pfKIMsg::kPrivateMsg );

                    msg->SetTimeStamp( 0 );     // Remove timestamp

                    // write message (and label) to ram stream
                    hsRAMStream stream;
                    hsgResMgr::ResMgr()->WriteCreatable( &stream, msg );

                    // put stream in net msg wrapper
                    plNetMsgGameMessageDirected netMsgWrap;
                    netMsgWrap.StreamInfo()->CopyStream( &stream );
                    netMsgWrap.StreamInfo()->SetStreamType( msg->ClassIndex() );                    // type of game msg
                    netMsgWrap.SetTimeOffset( 0 );
                    netMsgWrap.SetClassName( msg->ClassName() );
                    netMsgWrap.SetSenderPlayerID( plNetClientMgr::GetInstance()->GetPlayerID() );       

                    // set directed client receiver
                    netMsgWrap.Receivers()->AddReceiverPlayerID( plNetClientMgr::GetInstance()->TransportMgr().GetMember( mbrIndex )->GetPlayerID() );

                    // send
                    msgFlags = msg->GetFlags();
                    plNetClientMgr::GetInstance()->SendMsg( &netMsgWrap, 0 );
                }
                else
                {
                    // Broadcast to all
                    msg->SetBCastFlag(plMessage::kNetPropagate | plMessage::kNetForce);
                    msg->SetBCastFlag(plMessage::kLocalPropagate, 0);
                    msgFlags = msg->GetFlags();
                    plgDispatch::MsgSend( msg );
                }
                
                fChatList->AddElement( new pfKIChatElement( userName, text, msgFlags ) );
                fChatList->ScrollToBegin();
                IncFadeOutTimer( -fFadeOutTimer );
                if( fChatList->GetNumElements() > kMaxNumChatItems )
                    fChatList->RemoveElement( 0 );
            }
        }

        void    SetCurrentAvatar( plKey avKey )
        {
            static char str[ 512 ];
            int         i;


            pfGUITextBoxMod *text = pfGUITextBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_CurrPlayerText ) );
            const char *avName = plNetClientMgr::GetInstance()->GetPlayerName( avKey );
            if( text != nil )
            {
                if( avKey == nil || avName == nil )
                    text->SetText( "" );
                else
                {
                    sprintf( str, "%s", avName );
                    text->SetText( str );
                }
                IncFadeOutTimer( -fFadeOutTimer );
            }

            pfGUIListBoxMod *userList = pfGUIListBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKITempID_PlayerList ) );
            if( userList != nil && userList->GetNumElements() > 0 )
            {
                if( avKey == nil )
                    userList->SetSelection( 0 );
                else
                {
                    for( i = 0; i < userList->GetNumElements(); i++ )
                    {
                        pfKIListPlayerItem *el = (pfKIListPlayerItem *)userList->GetElement( i );
                        if( el != nil && el->GetPlayerKey() == avKey )
                        {
                            userList->SetSelection( i );
                            break;
                        }
                    }
                }
            }
        }
};

//// Callback From the KI Vault //////////////////////////////////////////////

class pfKITextVaultCallback : public plKICallback
{
    protected:

        plKIMainProc    *fTextDlgProc;
        plKIMiniProc    *fMiniProc;

    public:

        pfKITextVaultCallback( plKIMainProc *proc, plKIMiniProc *miniProc )
        {
            fTextDlgProc = proc;
            fMiniProc = miniProc;
        }

        void KIFolderAdded( plKIFolder *folder )
        {
        }

        void KIFolderRemoved()
        {
        }

        void KIElementAddedToFolder( plKIElement *elem, plKIFolder *folder )
        {
            fTextDlgProc->UpdateTextList();
//          if( folder == fMiniProc->GetChatFolder() )
//              fMiniProc->ReceivedChatItem( elem );
        }

        void KIElementChanged( plKIElement *elem )
        {
            fTextDlgProc->UpdateTextList();
        }

        void KIElementSeen( plKIElement *elem )
        {
        }

        void KIElementRemovedFromFolder( plKIFolder *folder )
        {
            fTextDlgProc->UpdateTextList();
//          if( folder == fMiniProc->GetChatFolder() )
//              fMiniProc->UpdateChatList();
        }

        void KIAllChanged( void )
        {
            // Gotta re-grab the list
            fTextDlgProc->GrabVaultFolder();
            fTextDlgProc->UpdateTextList();
            fMiniProc->GrabChatList();
            fMiniProc->UpdateChatList();
        }

        void KISelectedBuddyChanged( plKIPlayerElement *elem )
        {
        }

        void KISendModeChanged( int newMode )
        {
        }
};

//// Constructor & Destructor ////////////////////////////////////////////////

pfKI::pfKI()
{
//  fKIVaultCallback = nil;
//  fInstance = this;
}

pfKI::~pfKI()
{
    pfGameGUIMgr::GetInstance()->UnloadDialog( "KIMain" );
    pfGameGUIMgr::GetInstance()->UnloadDialog( "KIEntry" );
    pfGameGUIMgr::GetInstance()->UnloadDialog( "KIYesNo" );
    pfGameGUIMgr::GetInstance()->UnloadDialog( "KIMini" );
    pfGameGUIMgr::GetInstance()->UnloadDialog( "KIBlackBar" );

//  delete fKIVaultCallback;
//  fInstance = nil;
}

void    pfKI::Init( void )
{
#ifdef USE_INTERNAL_PLAYERBOOK
    IInitPlayerBook();
#endif  // USE_INTERNAL_PLAYERBOOK

    pfGameGUIMgr    *mgr = pfGameGUIMgr::GetInstance();


    // Load KI dat files
    mgr->LoadDialog( "KIBlackBar" );
#ifdef USE_INTERNAL_KI
    mgr->LoadDialog( "KIMini" );
    mgr->LoadDialog( "KIMain" );
    mgr->LoadDialog( "KIEntry" );
    mgr->LoadDialog( "KIYesNo" );


    pfGUIDialogMod *yesNoDlg = mgr->GetDialogFromTag( kKIYesNoDlg );
    pfGUIDialogMod *mainDlg = mgr->GetDialogFromTag( kKIMainDialog );
    pfGUIDialogMod *arDlg = mgr->GetDialogFromTag( kKIEntryDlg );
    pfGUIDialogMod *miniDlg = mgr->GetDialogFromTag( kKIMiniDialog );
    pfGUIDialogMod *blackBarDlg = mgr->GetDialogFromTag( kKITempID_BlackBarDlg );

    if( yesNoDlg == nil || mainDlg == nil || arDlg == nil || miniDlg == nil )
    {
        hsStatusMessage( "==== WARNING: KI Interface not inited (GUI data missing) ====" );
        return;
    }
    if( mainDlg->GetVersion() != kDesiredKIVersion )
    {
        char    str[ 512 ];
        sprintf( str, "Incorrect KI dataset version. KI will not be loaded. (Looking for version %d, found version %d)",
                    kDesiredKIVersion, mainDlg->GetVersion() );
        hsMessageBox( str, "Error", hsMessageBoxNormal );
        return;
    }

    // Init our yes/no dialog
    fYesNoProc = new plKIYesNoBox;
    yesNoDlg->SetHandler( fYesNoProc );

    // Init our main dialog
    pfGUIListBoxMod *listOfLists = pfGUIListBoxMod::ConvertNoRef( mainDlg->GetControlFromTag( kKITempID_ListOfLists ) );
    pfGUIListBoxMod *list = pfGUIListBoxMod::ConvertNoRef( mainDlg->GetControlFromTag( kKITestEditBox ) );
    pfGUIControlMod *editBtn = mainDlg->GetControlFromTag( kKIEditButton );
    pfGUIControlMod *removeBtn = mainDlg->GetControlFromTag( kKIRemoveButton );
    pfGUIRadioGroupCtrl *destRadio = pfGUIRadioGroupCtrl::ConvertNoRef( mainDlg->GetControlFromTag( kKITempID_MsgDestRadio ) );
    
    fMainProc = new plKIMainProc( list, fYesNoProc, miniDlg );
    mainDlg->SetHandler( fMainProc );
    destRadio->SetValue( 0 );
    

    // Init our add/remove text item dialog
    fAddEditProc = new plKIAddEditBox( pfGUIListBoxMod::ConvertNoRef( list ), mainDlg, fMainProc );
    arDlg->SetHandler( fAddEditProc );

    fMainProc->fAddRemoveHandler = fAddEditProc;

    // Make us a proc for our mini dialog's maximize button
    fMiniProc = new plKIMiniProc( mainDlg );
    miniDlg->SetHandler( fMiniProc );

    // Set the callback for the ki vault thingy
    plKI *kiVault = plNetClientMgr::GetInstance()->GetPlayerKI();
    fKIVaultCallback = new pfKITextVaultCallback( fMainProc, fMiniProc );
    kiVault->AddCallback( fKIVaultCallback );

    // Finally, show our KI main dialog
    if( blackBarDlg != nil )
    {
        blackBarDlg->SetHandler( new plBlackBarProc( miniDlg, mainDlg, blackBarDlg->GetHandler() ) );
        blackBarDlg->Show();
    }
    else
        miniDlg->Show();

    // Register for KI messages
    plgDispatch::Dispatch()->RegisterForExactType( pfKIMsg::Index(), GetKey() );
    plgDispatch::Dispatch()->RegisterForExactType( plRemoteAvatarInfoMsg::Index(), GetKey() );
    plgDispatch::Dispatch()->RegisterForExactType( plMemberUpdateMsg::Index(), GetKey() );
    plgDispatch::Dispatch()->RegisterForExactType( plTimeMsg::Index(), GetKey() );
#endif // USE_INTERNAL_KI

}

hsBool  pfKI::MsgReceive( plMessage *msg )
{
    pfKIMsg *kiMsg = pfKIMsg::ConvertNoRef( msg );
    if( kiMsg != nil )
    {
        if ( fMiniProc != nil )
        {
            switch ( kiMsg->GetCommand() )
            {
                case pfKIMsg::kHACKChatMsg:
                    fMiniProc->ReceivedChatItem( kiMsg->GetUser(), kiMsg->GetString(), kiMsg->GetFlags() );
                    break;

                case pfKIMsg::kEnterChatMode:
                    fMiniProc->EnterChatMode( !fMiniProc->IsChatting() );
                    break;

                case pfKIMsg::kSetChatFadeDelay:
                    fMiniProc->SetFadeOutDelay( kiMsg->GetDelay() );
                    break;

                case pfKIMsg::kSetTextChatAdminMode:
                    fMiniProc->SetLocalClientAsAdmin( kiMsg->GetFlags() & pfKIMsg::kAdminMsg ? true : false );
                    break;
            }
        }

        return true;
    }

    plRemoteAvatarInfoMsg *avInfo = plRemoteAvatarInfoMsg::ConvertNoRef( msg );
    if( avInfo != nil )
    {
        if( fMainProc != nil )
            fMainProc->SetCurrentAvatar( avInfo->GetAvatarKey() );
        if( fMiniProc != nil )
            fMiniProc->SetCurrentAvatar( avInfo->GetAvatarKey() );
        return true;
    }

    plMemberUpdateMsg *userUpdateMsg = plMemberUpdateMsg::ConvertNoRef( msg );
    if( userUpdateMsg != nil )
    {
        if( fMiniProc != nil )
            fMiniProc->RefreshUserList();
        return true;
    }

    plTimeMsg   *time = plTimeMsg::ConvertNoRef( msg );
    if( time != nil )
    {
        if( fMiniProc != nil )
            fMiniProc->IncFadeOutTimer( time->DelSeconds() );
        return true;
    }

    return hsKeyedObject::MsgReceive( msg );
}


//////////////////////////////////////////////////////////////////////////////
//// Player Book Stuff ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#include "../plNetCommon/plNetAvatarVault.h"
#include "../pfGameGUIMgr/pfGUICheckBoxCtrl.h"
#include "../plMessage/plLinkToAgeMsg.h"
#include "../pfGameGUIMgr/pfGUIDynDisplayCtrl.h"
#include "../plGImage/plDynamicTextMap.h"
#include "../plJPEG/plJPEG.h"

//// plPlayerBookProc ////////////////////////////////////////////////////////

class plPlayerBookProc : public pfGUIDialogProc
{
    protected:

        pfGUIRadioGroupCtrl *fRadio;
        pfGUITextBoxMod     *fDescTextBox;

    public:
        virtual void    DoSomething( pfGUIControlMod *ctrl )
        {
            if( ctrl->GetTagID() == kPBSaveSlotRadio )
            {
                if( plNetClientMgr::GetInstance()->IsEnabled() )
                {
                    plPlayerLinkPoint   &linkPt = plNetClientMgr::GetInstance()->GetLinkPoint( fRadio->GetValue() );
                    fDescTextBox->SetText( linkPt.GetDescription() );
                }
            }
            else if( ctrl->GetTagID() == kPBLinkToBtn )
            {
                if( plNetClientMgr::GetInstance()->IsEnabled() )
                {
                    plLinkBackToAgeMsg *msg = new plLinkBackToAgeMsg;
                    msg->AddReceiver( plNetClientMgr::GetInstance()->GetKey() );
                    msg->fSavedLinkPointNum = fRadio->GetValue();
                    plgDispatch::MsgSend( msg );
                }
                else
                {
                }
            }
            else if( ctrl->GetTagID() == kPBSaveLinkBtn )
            {
                if( plNetClientMgr::GetInstance()->IsEnabled() )
                {
                    plNetClientMgr::GetInstance()->SaveLinkPoint( fRadio->GetValue(), "Description" );
                }
                else
                {
                }
            }
        }

        virtual void    OnShow( void )
        {
            fDescTextBox = pfGUITextBoxMod::ConvertNoRef( fDialog->GetControlFromTag( kKIStaticText ) );
            fDescTextBox->SetText( "" );

            fRadio = pfGUIRadioGroupCtrl::ConvertNoRef( fDialog->GetControlFromTag( kPBSaveSlotRadio ) );
            fRadio->SetValue( 0 );

            pfGUIDynDisplayCtrl *disp = pfGUIDynDisplayCtrl::ConvertNoRef( fDialog->GetControlFromTag( kPBSaveSlotPrev1 ) );
            plDynamicTextMap *map = disp->GetMap( 0 );
            map->ClearToColor( hsColorRGBA().Set( 1, 0, 0, 1 ) );

            plMipmap *img = plJPEG::Instance().ReadFromFile( "e:\\plasma20\\data\\localdata\\testfile.jpg" );
            if( img == nil )
                return;

            map->DrawImage( 4, 4, img );
        }
};

//// IInitPlayerBook /////////////////////////////////////////////////////////

void    pfKI::IInitPlayerBook( void )
{
    pfGameGUIMgr    *mgr = pfGameGUIMgr::GetInstance();

    mgr->LoadDialog( "PBDialog" );

    pfGUIDialogMod *pbDialog = mgr->GetDialogFromTag( kPlayerBook );
    if( pbDialog == nil )
        return;

    fPBProc = new plPlayerBookProc();
    pbDialog->SetHandler( fPBProc );
    pbDialog->Show();
}