/*
* actionmanager.cpp
*
* Copyright (C) 2005 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
*
* Credits : 
*            Michael Cummings <cummings.michael@gmail.com>
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Creation Date: 1/20/2005
* Description : server manager for clickable map object actions
*
*/

#include <psconfig.h>
#include <csutil/xmltiny.h>

#include "iutil/object.h"
#include <iengine/campos.h>
#include <iengine/region.h>
#include <propclass/linmove.h>
#include <csgeom/math3d.h>

#include "globals.h"
#include "iserver/idal.h"
#include "actionmanager.h"
#include "gem.h"
#include "clients.h"
#include "events.h"
#include "psserver.h"
#include "util/psdatabase.h"
#include "net/msghandler.h"
#include "net/messages.h"
#include "util/log.h"
#include "util/serverconsole.h"

#include "cachemanager.h"
#include "combatmanager.h"
#include "netmanager.h"
#include "npcmanager.h"
#include "psserverchar.h"
#include "bulkobjects/psactionlocationinfo.h"
#include "entitymanager.h"
#include "progressionmanager.h"
#include "util/eventmanager.h"


psActionTimeoutGameEvent::psActionTimeoutGameEvent( ActionManager *mgr, const psActionLocation * actionLocation, size_t clientnum)
: psGameEvent(0, 1000, "psActionTimeoutGameEvent")
{
    valid = false;
    if ( !mgr ) 
        return;
    actionmanager =  mgr;
    info = actionLocation;
    client = clientnum;

    valid = true;
}

psActionTimeoutGameEvent::~psActionTimeoutGameEvent()
{
    valid = false;
    actionmanager = NULL;
    info = NULL;
    client = 0;
}

void psActionTimeoutGameEvent::Trigger()
{
    psSectorInfo *sector;
    float pos_x, pos_y, pos_z, yrot;
    Client *clientPtr = psserver->GetNetManager()->GetClient( (int)client );   
    if ( clientPtr && info )
    {
        clientPtr->GetCharacterData()->GetLocationInWorld( sector, pos_x, pos_y, pos_z, yrot );
        csVector3 clientPos(pos_x, pos_y, pos_z );

        if ( csSquaredDist::PointPoint( clientPos, info->position ) > ( info->radius * info->radius ) )
        {
            actionmanager->RemoveActiveTrigger( client, info );
            valid = false;
        }
        else
        {
            // still in range create new event
            psActionTimeoutGameEvent *newevent = new psActionTimeoutGameEvent( actionmanager, info, client );
            psserver->GetEventManager()->Push( newevent );
        }
    }
}

//----------------------------------------------------------------------------

ActionManager::ActionManager(  psDatabase *db, EntityManager *celserv )
{
    database = db;

    entitymanager = celserv;

    //clients = psserver->GetNetManager()->GetConnections();
    // Action Messages from client that need handling
    psserver->GetEventManager()->Subscribe( this, MSGTYPE_MAPACTION, REQUIRE_READY_CLIENT );
    //// Used to handle proximity triggers
    //   psserver->GetEventManager()->Subscribe( this, MSGTYPE_DEAD_RECKONING, REQUIRE_READY_CLIENT );
    //PreloadActionLocations();
}


ActionManager::~ActionManager()
{
    // Unsubscribe from Messages
    psserver->GetEventManager()->Unsubscribe( this, MSGTYPE_MAPACTION );
    //psserver->GetEventManager()->Unsubscribe( this, MSGTYPE_DEAD_RECKONING );
    {
        csHash<psActionLocation *>::GlobalIterator it (actionLocationList.GetIterator ());
        while ( it.HasNext () )
        {
            psActionLocation* actionLocation = it.Next ();
            delete actionLocation;
        }
    }
    database = NULL;
}

void ActionManager::HandleMessage( MsgEntry *me, Client *client )
{
    switch ( me->GetType() )
    {
        case MSGTYPE_MAPACTION:
        {
            psMapActionMessage msg( me );

            if ( msg.valid )
                HandleMessage( &msg, client );
            break;
        }
    //case MSGTYPE_DEAD_RECKONING:
    //    {
    //        psDRMessage msg( me, CacheManager::GetSingleton().GetMsgStrings(), psserver->GetEntityManager()->GetEngine() );

    //        if ( msg.valid )
 //               HandleMessage( &msg, client );
    //        break;
    //    }
    }
}

