/*==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==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  pfGUICtrlGenerator Definitions                                          //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "pfGUICtrlGenerator.h"
#include "pfGameGUIMgr.h"
#include "pfGUIControlMod.h"
#include "pfGUIDialogMod.h"
#include "pfGUIButtonMod.h"
#include "pfGUIDragBarCtrl.h"
#include "pfGUIControlHandlers.h"
#include "pfGUIMenuItem.h"

#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayer.h"
#include "plGImage/plMipmap.h"
#include "pnKeyedObject/plFixedKey.h"
#include "plDrawable/plDrawableSpans.h"
#include "plDrawable/plDrawableGenerator.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plDrawInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnMessage/plIntRefMsg.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plNodeRefMsg.h"
#include "plPipeline/plTextGenerator.h"
#include "plScene/plPostEffectMod.h"
#include "plScene/plSceneNode.h"
#include "pnMessage/plClientMsg.h"
#include "plMessage/plLayRefMsg.h"
#include "pnMessage/plAttachMsg.h"

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


//// Constructor/Destructor //////////////////////////////////////////////////

pfGUICtrlGenerator::pfGUICtrlGenerator()
{
    strcpy( fFontFace, "Arial" );
    fFontSize = 18;
}

pfGUICtrlGenerator::~pfGUICtrlGenerator()
{
    Shutdown();
}

void    pfGUICtrlGenerator::Shutdown( void )
{
    int i;
    

    // Destroy our scene nodes and dialogs
    for( i = 0; i < fDynDlgNodes.GetCount(); i++ )
    {
        pfGameGUIMgr::GetInstance()->UnloadDialog( fDynDialogs[ i ] );
        fDynDlgNodes[ i ]->GetKey()->UnRefObject();
    }
    fDynDlgNodes.Reset();
    fDynDialogs.Reset();
    
    for( i = 0; i < fTextGens.GetCount(); i++ )
        delete fTextGens[ i ];
    fTextGens.Reset();

}

//// Instance ////////////////////////////////////////////////////////////////

pfGUICtrlGenerator  &pfGUICtrlGenerator::Instance( void )
{
    static pfGUICtrlGenerator       myInstance;

    return myInstance;
}

//// IGetNextKeyName /////////////////////////////////////////////////////////

void    pfGUICtrlGenerator::IGetNextKeyName( char *name, const char *prefix )
{
    static UInt32 keyCount = 0;


    sprintf( name, "%s%d", prefix, keyCount++ );
}

//// IAddKey /////////////////////////////////////////////////////////////////

plKey pfGUICtrlGenerator::IAddKey( hsKeyedObject *ko, const char *prefix )
{
    char    keyName[ 128 ];


    IGetNextKeyName( keyName, prefix );
    return hsgResMgr::ResMgr()->NewKey( keyName, ko, plLocation::kGlobalFixedLoc );
}

//// SetFont /////////////////////////////////////////////////////////////////

void    pfGUICtrlGenerator::SetFont( const char *face, UInt16 size )
{
    strcpy( fFontFace, face );
    fFontSize = size;
}

//// ICreateSolidMaterial ////////////////////////////////////////////////////
//  Creates a material with no texture, just color.

hsGMaterial *pfGUICtrlGenerator::ICreateSolidMaterial( hsColorRGBA &color )
{
    hsColorRGBA     black;


    // Create a material with a simple blank layer, fully ambient
    hsGMaterial *material = TRACKED_NEW hsGMaterial;
    IAddKey( material, "GUIMaterial" );

    plLayer *lay = material->MakeBaseLayer();
    black.Set( 0.f,0.f,0.f,1.f );

    lay->SetRuntimeColor( black );
    lay->SetPreshadeColor( black );
    lay->SetAmbientColor( color );

    return material;
}

//// ICreateTextMaterial /////////////////////////////////////////////////////
//  Creates a material with a texture that has a string centered on it.

hsGMaterial *pfGUICtrlGenerator::ICreateTextMaterial( const char *text, hsColorRGBA &bgColor, 
                                                     hsColorRGBA &textColor, float objWidth, float objHeight )
{
    UInt16          pixWidth, pixHeight, strWidth, strHeight;
    hsColorRGBA     black, white;


    // Guess at some pixel width and heights we want. We're guessing b/c we want it to look reasonably
    // good on the screen, but we don't know exactly how big is big, so we guess
    pixWidth = (UInt16)(objWidth * 64.f);
    pixHeight = (UInt16)(objHeight * 64.f);

    // Create blank mipmap
    plMipmap *bitmap = TRACKED_NEW plMipmap( 1, 1, plMipmap::kRGB32Config, 1 );
    IAddKey( bitmap, "GUIMipmap" );

    // Create textGen to write string with
    plTextGenerator *textGen = TRACKED_NEW plTextGenerator( bitmap, pixWidth, pixHeight );
    textGen->SetFont( fFontFace, (UInt16)fFontSize );
    textGen->ClearToColor( bgColor );
    textGen->SetTextColor( textColor );
    strWidth = textGen->CalcStringWidth( text, &strHeight );
    textGen->DrawString( ( pixWidth - strWidth ) >> 1, ( pixHeight - strHeight ) >> 1, text );
    textGen->FlushToHost();
    fTextGens.Append( textGen );

    // Create a material with a simple blank layer, fully ambient
    hsGMaterial *material = TRACKED_NEW hsGMaterial;
    IAddKey( material, "GUIMaterial" );

    plLayer *lay = material->MakeBaseLayer();
    white.Set( 1.f,1.f,1.f,1.f );
    black.Set( 0.f,0.f,0.f,1.f );

    lay->SetRuntimeColor( black );
    lay->SetPreshadeColor( black );
    lay->SetAmbientColor( white );

    hsgResMgr::ResMgr()->AddViaNotify( bitmap->GetKey(), TRACKED_NEW plLayRefMsg( lay->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture ), plRefFlags::kActiveRef );
//  lay->SetTexture( bitmap );
    lay->SetTransform( textGen->GetLayerTransform() );

    return material;
}

//// GenerateDialog //////////////////////////////////////////////////////////

void    pfGUICtrlGenerator::GenerateDialog( const char *name )
{
    IGenerateDialog( name, 20.f, false );
}

//// IGenSceneObject /////////////////////////////////////////////////////////

plSceneObject   *pfGUICtrlGenerator::IGenSceneObject( pfGUIDialogMod *dlg, plDrawable *myDraw, plSceneObject *parent,
                                                        hsMatrix44 *l2w, hsMatrix44 *w2l )
{
    plKey snKey = ( dlg != nil ) ? ( dlg->GetTarget() != nil ? dlg->GetTarget()->GetSceneNode() : nil ) : nil;
    if( snKey == nil )
        snKey = fDynDlgNodes.Peek()->GetKey();

    hsgResMgr::ResMgr()->SendRef( myDraw->GetKey(), TRACKED_NEW plNodeRefMsg( snKey, plRefMsg::kOnCreate, 0, plNodeRefMsg::kDrawable ), plRefFlags::kActiveRef );       

    plDrawInterface *newDI = TRACKED_NEW plDrawInterface;
    IAddKey( newDI, "GUIDrawIFace" );

    plSceneObject   *newObj = TRACKED_NEW plSceneObject;
    IAddKey( newObj, "GUISceneObject" );

    plCoordinateInterface *newCI = TRACKED_NEW plCoordinateInterface;
    IAddKey( newCI, "GUICoordIFace" );

    hsgResMgr::ResMgr()->SendRef( newCI->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface ), plRefFlags::kActiveRef );
    hsgResMgr::ResMgr()->SendRef( newDI->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface ), plRefFlags::kActiveRef );
    hsgResMgr::ResMgr()->SendRef( myDraw->GetKey(), TRACKED_NEW plIntRefMsg( newDI->GetKey(), plRefMsg::kOnCreate, 0, plIntRefMsg::kDrawable ), plRefFlags::kActiveRef );

    if( parent == nil )
    {
        parent = ( fDynDragBars.GetCount() > 0 ) ? fDynDragBars.Peek() : nil;
        if( parent == nil )
            parent = dlg->GetTarget();
    }

    if( parent != nil )
//      hsgResMgr::ResMgr()->SendRef( newCI->GetKey(), TRACKED_NEW plIntRefMsg( parent->GetKey(), plRefMsg::kOnCreate, 0, plIntRefMsg::kChild ), plRefFlags::kActiveRef );
        hsgResMgr::ResMgr()->SendRef( newCI->GetKey(), TRACKED_NEW plAttachMsg( parent->GetKey(), nil, plRefMsg::kOnRequest ), plRefFlags::kActiveRef );
    
    newObj->SetSceneNode( snKey );

    if( l2w != nil )
    {
        newObj->SetTransform( *l2w, *w2l );
//      newCI->SetLocalToParent( *l2w, *w2l );
//      myDraw->SetTransform( -1, *l2w, *w2l );
    }

    return newObj;
}

//// GenerateRectButton //////////////////////////////////////////////////////

pfGUIButtonMod  *pfGUICtrlGenerator::GenerateRectButton( const char *title, float x, float y, float width, float height,
                                    const char *consoleCmd, hsColorRGBA &color, hsColorRGBA &textColor )
{
    hsGMaterial     *material;
    hsMatrix44      l2w, w2l;
    hsVector3       vec;
    pfGUIDialogMod  *dlgToAddTo = IGetDialog();


    // Get us a material
    material = ICreateTextMaterial( title, color, textColor, width * 20.f, height * 20.f );

    pfGUIButtonMod *but = CreateRectButton( dlgToAddTo, title, x, y, width, height, material );
    if( but != nil )
        but->SetHandler( TRACKED_NEW pfGUIConsoleCmdProc( consoleCmd ) );

    return but;
}

//// CreateRectButton ////////////////////////////////////////////////////////

pfGUIButtonMod  *pfGUICtrlGenerator::CreateRectButton( pfGUIDialogMod *parent, const char *title, float x, float y, float width, float height,
                                    hsGMaterial *material, hsBool asMenuItem )
{
    wchar_t *wTitle = hsStringToWString(title);
    pfGUIButtonMod *retVal = CreateRectButton(parent,wTitle,x,y,width,height,material,asMenuItem);
    delete [] wTitle;
    return retVal;
}

pfGUIButtonMod  *pfGUICtrlGenerator::CreateRectButton( pfGUIDialogMod *parent, const wchar_t *title, float x, float y, float width, float height,
                                    hsGMaterial *material, hsBool asMenuItem )
{
    plDrawableSpans *myDraw;
    hsMatrix44      l2w, w2l;
    hsVector3       vec;


    // Translate x and y from (0:1) to (-10:10)
    x = ( x - 0.5f ) * 20.f;
    y = ( y - 0.5f ) * 20.f;
    // Translate width and height from (0:1) to (-10:10)
    width *= 20.f;
    height *= 20.f;

    // Create drawable that is rectangular
    l2w.Reset();
    hsPoint3 corner( x, -y, -100 );
    hsVector3 xVec( width, 0, 0 ), yVec( 0, height, 0 ), zVec( 0, 0, 0.1f );

    myDraw = plDrawableGenerator::GeneratePlanarDrawable( corner, xVec, yVec, material, l2w );

    plSceneObject *newObj = IGenSceneObject( parent, myDraw );

    pfGUIButtonMod *newBtn = asMenuItem ? TRACKED_NEW pfGUIMenuItem : TRACKED_NEW pfGUIButtonMod;
    IAddKey( newBtn, "GUIButton" );
    hsgResMgr::ResMgr()->SendRef( newBtn->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );
    parent->AddControl( newBtn );
    hsgResMgr::ResMgr()->AddViaNotify( newBtn->GetKey(), TRACKED_NEW plGenRefMsg( parent->GetKey(), plRefMsg::kOnCreate, parent->GetNumControls() - 1, pfGUIDialogMod::kControlRef ), plRefFlags::kActiveRef );

    return newBtn;
}

//// GenerateSphereButton ////////////////////////////////////////////////////

pfGUIButtonMod  *pfGUICtrlGenerator::GenerateSphereButton( float x, float y, float radius,
                                                            const char *consoleCmd, hsColorRGBA &color )
{
    hsGMaterial     *material;
    plDrawableSpans *myDraw;
    hsMatrix44      l2w, w2l;
    hsVector3       vec;
    hsPoint3        pt( x, -y, -100.f );
    pfGUIDialogMod  *dlgToAddTo = IGetDialog();


    // Translate x and y from (0:1) to (-10:10)
    x = ( x - 0.5f ) * 20.f;
    y = ( y - 0.5f ) * 20.f;
    // Translate width and height from (0:1) to (-10:10)
    radius *= 20.f;

    // Get us a material
    material = ICreateSolidMaterial( color );

    // Create drawable that is rectangular
    l2w.Reset();
    // We bump up the quality since we're actually far closer to these things then the normal
    // world camera would put us
    myDraw = plDrawableGenerator::GenerateSphericalDrawable( pt, radius, material, l2w,
                                                            false, nil, nil, nil, 100.f );

    vec.Set( x, -y, 0 );
    l2w.MakeTranslateMat( &vec );
    l2w.GetInverse( &w2l );

    plSceneObject *newObj = IGenSceneObject( dlgToAddTo, myDraw );//, nil, &l2w, &w2l );

    pfGUIButtonMod *newBtn = TRACKED_NEW pfGUIButtonMod;
    IAddKey( newBtn, "GUIButton" );
    newBtn->SetHandler( TRACKED_NEW pfGUIConsoleCmdProc( consoleCmd ) );
    hsgResMgr::ResMgr()->AddViaNotify( newBtn->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );
    dlgToAddTo->AddControl( newBtn );

    return newBtn;
}

//// GenerateDragBar //////////////////////////////////////////////////////

pfGUIDragBarCtrl *pfGUICtrlGenerator::GenerateDragBar( float x, float y, float width, float height, hsColorRGBA &color )
{
    hsGMaterial     *material;
    plDrawableSpans *myDraw;
    hsMatrix44      l2w, w2l;
    hsVector3       vec;
    pfGUIDialogMod  *dlgToAddTo = IGetDialog();


    // Translate x and y from (0:1) to (-10:10)
    x = ( x - 0.5f ) * 20.f;
    y = ( y - 0.5f ) * 20.f;
    // Translate width and height from (0:1) to (-10:10)
    width *= 20.f;
    height *= 20.f;

    // Get us a material
    material = ICreateSolidMaterial( color );

    // Create drawable that is rectangular
    l2w.Reset();

    hsPoint3 corner( x, -y, -100 );//x - width / 2.f, -y - height / 2.f, -100 );
    hsVector3 xVec( width, 0, 0 ), yVec( 0, height, 0 ), zVec( 0, 0, 0.1f );

    myDraw = plDrawableGenerator::GenerateBoxDrawable( corner, xVec, yVec, zVec,/*width, height, 0.01f, */material, l2w );

    // Drag bars are special--everything else gets attached to them and they get attached to the dialog
    vec.Set( x, -y, -100 );
    l2w.MakeTranslateMat( &vec );
    l2w.GetInverse( &w2l );

    plSceneObject *newObj = IGenSceneObject( dlgToAddTo, myDraw, dlgToAddTo->GetTarget(), &l2w, &w2l );

    fDynDragBars[ fDynDragBars.GetCount() - 1 ] = newObj;

    pfGUIDragBarCtrl *newBtn = TRACKED_NEW pfGUIDragBarCtrl;
    IAddKey( newBtn, "GUIDragBar" );
    hsgResMgr::ResMgr()->AddViaNotify( newBtn->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );
    dlgToAddTo->AddControl( newBtn );

