/*==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/>.

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();
}