void ActionManager::HandleMessage( psMapActionMessage *msg, Client *client )
{
    if ( !msg->valid ) return;
    
    switch ( msg->command )
    {
    case psMapActionMessage::QUERY :
        HandleQueryMessage( msg->actionXML, client );
        break;
    case psMapActionMessage::SAVE :
        HandleSaveMessage( msg->actionXML, client );
        break;
    case psMapActionMessage::LIST_QUERY :
        HandleListMessage( msg->actionXML, client );
        break;
    case psMapActionMessage::DELETE_ACTION:
        HandleDeleteMessage( msg->actionXML, client );
        break;
    case psMapActionMessage::RELOAD_CACHE:
        HandleReloadMessage( client );
        break;
    }
}

//void ActionManager::HandleMessage( psDRMessage *msg, Client *client )
//{
//    gemActor *actor = client->GetActor();
//
//    // We don't want zombies
//    if (actor && !actor->IsAlive())
//        return;
//
//    if (msg->sector == NULL) return;
//
//    // Create Query Message for Query
//    const char* xmlFormat = "<location><sector>%s</sector><position><x>%0.4f</x><y>%.4f</y><z>%.4f</z></position><triggertype>PROXIMITY</triggertype></location>";
//
//    csString xml("");
//    xml.Format( xmlFormat, EscpXML( msg->sectorName ).GetData(), msg->pos.x, msg->pos.y, msg->pos.z );
//
//    psMapActionMessage mamsg( client->GetClientNum(), psMapActionMessage::QUERY, xml );
//    psserver->GetEventManager()->Publish( mamsg.msg );
//}

void ActionManager::HandleQueryMessage( csString xml, Client *client )
{
    csString  triggerType;
    bool handled = false;

    // Search for matching locations
    csRef<iDocument> doc;
    csRef<iDocumentNode> root, topNode, node;   
    
    if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) 
        && (node = topNode->GetNode("triggertype"))) 
    {        
        triggerType = node->GetContentsValue();
        
        if ( triggerType.CompareNoCase( "SELECT" )    )   handled = HandleSelectQuery   ( topNode, client );
        if ( triggerType.CompareNoCase( "PROXIMITY" ) )   handled = HandleProximityQuery( topNode, client );

        if ( !handled )
        {
            psMapActionMessage msg(  client->GetClientNum(), psMapActionMessage::NOT_HANDLED, xml );
            if ( msg.valid ) 
                msg.SendMessage();
        }
    }
    else
    {
        Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", 
        client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() );
    }
}

bool ActionManager::HandleSelectQuery( csRef<iDocumentNode> topNode, Client *client )
{
    csString  triggerType(""),
              sectorName(""),
              meshName("");
    bool handled = false;

    // Search for matching locations
    csRef<iDocumentNode> node;

    // sector
    node = topNode->GetNode( "triggertype" );
    if ( node )
    {
        triggerType = node->GetContentsValue();
    }

    // sector
    node = topNode->GetNode( "sector" );
    if ( node )
    {
        sectorName = node->GetContentsValue();
    }

    // mesh
    node = topNode->GetNode( "mesh" );
    if ( node )
    {
        meshName = node->GetContentsValue();
    }

    psActionLocation *search = new psActionLocation();
    search->Load( topNode );

    // Search for matches
    csArray<psActionLocation *> matchMesh;  // matches on sector + mesh
    csArray<psActionLocation *> matchPoly;  // matches on sector + mesh + poly
    csArray<psActionLocation *> matchPoint; // matches on sector + mesh + poly + point w/in radius
    csArray<psActionLocation *> matches;    
    psActionLocation* actionLocation;

    csHash<psActionLocation *>::Iterator iter ( actionLocationList.GetIterator( csHashCompute( triggerType + sectorName + meshName ) ) );
    while ( iter.HasNext() )
    {
        actionLocation = iter.Next ();

        switch ( actionLocation->IsMatch( search ) ) 
        {
        case 0: // No Match
            break;
        case 1: // Match on Sector + Mesh
            matchMesh.Push( actionLocation );
            break;
        case 2: // Match on Poly
            matchPoly.Push( actionLocation );
            break;
        case 3: // Match on Point
            matchPoint.Push( actionLocation );
            break;
        }
    }

    // Use correct Set of Matches
    if ( matchPoint.Length() != 0 )
        matchPoint.TransferTo( matches );
    else if ( matchPoly.Length() != 0 )
        matchPoly.TransferTo( matches );
    else if ( matchMesh.Length() != 0 )
        matchMesh.TransferTo( matches );
    
    // ProcessMatches
    handled = ProcessMatches( matches, client );

    // cleanup
    delete search;

    return handled;
}