/*  vec.Set( -x, y, 100 );
    l2w.MakeTranslateMat( &vec );
    l2w.GetInverse( &w2l );

    plCoordinateInterface *ci = (plCoordinateInterface *)dlgToAddTo->GetTarget()->GetCoordinateInterface();
    ci->SetLocalToParent( l2w, w2l );
*/
    return newBtn;
}

//// IGetDialog //////////////////////////////////////////////////////////////

pfGUIDialogMod  *pfGUICtrlGenerator::IGetDialog( void )
{
    if( fDynDialogs.GetCount() == 0 )
        IGenerateDialog( "GUIBaseDynamicDlg", 20.f );

    hsAssert( fDynDialogs.GetCount() > 0, "Unable to get a dynamic dialog to add buttons to" );
    return fDynDialogs.Peek();
}

//// IGenerateDialog /////////////////////////////////////////////////////////

pfGUIDialogMod  *pfGUICtrlGenerator::IGenerateDialog( const char *name, float scrnWidth, hsBool show )
{
    float           fovX, fovY;
    plSceneNode     *node;
    pfGUIDialogMod  *dialog;


    // Create the rendermod
    plPostEffectMod *renderMod = TRACKED_NEW plPostEffectMod;
    IAddKey( renderMod, "GUIRenderMod" );

    renderMod->SetHither( 0.5f );
    renderMod->SetYon( 200.f );

    // fovX should be such that scrnWidth is the projected width at z=100
    fovX = atan( scrnWidth / ( 2.f * 100.f ) ) * 2.f;
    fovY = fovX;// * 3.f / 4.f;

    renderMod->SetFovX( fovX * 180.f / hsScalarPI );
    renderMod->SetFovY( fovY * 180.f / hsScalarPI );

    // Create the sceneNode to go with it
    node = TRACKED_NEW plSceneNode;
    IAddKey( node, "GUISceneNode" );
    node->GetKey()->RefObject();
    fDynDlgNodes.Append( node );
    fDynDragBars.Append( nil );

    hsgResMgr::ResMgr()->AddViaNotify( node->GetKey(), TRACKED_NEW plGenRefMsg( renderMod->GetKey(), plRefMsg::kOnCreate, 0, plPostEffectMod::kNodeRef ), plRefFlags::kPassiveRef );        

    // Create the dialog
    dialog = TRACKED_NEW pfGUIDialogMod;
    IAddKey( dialog, "GUIDialog" );

    dialog->SetRenderMod( renderMod );
    dialog->SetName( name );

    // Create the dummy scene object to hold the dialog
    plSceneObject   *newObj = TRACKED_NEW plSceneObject;
    IAddKey( newObj, "GUISceneObject" );

    // *#&$(*@&#$ need a coordIface...
    plCoordinateInterface *newCI = TRACKED_NEW plCoordinateInterface;
    IAddKey( newCI, "GUICoordIFace" );

    hsMatrix44 l2w, w2l;
    l2w.Reset();
//  l2w.NotIdentity();

    l2w.GetInverse( &w2l );

    // Using SendRef here because AddViaNotify will queue the messages up, which doesn't do us any good
    // if we need these refs right away
    hsgResMgr::ResMgr()->SendRef( dialog->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );      

    hsgResMgr::ResMgr()->AddViaNotify( newCI->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface ), plRefFlags::kActiveRef );     
    hsgResMgr::ResMgr()->AddViaNotify( renderMod->GetKey(), TRACKED_NEW plObjRefMsg( newObj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );      

    // Add the dialog to the GUI mgr
    plGenRefMsg *refMsg = TRACKED_NEW plGenRefMsg( pfGameGUIMgr::GetInstance()->GetKey(), 
                                            plRefMsg::kOnCreate, 0, pfGameGUIMgr::kDlgModRef );
    hsgResMgr::ResMgr()->AddViaNotify( dialog->GetKey(), refMsg, plRefFlags::kActiveRef );      

    newObj->SetSceneNode( node->GetKey() );

    newObj->SetTransform( l2w, w2l );
//  newCI->SetLocalToParent( l2w, w2l );

    if( show )
        pfGameGUIMgr::GetInstance()->ShowDialog( dialog );

    fDynDialogs.Append( dialog );
    return dialog;
}