bool ActionManager::HandleProximityQuery( csRef<iDocumentNode> topNode, Client *client )
{
    csString  sectorName("");
    csVector3 position( 0.0f, 0.0f, 0.0f );

    bool handled = false;

    // Search for matching locations
    csRef<iDocumentNode> node;
    
    // sector
    node = topNode->GetNode( "sector" );
    if ( node )
    {
        sectorName = node->GetContentsValue();
    }

    csRef<iDocumentNode> posNode = topNode->GetNode( "position" );
    if ( topNode )
    {
        float posx=0.0f, posy=0.0f, posz=0.0f;

        node = posNode->GetNode( "x" );
        if ( node ) posx = node->GetContentsValueAsFloat();

        node = posNode->GetNode( "y" );
        if ( node ) posy = node->GetContentsValueAsFloat();

        node = posNode->GetNode( "z" );
        if ( node ) posz = node->GetContentsValueAsFloat();

        position = csVector3( posx, posy, posz );
    }
    
    csArray<psActionLocation *> matches;    
    psActionLocation* actionLocation;
    csHash<psActionLocation *>::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) );
    while (iter.HasNext ())
    {
        actionLocation = iter.Next ();
        
        if ( actionLocation->triggertype == "PROXIMITY" )
        {
            csString key("");
            size_t id= actionLocation->id; 
            key.AppendFmt("%zu", id);
            size_t clientnum=client->GetClientNum();
            key.AppendFmt( "%zu", clientnum );
            csHash<psActionLocation *>::Iterator active ( activeTriggers.GetIterator( csHashCompute( key ) ) );

            if ( ( actionLocation->sectorname == sectorName ) &&
                 ( csSquaredDist::PointPoint( actionLocation->position, position ) < ( actionLocation->radius * actionLocation->radius ) ) )
            { // Found match
                if ( !active.HasNext() )
                    matches.Push( actionLocation );
            }
        }
    }

    // ProcessMatches
    handled = ProcessMatches( matches, client );

    csArray<psActionLocation *>::Iterator results( matches.GetIterator() );
    while ( results.HasNext() )
    {
        actionLocation = results.Next();
        csString key("");
        size_t id=actionLocation->id;
        size_t clientnum=client->GetClientNum();
        key.AppendFmt("%zu",id );
        key.AppendFmt( "%zu", clientnum );
        activeTriggers.Put( csHashCompute( key ), actionLocation );
        psActionTimeoutGameEvent *event = new psActionTimeoutGameEvent( this, actionLocation, client->GetClientNum() );
        psserver->GetEventManager()->Push( event );
    }
    return handled;
}

psActionLocation *ActionManager::FindAction( uint id )
{
    psActionLocation* actionLocation;
    csHash<psActionLocation *>::GlobalIterator iter ( actionLocationList.GetIterator() );

    while ( iter.HasNext() )
    {
        actionLocation = iter.Next();

        if ( actionLocation->GetGemObject()->GetEntity()->GetID() == id )
        {
            return actionLocation;
        }
    }
    return NULL;
}

bool ActionManager::ProcessMatches( csArray<psActionLocation *> matches, Client* client )
{
    bool handled = false;
    psActionLocation* actionLocation;

    // Call correct OperationHandler
    csArray<psActionLocation *>::Iterator results( matches.GetIterator() );
    while ( results.HasNext() )
    {
        actionLocation = results.Next();

        if ( actionLocation->responsetype == "EXAMINE" )
        {
            HandleExamineOperation( actionLocation, client );
            handled = true;
        }

        if ( actionLocation->responsetype == "SCRIPT" )
        {
            HandleScriptOperation( actionLocation, client );
            handled = true;
        }
    }

    return handled;
}

void ActionManager::HandleListMessage( csString xml, Client *client )
{
    if ( client->GetSecurityLevel() <= 29)
    {
        psserver->SendSystemError(client->GetAccountID(), "Access is denied. Only Admin level 9 can manage Actions.");
        return;
    }

    csString  sectorName;

    // Search for matching locations
    csRef<iDocument> doc ;    
    csRef<iDocumentNode> root, topNode, node;

    if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) 
        && (node = topNode->GetNode("sector"))) // sector
    {
        sectorName = node->GetContentsValue();
        
        // Call proper operation
        csString responseXML("");
        responseXML.Append( "<locations>" );

        csHash<psActionLocation *>::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) );
        while (iter.HasNext ())
        {
            psActionLocation* actionLocation = iter.Next ();

            responseXML.Append( actionLocation->ToXML() );
        }
        responseXML.Append( "</locations>" );

        psMapActionMessage msg( client->GetClientNum(), psMapActionMessage::LIST, responseXML );
        if ( msg.valid ) 
            msg.SendMessage();
    }
    else
    {
        Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", 
        client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() );
    }

}

void ActionManager::HandleSaveMessage( csString xml, Client *client )
{
    if ( client->GetSecurityLevel() <= 29)
    {
        psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions.");
        return;
    }

    // Search for matching locations
    csRef<iDocument> doc;    
    csRef<iDocumentNode> root, topNode;
    if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))))// sector
    {
        csRef<iDocumentNode> node;
        psActionLocation *action;

        node = topNode->GetNode( "id" );
        if ( !node ) return;
        csString id( node->GetContentsValue() );
    
        node = topNode->GetNode( "name" );
        if ( !node ) return;
        csString name( node->GetContentsValue() );

        node = topNode->GetNode( "sector" );
        if ( !node ) return;
        csString sectorName( node->GetContentsValue() );

        node = topNode->GetNode( "mesh" );
        if ( !node ) return;
        csString meshName( node->GetContentsValue() );

        if ( id.Length() == 0 )
        { // Add New Location
            action = new psActionLocation();
            CS_ASSERT( action != NULL );
        }
        else
        { // Update existing location
            psActionLocation *current;

            // Find Elememt
            csHash<psActionLocation *>::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) );
            while ( iter.HasNext() )
            {
                current = iter.Next();
                CS_ASSERT( current != NULL );

                if ( current->id == (size_t)atoi( id.GetData() ) )
                {
                    action = current;
                }
            }

            // Remove from Cache
            actionLocationList.Delete( csHashCompute( action->triggertype + action->sectorname + action->meshname ), action );
            actionLocation_by_name.Delete( csHashCompute( action->name ), action );
            actionLocation_by_sector.Delete( csHashCompute( action->sectorname ), action );
        }

        if ( action != NULL )
        {
            // Update DB
            if ( action->Load( topNode ) )
            {
                action->Save();

                //Update Cache
                if ( entitymanager->CreateActionLocation( action, false ) )
                {
                    actionLocationList.Put( csHashCompute( action->triggertype + action->sectorname + action->meshname ), action );
                    actionLocation_by_name.Put( csHashCompute( action->name ), action );
                    actionLocation_by_sector.Put( csHashCompute( action->sectorname ), action );
                }
                else
                {
                    Error2("Failed to find load action : \"%s\"", action->name.GetData());
                    delete action;
                }
            }
            else
            {
                Error2("Failed to find load action : \"%s\"", action->name.GetData());
                delete action;
            }
        }

        csString xmlMsg;
        csString escpxml = EscpXML(sectorName);
        xmlMsg.Format("<location><sector>%s</sector></location>", escpxml.GetData() );
        HandleListMessage(xmlMsg, client );
    }
    else
    {
        Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", 
        client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() );
    }
}

void ActionManager::HandleDeleteMessage( csString xml, Client *client )
{
    if ( client->GetSecurityLevel() <= 29)
    {
        psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions.");
        return;
    }

    // Search for matching locations
    csRef<iDocument>     doc;    
    csRef<iDocumentNode> root, topNode, node;

    if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) 
        && (node = topNode->GetNode("id")))     
    {
        csString id( node->GetContentsValue() );

        // Find Elememt
        psActionLocation *current, *actionLocation;
        csHash<psActionLocation *>::GlobalIterator iter ( actionLocationList.GetIterator() );
        while ( iter.HasNext() )
        {
            current = iter.Next();
            CS_ASSERT( current != NULL );

            if ( current->id == (size_t)atoi( id.GetData() ) )
            {
                actionLocation = current;
            }
        }

        // No Match
        if ( !actionLocation ) return;

        csString sectorName = actionLocation->sectorname;

        // Update DB
        if ( actionLocation->Delete() )
        {
            // Remove from Cache
            actionLocationList.Delete( csHashCompute( actionLocation->triggertype + actionLocation->sectorname + actionLocation->meshname ), actionLocation );
            actionLocation_by_name.Delete( csHashCompute( actionLocation->name ), actionLocation );
            actionLocation_by_sector.Delete( csHashCompute( actionLocation->sectorname ), actionLocation );

            delete actionLocation;
        }

        csString xmlMsg;
        csString escpxml = EscpXML(sectorName);
        xmlMsg.Format("<location><sector>%s</sector></location>", escpxml.GetData() );
        HandleListMessage(xmlMsg, client );        
    }
    else
    {
        Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", 
        client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() );
    }

    
}

void ActionManager::HandleReloadMessage(Client * client)
{
    if ( client->GetSecurityLevel() <= 29)
    {
        psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions.");
        return;
    }

    csHash<psActionLocation *>::GlobalIterator it (actionLocationList.GetIterator ());
    while (it.HasNext ())
    {
        psActionLocation* actionLocation = it.Next ();
        delete actionLocation;
    }

    actionLocationList.DeleteAll();
    actionLocation_by_sector.DeleteAll();
    actionLocation_by_name.DeleteAll();

    RepopulateActionLocations();
}

void ActionManager::HandleExamineOperation( psActionLocation* action, Client *client )
{
    // Create Entity on Client
    action->Send( client->GetClientNum() );

    // Set as the target
    client->SetTargetObject( action->GetGemObject(), true );

    // check if the actionlocation is linked to real item
    int instance_id = action->GetInstanceIDOfContainer();
    if (instance_id==-1) 
    {
        if (action->GetGemObject()->GetItem())
          instance_id = (int)action->GetGemObject()->GetItem()->GetUID();
    }

    gemItem* realItem = psserver->GetEntityManager()->GetGEM()->FindItemEntity( instance_id );

    // Invoke Interaction menu
    int options = psGUIInteractMessage::EXAMINE;
    if (!action->IsGameBoard())
        options |= psGUIInteractMessage::USE;
    else
        options |= psGUIInteractMessage::PLAYGAME;
    options |= psGUIInteractMessage::CLOSE;

    if (realItem && realItem->IsContainer()) options |= psGUIInteractMessage::COMBINE;
    if (realItem && realItem->IsLockable())  options |= psGUIInteractMessage::UNLOCK;

    psGUIInteractMessage interactMsg( client->GetClientNum(), options );
    interactMsg.SendMessage();

}



void ActionManager::HandleScriptOperation( psActionLocation* action, Client *client )
{
    // if no event is specified, do nothing
    if ( action->response.Length() > 0)
    {
        ProgressionEvent *progEvent = psserver->GetProgressionManager()->FindEvent( action->response );
        if (progEvent)
        {
            if ( progEvent->Run( client->GetActor(), NULL, psserver->GetProgressionManager() ) )
            {
                return;
            }
        }
        else
        {
            Error2("Failed to find progression event \"%s\"", action->response.GetData());
            return;
        }
    }
}

void ActionManager::RemoveActiveTrigger( size_t clientnum, const psActionLocation *actionLocation )
{
    csString key("");
    size_t id= actionLocation->id;
    key.AppendFmt("%zu", id);
    key.AppendFmt("%zu",  clientnum );

    csHash<psActionLocation *>::Iterator active ( activeTriggers.GetIterator( csHashCompute( key ) ) );

    while ( active.HasNext() )
        activeTriggers.Delete( csHashCompute( key ), active.Next() );
}

bool ActionManager::RepopulateActionLocations(psSectorInfo *sectorinfo)
{
    unsigned int currentrow;
    psActionLocation* newaction;
    csString query;
    
    if ( sectorinfo )
        query.Format("SELECT al.*, master.triggertype master_triggertype, master.responsetype master_responsetype, master.response master_response FROM action_locations al LEFT OUTER JOIN action_locations master ON al.master_id = master.id WHERE al.sectorname='%s'", sectorinfo->name.GetData());
    else
        query = "SELECT al.*, master.triggertype master_triggertype, master.responsetype master_responsetype, master.response master_response FROM action_locations al LEFT OUTER JOIN action_locations master ON al.master_id = master.id";
    
    Result result( db->Select(query) );

    if (!result.IsValid())
        return false;

    for ( currentrow = 0; currentrow < result.Count(); currentrow++)
    {
        newaction = new psActionLocation();
        CS_ASSERT(newaction != NULL);

        if ( newaction->Load( result[currentrow] ) )
        {
            if ( entitymanager->CreateActionLocation( newaction, false ) )
            {
                actionLocationList.Put( csHashCompute( newaction->triggertype + newaction->sectorname + newaction->meshname ), newaction );
                actionLocation_by_name.Put( csHashCompute( newaction->name ), newaction );
                actionLocation_by_sector.Put( csHashCompute( newaction->sectorname ), newaction );
            }
            else
            {
                Error2("Failed to find load action : \"%s\"", newaction->name.GetData());
                delete newaction;
            }
        }
        else
        {
            Error2("Failed to find load action : \"%s\"", newaction->name.GetData());
            delete newaction;
        }
    }
    return true;
}

