/*
* npcclient.cpp - author: Keith Fulton <keith@paqrat.com>
*
* Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
*
*
* 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 (version 2 of the License)
* 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.
*
*/
#include <psconfig.h>

#include <iutil/objreg.h>
#include <iutil/cfgmgr.h>
#include <iutil/cmdline.h>
#include <iutil/object.h>
#include <csutil/csobject.h>
#include <ivaria/reporter.h>
#include <iutil/vfs.h>
#include <csutil/csstring.h>
#include <iutil/document.h>
#include <csutil/xmltiny.h>
#include <cstool/collider.h>
#include <ivaria/collider.h>

#include "util/serverconsole.h"
#include "pathfind.h"
#include "util/psdatabase.h"
#include "net/connection.h"
#include "net/msghandler.h"
#include "util/eventmanager.h"
#include "util/location.h"
#include "util/psstring.h"

#include "npcclient.h"

#include "psbehave/psworld.h"

#include "networkmgr.h"
#include "npcbehave.h"
#include "npc.h"
#include "perceptions.h"
#include "pathmanager.h"
#include "pathfind.h"
#include "gem.h"
#include "tribe.h"

#ifndef INFINITY
#define INFINITY 999999999.0F
#endif

bool running;
extern iDataConnection *db;

class psNPCTick : public psGameEvent
{
protected:
    psNPCClient *client;

public:
    psNPCTick(int offsetticks, psNPCClient *c);
    virtual void Trigger();  // Abstract event processing function
};


psNPCClient::psNPCClient ()
{
    world        = NULL;
    eventmanager = NULL;
    running      = true;
    database     = NULL;
    // Initialize the RNG using current time() as the seed value
    randomGen.Initialize();
}

psNPCClient::~psNPCClient()
{
    running = false;
    if (database)
        delete database;
}

bool psNPCClient::Initialize(iObjectRegistry* object_reg,const char *_host, const char *_user, const char *_pass, int _port)
{
    objreg = object_reg;

    if (!CelBase::Initialize(object_reg))
        return false;

    configmanager = CS_QUERY_REGISTRY(object_reg, iConfigManager);
    if (!configmanager)
    {
        CPrintf (CON_ERROR, "Couldn't find Configmanager!\n");
        return false;
    }

    engine = CS_QUERY_REGISTRY(object_reg, iEngine);
    if (!engine)
    {
        CPrintf (CON_ERROR, "Couldn't find Engine!\n");
        return false;
    }

    // Start Database

    database = new psDatabase(object_reg);

    csString db_host, db_user, db_pass, db_name;

    db_host = configmanager->GetStr("Planeshift.Database.npchost", "localhost");
    db_user = configmanager->GetStr("Planeshift.Database.npcuserid", "planeshift");
    db_pass = configmanager->GetStr("Planeshift.Database.npcpassword", "planeshift");
    db_name = configmanager->GetStr("Planeshift.Database.npcname", "planeshift");

    Debug4(LOG_STARTUP,0,COL_BLUE "Database Host: '%s' User: '%s' Databasename: '%s'\n" COL_NORMAL,
      (const char*) db_host, (const char*) db_user, (const char*) db_name);

    if (!database->Initialize(db_host, db_user, db_pass, db_name))
    {
        Error1("Could not create database or connect to it.\n");
        delete database;
        return false;
    }

    csString user,pass,host;
    int port;

    host = _host ? _host : configmanager->GetStr("Planeshift.NPCClient.host", "localhost");
    user = _user ? _user : configmanager->GetStr("Planeshift.NPCClient.userid", "superclient");
    pass = _pass ? _pass : configmanager->GetStr("Planeshift.NPCClient.password", "planeshift");
    port = _port ? _port : configmanager->GetInt("Planeshift.NPCClient.port", 13331);

    CPrintf(CON_DEBUG, "Connecting to Host: '%s' User: '%s' Password: '%s' Port %d...\n",
        (const char*) host, (const char*) user, (const char*) pass, port);

    CPrintf (CON_DEBUG, "Initialize Network Thread...\n");
    connection = new psNetConnection;
    if (!connection->Initialize(object_reg))
    {
        CPrintf (CON_ERROR, "Network thread initialization failed!\n");
        delete connection;
        return false;
    }
    if (!connection->Connect(host,port))
    {
        CPrintf(CON_ERROR, "Couldn't connect to %s on port %d.\n",(const char *)host,port);
        exit(1);
    }

    eventmanager = new EventManager;
    msghandler   = eventmanager;
    psMessageCracker::msghandler = eventmanager;

    if (!msghandler->Initialize(connection, 1000))
        return false;                // Attach to incoming messages.
    if (!msghandler->StartThread() )
        return false;

    CPrintf(CON_DEBUG, "Initialising CEL ---- ");


    csRef<iVFS> vfs = CS_QUERY_REGISTRY (object_reg, iVFS);
    if (!vfs)
        CPrintf(CON_ERROR, "could not open VFS\n");

    if (!LoadNPCTypes("/this/data/npcbehave.xml",vfs))
    {
        CPrintf(CON_ERROR, "Couldn't load npcbehave.xml.\n");
        exit(1);
    }

    if (!LoadWaypoints())
    {
        CPrintf(CON_ERROR, "Couldn't load the sc_waypoints table\n");
        exit(1);
    }
    
    if (!LoadNPCDefinitions("/this/data/npcdefs.xml",vfs))
    {
        CPrintf(CON_ERROR, "Couldn't load npcdefs.xml\n");
        exit(1);
    }

    if (!LoadLocations())
    {
        CPrintf(CON_ERROR, "Couldn't load the sc_locations table\n");
        exit(1);
    }

    if (!LoadTribes())
    {
        CPrintf(CON_ERROR, "Couldn't load the tribes table\n");
        exit(1);
    }
    

    cdsys = CS_QUERY_REGISTRY (objreg, iCollideSystem);

    //LoadMap("npcroom");
    //LoadMap("sewers");
    //LoadMap("arena");
    //LoadMap("akkaio");
    //pathFindTest(this);exit(0);

    PFMaps = new psPFMaps(objreg);
    
    network = new NetworkManager(msghandler,connection);
    network->Authenticate(host,port,user,pass);
    
    return true;
}

int psNPCClient::MainLoop ()
{
    // Eventually load an autoexec file
    csRef<iCommandLineParser> cmdline = 
        CS_QUERY_REGISTRY(objreg, iCommandLineParser);

    if (cmdline)
    {
        const char* ofile = cmdline->GetOption ("output");
        if (ofile != NULL)
        {
            ConsoleOut::SetOutputFile (ofile, false);
        }
        else
        {
            const char* afile = cmdline->GetOption ("append");
            if (afile != NULL)
            {
                ConsoleOut::SetOutputFile (afile, true);
            }
        }
        const char* autofile;
        for (int i = 0; (autofile = cmdline->GetOption("run", i)); i++)
        {
            char toexec[1000];
            strcpy(toexec, "exec ");
            strcat(toexec, autofile);
            ServerConsole::ExecuteScript(toexec);
        }
    }
    ConsoleOut::SetMaximumOutputClassStdout (CON_SPAM);
    ConsoleOut::SetMaximumOutputClassFile (CON_SPAM);

    // This starts the NPC AI processing loop.
    psNPCTick *tick = new psNPCTick(1000,this);
    eventmanager->Push(tick);

    return ServerConsole::MainLoop ();
}

void psNPCClient::Disconnect()
{
    network->Disconnect();
}

bool psNPCClient::LoadNPCTypes(const char *xmlfile,iVFS *vfs)
{
    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

    csRef<iDataBuffer> buff = vfs->ReadFile( xmlfile );

    if ( !buff || !buff->GetSize() )
    {
        return false;
    }

    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        Error3("%s in %s", error, xmlfile);
        return false;
    }
    csRef<iDocumentNode> root    = doc->GetRoot();
    if(!root)
    {
        Error2("No XML root in %s", xmlfile);
        return false;
    }

    csRef<iDocumentNode> topNode = root->GetNode("npctypes");
    if(!topNode)
    {
        Error2("No <npctypes> tag in %s", xmlfile);
        return false;
    }
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();

    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();

        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "npctype" ) == 0 )
        {
            NPCType *npctype = new NPCType;
            if (npctype->Load(node))
            {
                npctypes.Insert(npctype);
            }
            else
            {
                delete npctype;
                return false;
            }
        }
    }

    return true;
}

void psNPCClient::Add( gemNPCObject* object )
{
    objects.Push( object );
}

void psNPCClient::Remove ( unsigned id )
{
    gemNPCObject * obj = FindEntityID( id );
    if (obj == NULL)
    {
        CPrintf(CON_DEBUG, "NPCObject %d cannot be removed - not found\n", id);
        return;
    }

    // Remove entity from all hated lists.
    for (size_t x=0; x<npcs.Length(); x++)
    {
        npcs[x]->RemoveFromHateList(id);
    }

    npc_entities.DeleteAll( id );
    objects.Delete( obj );
    delete obj;


    CPrintf(CON_DEBUG, "NPCObject %u removed\n", id);
}

gemNPCObject *psNPCClient::FindEntityID(unsigned entityID)
{
    size_t i;
    for (i=0; i<objects.Length(); i++)
    {
        if (objects[i]->GetEntity()->GetID() == entityID)
            return objects[i];
    }
    return NULL;
}

gemNPCObject *psNPCClient::FindCharacterID(int char_id)
{
    size_t i;
    for (i=0; i<objects.Length(); i++)
    {
        if (objects[i]->GetPlayerID() == char_id)
            return objects[i];
    }
    return NULL;
}

void psNPCClient::RemoveAll()
{
    size_t i;
    for (i=0; i<objects.Length(); i++)
    {
        UnattachNPC(objects[i]->GetEntity(),FindAttachedNPC(objects[i]->GetEntity()));
    }
    objects.DeleteAll();
    npc_entities.DeleteAll();
}

bool psNPCClient::ReadSingleNPC(int char_id)
{
    Result result(db->Select("select * from sc_npc_definitions where char_id=%d",char_id));
    if (!result.IsValid())
    {
        Error2("Error loading char_id %d.",char_id);
        return false;
    }
    NPC *newnpc = new NPC;
    if (newnpc->Load(result[0],npctypes))
    {
        npcs.Push(newnpc);
        return true;
    }
    else
    {
        delete newnpc;
        return false;
    }
}
bool psNPCClient::ReadNPCsFromDatabase()
{
    Result rs(db->Select("select * from sc_npc_definitions"));

    if (!rs.IsValid())
    {
        Error2("Could not load npcs from db: %s",db->GetLastError() );
        return false;
    }
    for (int i=0; i<(int)rs.Count(); i++)
    {
        NPC *npc = new NPC;
        if (npc->Load(rs[i],npctypes))
        {
            npcs.Push(npc);
        }
        else
        {
            delete npc;
            return false;
        }
    }
    return true;
}

bool psNPCClient::LoadNPCDefinitions(const char *xmlfile,iVFS *vfs)
{
    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

    csRef<iDataBuffer> buff = vfs->ReadFile( xmlfile );

    if ( !buff || !buff->GetSize() )
    {
        return false;
    }

    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        Error3("Error %s in %s", error, xmlfile);
        return false;
    }

    if (!ReadNPCsFromDatabase())
        return false;
 
    csRef<iDocumentNode> root    = doc->GetRoot();
    if (!root)
    {
        Error2("No XML root in %s", xmlfile);
        return false;
    }
    csRef<iDocumentNode> topNode = root->GetNode("locations");
    if(!topNode)
    {
        Error2("No tag <locations> in %s", xmlfile);
        return false;
    }

    if (!LoadLocations(topNode))
        return false;

    topNode = root->GetNode("waypoints");
    if (!topNode)
    {
        Error2("No tag <waypoints> in %s", xmlfile);
        return false;
    }

    if (!LoadWaypoints(topNode))
        return false;

    topNode = root->GetNode("paths");
    if (!topNode)
    {
        Error2("No tag <paths> in %s", xmlfile);
        return false;
    }
    pathmgr = new PathManager;
    if (!pathmgr->Load(topNode))
        return false;

    return true;
}

bool psNPCClient::LoadLocations(iDocumentNode *topNode)
{
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();
    
    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();

        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        if ( strcmp( node->GetValue(), "loctype" ) == 0 ||
            strcmp( node->GetValue(), "region" ) == 0)
        {
            LocationType *loctype = new LocationType;
            if (strcmp( node->GetValue(), "region" ) == 0)
                loctype->region = true;
            else
                loctype->region = false;

            if (loctype->Load(node))
            {
                loctypes.Insert(loctype);
            }
            else
            {
                delete loctype;
                return false;
            }
        }
    }
    return true;
}

bool psNPCClient::LoadWaypoints(iDocumentNode *topNode)
{
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();
    
    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();
        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        if (!strcmp( node->GetValue(), "waypointlist" ))
        {
            csRef<iDocumentNodeIterator> iter = node->GetNodes();
            while (iter->HasNext())
            {
                csRef<iDocumentNode> node = iter->Next();
                if ( node->GetType() != CS_NODE_ELEMENT )
                    continue;
                if (!strcmp(node->GetValue(),"waypoint"))
                {
                    Waypoint *wp = new Waypoint;
                    if (wp->Load(node,engine))
                    {
                        waypoints.Insert(wp,true);
                    }
                    else
                    {
                        CPrintf(CON_ERROR, "Waypoint node had no name specified!\n");
                        delete wp;
                        return false;
                    }
                }
            }
        }
        else if (!strcmp(node->GetValue(), "waylinks" ))
        {
            csRef<iDocumentNodeIterator> iter = node->GetNodes();
            while (iter->HasNext())
            {
                csRef<iDocumentNode> node = iter->Next();
                if ( node->GetType() != CS_NODE_ELEMENT )
                    continue;
                if (!strcmp(node->GetValue(),"point"))
                {
                    Waypoint key;
                    key.name = node->GetAttributeValue("name");
                    Waypoint *wp = waypoints.Find(&key);
                    if (!wp)
                    {
                        CPrintf(CON_ERROR, "Waypoint called '%s' not defined.\n",key.name.GetData() );
                        return false;
                    }
                    else
                    {
                        csRef<iDocumentNodeIterator> iter = node->GetNodes();
                        while (iter->HasNext())
                        {
                            csRef<iDocumentNode> node = iter->Next();
                            if ( node->GetType() != CS_NODE_ELEMENT )
                                continue;
                            if (!strcmp(node->GetValue(),"link"))
                            {
                                Waypoint key;
                                key.name = node->GetAttributeValue("name");
                                Waypoint *wlink = waypoints.Find(&key);
                                if (!wlink)
                                {
                                    CPrintf(CON_ERROR, "Waypoint called '%s' not defined.\n",key.name.GetData() );
                                    return false;
                                }
                                else
                                {
                                    bool one_way = node->GetAttributeValueAsBool("oneway",false);

                                    float dist = 0;
                                    if (wp->loc.GetSector(engine) == wlink->loc.GetSector(engine))
                                    {
                                        dist = (wp->loc.pos - wlink->loc.pos).Norm();
                                    }
                                    
                                    wp->links.Push(wlink);
                                    wp->dists.Push(dist);
                                    
                                    if (!one_way)
                                    {
                                        wlink->links.Push(wp);  // bi-directional link is implied
                                        wlink->dists.Push(dist);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return true;
}

bool psNPCClient::LoadWaypoints()
{
    Result rs(db->Select("select wp.*,s.name as sector from sc_waypoints wp, sectors s where wp.loc_sector_id = s.id"));

    if (!rs.IsValid())
    {
        Error2("Could not load waypoints from db: %s",db->GetLastError() );
        return false;
    }
    for (int i=0; i<(int)rs.Count(); i++)
    {
        Waypoint *wp = new Waypoint();

        if (wp->Load(rs[i],engine))
        {
           waypoints.Insert(wp,true);
        }
        else
        {
            Error2("Could not load waypoint: %s",db->GetLastError() );            
            delete wp;
            return false;
        }
        
    }

    Result rs1(db->Select("select * from sc_waypoint_links"));

    if (!rs1.IsValid())
    {
        Error2("Could not load waypoint links from db: %s",db->GetLastError() );
        return false;
    }
    for (int i=0; i<(int)rs1.Count(); i++)
    {
        Waypoint * wp1 = FindWaypoint(rs1[i].GetInt("wp1"));
        Waypoint * wp2 = FindWaypoint(rs1[i].GetInt("wp2"));
        psString flagstr(rs1[i]["flags"]);

        bool one_way = flagstr.FindSubString("ONEWAY",0,true)!=-1;

        float dist = 0;
        if (wp1->loc.GetSector(engine) == wp2->loc.GetSector(engine))
        {
            dist = (wp1->loc.pos - wp2->loc.pos).Norm();
        }
                                    
        wp1->links.Push(wp2);
        wp1->dists.Push(dist);
        
        if (!one_way)
        {
            wp2->links.Push(wp1);  // bi-directional link is implied
            wp2->dists.Push(dist);
        }
        
    }
    
    
    return true;
}

bool psNPCClient::LoadLocations()
{
    return true;
}

bool psNPCClient::LoadTribes()
{
    Result rs(db->Select("SELECT t.*, s.name AS home_sector_name FROM tribes t, sectors s WHERE s.id = t.home_sector_id"));

    if (!rs.IsValid())
    {
        Error2("Could not load tribes from db: %s",db->GetLastError() );
        return false;
    }
    for (int i=0; i<(int)rs.Count(); i++)
    {
        psTribe *tribe = new psTribe();
        if (tribe->Load(rs[i]))
        {
            tribes.Push(tribe);

            Result rs2(db->Select("select * from tribe_members where tribe_id=%d",tribe->GetID()));
            if (!rs2.IsValid())
            {
                Error2("Could not load tribe members from db: %s",db->GetLastError() );
                return false;
            }
            for (int j=0; j<(int)rs2.Count(); j++)
            {
                tribe->LoadMember(rs2[j]);
            }
        }
        else
        {
            delete tribe;
            return false;
        }
    }

    return true;
}


void psNPCClient::AttachNPC( gemNPCActor* actor, uint8_t DRcounter)
{
    if (!actor) return;

    NPC *npc = NULL;

    // Check based on characterID
    npc = FindNPC( actor->GetPlayerID() );
    if ( !npc )
    {
        csString templateName;
        templateName.Append( "FAMILIAR:" );
        templateName.Append( actor->GetRace() );

        npc = FindNPC( templateName.GetData() );

        if (!npc)
        {
            // Try to mach on name
            npc =  FindNPC( actor->GetName() );
        }
        

        if ( npc )
        {
            // Insert a row in the db for this guy next.  
            // We will get an entity msg in a second to make him come alive.
            npc->InsertCopy(actor->GetPlayerID());

            // Now requery so we have the new guy on our list when we get the entity msg.
            if ( !ReadSingleNPC(actor->GetPlayerID() ) )
            {
                Error3("Error creating copy of master %d as id %d.",npc->GetID(), actor->GetPlayerID());
                return;
            }

            npc = FindNPC( actor->GetPlayerID() );
        }
    }

    if ( !npc )
    {
        CPrintf(CON_NOTIFY,"NPC %s (ID %d) was not found in scripted npcs for this npcclient.\n",actor->GetName().GetData(), actor->GetPlayerID() );
        return;
    }
    if(npc->GetEntity() != NULL)
    {
        CPrintf(CON_NOTIFY,"NPC %s (ID %d) (eid %d) is already assigned eid %d name %s.\n",actor->GetName().GetData(), actor->GetPlayerID(), actor->GetEntity()->GetID(), npc->GetEntity()->GetID(), npc->GetName().GetData() );
        CS_ASSERT(npc->GetEntity() == NULL);
    }
    npc->SetEntity( actor->GetEntity() );

    npc->SetOwnerID( actor->GetOwnerID() );

    AttachNPC( actor->GetEntity(), npc );
    npc->SetAlive(true);

    if(DRcounter != (uint8_t) -1)
        npc->SetDRCounter(DRcounter);

    // Check if this NPC is part of a tribe
    for (size_t j=0; j<tribes.Length(); j++)
    {
        // Check if npc is part of the tribe and if so attach the npc
        tribes[j]->CheckAttach(npc);
    }
    

    npc_entities.Put(actor->GetEntity()->GetID() ,actor->GetEntity() );
    npc->Printf("We are now managing NPC: %s(%d) EID %u.\n", actor->GetName().GetData(), actor->GetPlayerID(), actor->GetEntity()->GetID());

    // Test if this actor is in a valid starting position.
    npc->CheckPosition();

    // Report final correct starting position.
    GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());
}



NPC *psNPCClient::FindNPC(int character_id)
{
    for (size_t x=0; x<npcs.Length(); x++)
    {
        if (npcs[x]->GetID() == character_id)
            return npcs[x];
    }
    return NULL;
}

NPC *psNPCClient::FindNPC(PS_ID entid)
{
    iCelEntity *ent = npc_entities.Get( entid, 0 );
    if (ent)
    {
        return FindAttachedNPC(ent);
    }
    return NULL;
}

NPC *psNPCClient::FindNPC(const char *name)
{
    csString scpNPC( name );
    for (size_t x=0; x<npcs.Length(); x++)
    {
        if ( npcs[x]->GetName() == scpNPC )
            return npcs[x];
    }
    return NULL;
}

void psNPCClient::TriggerEvent(NPC *npc,Perception *pcpt,float max_range,
                               csVector3 *base_pos, iSector *base_sector)
{
    if (npc)
    {
        npc->TriggerEvent(pcpt, eventmanager);
    }
    else
    {
        max_range *= max_range;  // compare to squared version

        for (size_t i=0; i<npcs.Length(); i++)
        {
            if (npcs[i]==NULL)  // one of our npcs is not active right now
                continue;

            if (!max_range)  // broadcast perceptions
            {
                npcs[i]->TriggerEvent(pcpt,eventmanager);
            }
            else
            {
                if (npcs[i]->GetEntity() == NULL)
                    continue;

                iSector *sector;
                csVector3 pos;
                float yrot;
                psGameObject::GetPosition(npcs[i]->GetEntity(),pos,yrot,sector);

                if (sector != base_sector)
                {
                    // TODO: Transform pos into the coordinate system of
                    //       base_pos so that next statement will give a
                    //       true length. For now we don't percept from one
                    //       sector to another.
                    continue;
                }
                
                pos -= *base_pos;
                if (pos.SquaredNorm() < max_range)
                {
                    npcs[i]->TriggerEvent(pcpt,eventmanager);
                }
            }
        }
    }
}

void psNPCClient::SetEntityPos(PS_ID id, csVector3& pos, iSector* sector)
{
    iCelEntity *ent = FindEntity(id);
    if (ent)
    {
        iCelEntity *npc = npc_entities.Get( ent->GetID(), 0 );
        if (npc)
        {
            // Skipp updating NPC
            //    CPrintf(CON_DEBUG, "Update npc %s skipped.\n",npc->GetName() );
            return;
        }

        psGameObject::SetPosition(ent,pos,sector);
        //    CPrintf(CON_DEBUG, "Setting entity '%s' to (%1.2f,%1.2f,%1.2f)...\n",ent->GetName(),pos.x,pos.y,pos.z);
    }
    else
    {
        CPrintf(CON_DEBUG, "Entity %d not found!\n",id);
    }
}

iCelEntity* psNPCClient::FindActor( const char* actor )
{
    csString name(actor);
    
    for ( size_t x = 0; x < objects.Length(); x++ )
    {       
        if ( name == objects[x]->GetName() )
        {
            return objects[x]->GetEntity();
        }        
    }
    
    CPrintf(CON_DEBUG, "Could not find actor: %s\n", actor );
    return NULL;
}

bool psNPCClient::IsReady()
{
    return network->IsReady();
}

void psNPCClient::Tick()
{
    if (IsReady())
    {    
        tick_counter++;
        
        csTicks when = csGetTicks();
        for (size_t i=0; i<npcs.Length(); i++)
        {
            npcs[i]->Advance(when,eventmanager);
        }

        for (size_t j=0; j<tribes.Length(); j++)
        {
            tribes[j]->Advance(when,eventmanager);
        }

        if (tick_counter % 4 == 0)
        {
            PerceptProximityItems();
        }
    }
    network->SendAllCommands();
    psNPCTick *tick = new psNPCTick(250,this);
    eventmanager->Push(tick);
}


// Class which is used to attach to an iCelEntity so that
// we can find the NPC * again.

SCF_VERSION (NPCFinder, 0, 0, 1);
struct NPCFinder : public csObject
{
private:
    NPC* npc;

public:
    NPCFinder (NPC *npc)
    {
        NPCFinder::npc = npc;
    }
    virtual ~NPCFinder ()
    { }
    NPC* GetNPC() const { return npc; }
    SCF_DECLARE_IBASE_EXT (csObject);
};

SCF_IMPLEMENT_IBASE_EXT (NPCFinder)
SCF_IMPLEMENTS_INTERFACE (NPCFinder)
SCF_IMPLEMENT_IBASE_EXT_END

void psNPCClient::AttachNPC(iCelEntity *entity, NPC *npc)
{
    NPC* old_npc = FindAttachedNPC(entity);
    if (old_npc == npc)
        return;

    if (old_npc != 0)
        UnattachNPC(entity, old_npc);

    CS_ASSERT(FindAttachedNPC(entity) == NULL);
    csRef<iObject> object (SCF_QUERY_INTERFACE (entity, iObject));
    csRef<NPCFinder> cef = csPtr<NPCFinder> (new NPCFinder(npc) );
    cef->SetName ("__npcfind__");    // @@@ For debugging mostly.
    csRef<iObject> cef_obj (SCF_QUERY_INTERFACE (cef, iObject));
    object->ObjAdd (cef_obj);
}

void psNPCClient::UnattachNPC(iCelEntity *entity, NPC *npc)
{
    csRef<iObject> object (SCF_QUERY_INTERFACE (entity, iObject));
    csRef<NPCFinder> cef (CS_GET_CHILD_OBJECT (object, NPCFinder));
    if (cef)
    {
        if (cef->GetNPC () != npc)
        { return; }
        csRef<iObject> cef_obj (SCF_QUERY_INTERFACE (cef, iObject));
        object->ObjRemove (cef_obj);
    }
}

NPC* psNPCClient::FindAttachedNPC(iCelEntity *entity)
{
    csRef<iObject> object (SCF_QUERY_INTERFACE (entity, iObject));
    csRef<NPCFinder> cef (CS_GET_CHILD_OBJECT (object, NPCFinder));
    if (cef)
        return cef->GetNPC();
    return 0;
}


bool psNPCClient::LoadMap(const char* mapfile)
{
    if (!world)
    {
        world = new psWorld();        
        world->Initialize( object_reg );
        world->CreateMap(mapfile, mapfile,psWorld::LOAD_NOW );
    }
    else
    {        
        world->NewRegion(mapfile,psWorld::LOAD_NOW);
    }
    return true;
}

LocationType *psNPCClient::FindRegion(const char *regname)
{
    if (!regname)
        return NULL;

    LocationType key;
    key.name = regname;

    LocationType *found = loctypes.Find(&key);
    if (found && found->region==true)
    {
        return found;
    }
    return NULL;
}

NPCType *psNPCClient::FindNPCType(const char *npctype_name)
{
    NPCType key(npctype_name);

    return npctypes.Find(&key);
}

Location *psNPCClient::FindNearestLocation(const char *loctype, csVector3& pos, iSector* sector, float range, float *found_range)
{
    LocationType key;
    key.name = loctype;

    LocationType *found = loctypes.Find(&key);
    if (found && found->region==false)
    {
        float min_range = range*range;    // Working with Squared values
        if (range == -1) min_range = -1;  // -1*-1 = 1, will use -1 later

        int   min_i = -1;

        for (size_t i=0; i<found->locs.Length(); i++)
        {
            float dist2;
            
            if (found->locs[i]->GetSector(engine) == sector)
            {
                dist2 = (pos - found->locs[i]->pos).SquaredNorm();
            }
            else
            {
                // TODO: Find range when in diffrent sector, for now set it large.
                dist2 = 999999999.0F;
            }
            

            if (min_range < 0 || dist2 < min_range)
            {
                min_range = dist2;
                min_i = (int)i;
            }
        }
        if (min_i > -1)  // found closest one
        {
            if (found_range) *found_range = sqrt(min_range);

            return found->locs[(size_t)min_i];
        }
    }
    return NULL;
}

Location *psNPCClient::FindRandomLocation(const char *loctype, csVector3& pos, iSector* sector, float range, float *found_range)
{
    LocationType key;
    key.name = loctype;

    csArray<Location*> nearby;
    csArray<float> dist;

    LocationType *found = loctypes.Find(&key);
    if (found && found->region==false)
    {
        float min_range = range*range;    // Working with Squared values
        if (range == -1) min_range = -1;  // -1*-1 = 1, will use -1 later

        for (size_t i=0; i<found->locs.Length(); i++)
        {
            float dist2;
            
            if (found->locs[i]->GetSector(engine) == sector)
            {
                dist2 = (pos - found->locs[i]->pos).SquaredNorm();
            }
            else
            {
                // TODO: Find range when in diffrent sector, for now set it large.
                dist2 = 999999999.0F;
            }
            

            if (min_range < 0 || dist2 < min_range)
            {
                nearby.Push(found->locs[i]);
                dist.Push(dist2);
            }
        }

        if (nearby.Length()>0)  // found one or more closer than range
        {
            size_t pick = GetRandom((uint32)nearby.Length());
            
            if (found_range) *found_range = sqrt(dist[pick]);

            return nearby[pick];
        }
    }
    return NULL;
}

Waypoint *psNPCClient::FindNearestWaypoint(csVector3& v,iSector *sector, float range, float * found_range)
{
    BinaryRBIterator<Waypoint> iter(&waypoints);
    Waypoint *wp;

    float min_range = range*range;    // Working with Squared values
    if (range == -1) min_range = -1;  // -1*-1 = 1, will use -1 later

    Waypoint *min_wp = NULL;

    for (wp = iter.First(); wp; wp = ++iter)
    {
        float dist2;
        
        if (wp->GetSector(engine) == sector)
        {
            dist2 = (wp->loc.pos - v).SquaredNorm();
        }
        else
        {
            // TODO: Finding distance to WPs in other sectors.
            // This will give a random waypoint in another sector
            // if no waypoint in current sector.
            dist2 = 999999999.0F;
        }
        
        if (min_range < 0 || dist2 < min_range)
        {
            min_range = dist2;
            min_wp = wp;
        }
    }
    if (min_wp && found_range) *found_range = sqrt(min_range);

    return min_wp;
}

Waypoint *psNPCClient::FindRandomWaypoint(csVector3& v,iSector *sector, float range, float * found_range)
{
    BinaryRBIterator<Waypoint> iter(&waypoints);
    Waypoint *wp;

    csArray<Waypoint*> nearby;
    csArray<float> dist;
 
    float min_range = range*range;    // Working with Squared values
    if (range == -1) min_range = -1;  // -1*-1 = 1, will use -1 later

    for (wp = iter.First(); wp; wp = ++iter)
    {
        float dist2;
        
        if (wp->GetSector(engine) == sector)
        {
            dist2 = (wp->loc.pos - v).SquaredNorm();
        }
        else
        {
            // TODO: Finding distance to WPs in other sectors.
            // This will give a random waypoint in another sector
            // if no waypoint in current sector.
            dist2 = 999999999.0F;
        }
        
        if (min_range < 0 || dist2 < min_range)
        {
            nearby.Push(wp);
            dist.Push(dist2);
        }
    }

    if (nearby.Length()>0)  // found one or more closer than range
    {
        size_t pick = GetRandom((uint32)nearby.Length());
        
        if (found_range) *found_range = sqrt(dist[pick]);
        
        return nearby[pick];
    }


    return NULL;
}

Waypoint *psNPCClient::FindWaypoint(int id)
{
    BinaryRBIterator<Waypoint> iter(&waypoints);
    Waypoint *wp;

    for (wp = iter.First(); wp; wp = ++iter)
    {
        if (wp->id == id)
        {
            return wp;
        }
    }
    
    return NULL;
}


csList<Waypoint*> psNPCClient::FindWaypointPath(Waypoint * start, Waypoint * end)
{
    csList<Waypoint*> waypoint_list;
    csList<Waypoint*> priority; // Should have been a priority queue
    BinaryRBIterator<Waypoint> iter(&waypoints);
    Waypoint *wp;
    
    // Using Dijkstra's algorithm to find shortest way

    // Initialize
    for (wp = iter.First(); wp; wp = ++iter)
    {
        wp->distance = INFINITY;
        wp->pi = NULL;
        
        // Insert WP into priority queue
        priority.PushBack(wp);
    }
    start->distance = 0;

    while (!priority.IsEmpty())
    {
        Waypoint *wp_u = NULL, *pri_wp = NULL;
        // Extract min from priority queue
        csList<Waypoint*>::Iterator pri(priority);
        csList<Waypoint*>::Iterator pri_loc;
        while (pri.HasNext())
        {
            pri_wp = pri.Next();

            if (!wp_u || pri_wp->distance < wp_u->distance)
            {
                wp_u = pri_wp;
                pri_loc = pri;
            }
        }
        priority.Delete(pri_loc);
        size_t v;
        for (v = 0; v < wp_u->links.Length(); v++)
        {
            Waypoint * wp_v = wp_u->links[v];
            // Relax
            if (wp_v->distance > wp_u->distance + wp_u->dists[v])
            {
                wp_v->distance = wp_u->distance + wp_u->dists[v];
                wp_v->pi = wp_u;
            }
        }
        // if wp == end, we should be done!!!!!!
    }

    wp = end;

    if (end->pi)
    {
        wp = end;
        while (wp)
        {
            waypoint_list.PushFront(wp);
            wp = wp->pi;
        }
    }

    return waypoint_list;
}



void psNPCClient::ListAllNPCs(char * pattern)
{
    CPrintf(CON_CMDOUTPUT, "%-9s %-5s %-30s %-6s %-6s %-20s %-15s %-20s %-20s %-3s\n", 
            "NPC ID", "EID", "Name", "Entity", "Status", "Brain","Behaviour","Owner","Tribe","Dbg");
    for (size_t i = 0; i < npcs.Length(); i++)
    {
        if (!pattern || strstr(npcs[i]->GetName().GetData(),pattern))
        {
            CPrintf(CON_CMDOUTPUT, "%-9d %-5d %-30s %-6s %-6s %-20s %-15s %-20s %-20s %-3s\n" ,
                    npcs[i]->GetID(),
                    (npcs[i]->GetEntity()?npcs[i]->GetEntity()->GetID():0),
                    npcs[i]->GetName().GetDataSafe(),
                    (npcs[i]->GetEntity()?"Entity":"None  "),
                    (npcs[i]->IsAlive()?"Alive":"Dead"),
                    (npcs[i]->GetBrain()?npcs[i]->GetBrain()->GetName().GetDataSafe():""),
                    (npcs[i]->GetCurrentBehavior()?npcs[i]->GetCurrentBehavior()->GetName():""),
                    npcs[i]->GetOwnerName().GetDataSafe(),
                    (npcs[i]->GetTribe()?npcs[i]->GetTribe()->GetName().GetDataSafe():""),
                    (npcs[i]->IsDebugging()?"Yes":"No")
                    );
        }
    }
}

bool psNPCClient::DumpNPC(char *pattern)
{
    int id = atoi(pattern);
    for (size_t i=0; i<npcs.Length(); i++)
    {
        if (npcs[i]->GetID() == id)
        {
            npcs[i]->DumpBehaviorList(true);

            npcs[i]->DumpHateList(true);            
            return true;
        }
    }
    return false;
}

void psNPCClient::ListAllEntities(bool onlyCharacters)
{
    char * format = "%-9s %-5s %-15s %-20s\n";
    
    if(onlyCharacters)
    {
        CPrintf(CON_CMDOUTPUT, "%-9s %-5s %-15s %-20s %-3s\n" ,"Player ID", "EID","Type","Name","Vis");
        for (size_t i=0; i < objects.Length(); i++)
        {
            gemNPCActor * actor = dynamic_cast<gemNPCActor *> (objects[i]);
            if(!actor)
                continue;

            CPrintf(CON_CMDOUTPUT, "%-9d %-5d %-15s %-20s %-3s\n",
                actor->GetPlayerID(),
                actor->GetEntity()->GetID(),
                actor->GetObjectType(),
                actor->GetName().GetData(),
                (actor->IsVisible()?"Yes":"No"));
        }
        return;
    }

    CPrintf(CON_CMDOUTPUT, "%-5s %-15s %-20s %-3s Position\n","EID","Type","Name","Vis");
    for (size_t i=0; i < objects.Length(); i++)
    {
        gemNPCObject * obj = objects[i];
        iCelEntity *ent = obj->GetEntity();
        csVector3 pos;
        float rot;
        iSector *sector;
        psGameObject::GetPosition(ent,pos,rot,sector);
        const char *sector_name = 
            (sector) ? sector->QueryObject()->GetName():"(null)";

        CPrintf(CON_CMDOUTPUT, "%5d %-15s %-20s %-3s (%9.3f,%9.3f,%9.3f, %s)\n",
                ent->GetID(),
                obj->GetObjectType(),
                obj->GetName().GetData(),
                (obj->IsVisible()?"Yes":"No"),
                pos.x,pos.y,pos.z,sector_name);
    }
        
}

void psNPCClient::ListTribes(char * pattern)
{
    CPrintf(CON_CMDOUTPUT, "%9s %-30s %-7s %-7s %7s %7s %7s %7s %-15s \n", "Tribe id", "Name", "MCount","NPCs","x","y","z","r","sector");
    for (size_t i = 0; i < tribes.Length(); i++)
    {
        if (!pattern || strstr(tribes[i]->GetName().GetData(),pattern))
        {
            csVector3 pos;
            iSector* sector;
            float radius;
            tribes[i]->GetHome(pos,radius,sector);
            CPrintf(CON_CMDOUTPUT, "%9d %-30s %-7d %-7d %7.1f %7.1f %7.1f %7.1f %-15s\n" ,
                    tribes[i]->GetID(),
                    tribes[i]->GetName().GetDataSafe(),
                    tribes[i]->GetMemberIDCount(),
                    tribes[i]->GetMemberCount(),
                    pos.x,pos.y,pos.z,radius,(sector?sector->QueryObject()->GetName():"(null)"));
            CPrintf(CON_CMDOUTPUT,"Members:\n");
            CPrintf(CON_CMDOUTPUT, "%9s %-5s %-30s %-6s %-6s %-15s %-15s %-20s %-20s\n", 
                    "NPC ID", "EID", "Name", "Entity", "Status", "Brain","Behaviour","Owner","Tribe");
            for (size_t j = 0; j < tribes[i]->GetMemberCount(); j++)
            {
                NPC * npc = tribes[i]->GetMember(j);
                CPrintf(CON_CMDOUTPUT, "%-9d %-5d %-30s %-6s %-6s %-15s %-15s %-20s %-20s\n" ,
                        npc->GetID(),
                        (npc->GetEntity()?npc->GetEntity()->GetID():0),
                        npc->GetName().GetDataSafe(),
                        (npc->GetEntity()?"Entity":"None  "),
                        (npc->IsAlive()?"Alive":"Dead"),
                        (npc->GetBrain()?npc->GetBrain()->GetName().GetDataSafe():""),
                        (npc->GetCurrentBehavior()?npc->GetCurrentBehavior()->GetName():""),
                        npc->GetOwnerName().GetDataSafe(),
                        (npc->GetTribe()?npc->GetTribe()->GetName().GetDataSafe():"")
                        );
            }
            CPrintf(CON_CMDOUTPUT,"Resources:\n");
            CPrintf(CON_CMDOUTPUT,"%-20s %7s\n","Name","Amount");
            for (size_t r = 0; r < tribes[i]->GetResourceCount(); r++)
            {
                CPrintf(CON_CMDOUTPUT,"%-20s %7.1f\n",
                        tribes[i]->GetResource(r).name.GetData(),
                        tribes[i]->GetResource(r).amount);
            }
        }
    }
}

void psNPCClient::ListWaypoints(char * pattern)
{
    BinaryRBIterator<Waypoint> iter(&waypoints);
    Waypoint *wp;

    CPrintf(CON_CMDOUTPUT, "%9s %-30s %-40s %-10s %10s %s\n", "WP id", "Name", "Position","Radius","Dist","PI");
    for (wp = iter.First(); wp; wp = ++iter)
    {
        if (!pattern || strstr(wp->name.GetDataSafe(),pattern))
        {
            CPrintf(CON_CMDOUTPUT, "%9d %-30s (%9.3f,%9.3f,%9.3f, %s) %9.3f %9.3f %s" ,
                    wp->id,wp->name.GetDataSafe(),wp->loc.pos.x,wp->loc.pos.y,wp->loc.pos.z,
                    wp->loc.sectorName.GetDataSafe(),wp->loc.radius,wp->distance,
                    (wp->pi?wp->pi->name.GetDataSafe():""));

            for (size_t i = 0; i < wp->links.Length(); i++)
            {
                CPrintf(CON_CMDOUTPUT," %s(%.1f)",wp->links[i]->name.GetDataSafe(),
                        wp->dists[i]);
            }

            CPrintf(CON_CMDOUTPUT,"\n");
        }
    }
}

void psNPCClient::ListLocations(char * pattern)
{
    BinaryRBIterator<LocationType> iter(&loctypes);
    LocationType *loc;

    CPrintf(CON_CMDOUTPUT, "%9s %-30s %-10s %10s\n", "Loc id", "Name", "Region","");
    for (loc = iter.First(); loc; loc = ++iter)
    {
        if (!pattern || strstr(loc->name.GetDataSafe(),pattern))
        {
            CPrintf(CON_CMDOUTPUT, "%9d %-30s %-10s\n" ,
                    1,loc->name.GetDataSafe(),
                    (loc->region?"True":"False"));

            for (size_t i = 0; i < loc->locs.Length(); i++)
            {
                CPrintf(CON_CMDOUTPUT, "          (%9.3f,%9.3f,%9.3f, %s) %9.3f  %9.3f\n" ,
                        loc->locs[i]->pos.x,loc->locs[i]->pos.y,loc->locs[i]->pos.z,
                        loc->locs[i]->sectorName.GetDataSafe(),
                        loc->locs[i]->radius,loc->locs[i]->rot_angle);
            }
            
        }
    }
}


void psNPCClient::HandleDeath(NPC *who)
{
    who->GetBrain()->Interrupt(who,eventmanager);
    who->SetAlive(false);
    if (who->GetTribe())
    {
        who->GetTribe()->HandleDeath(who);
    }
}

void psNPCClient::HandlePlayerDeath(PS_ID who)
{
}

iCelEntity* psNPCClient::FindEntity( uint32_t id )
{
    for (size_t x = 0; x < objects.Length(); x++ )
    {
        if ( objects[x]->GetEntity()->GetID() == id )
            return objects[x]->GetEntity();
    }
    return NULL;
}

void psNPCClient::PerceptProximityItems()
{
    for (size_t i=0; i < objects.Length(); i++)
    {
        gemNPCItem * item = dynamic_cast<gemNPCItem*>(objects[i]);
        
        if (item && item->GetEntity() && item->IsPickable())
        {
            ItemPerception pcpt_adjacent("item adjacent", item->GetEntity());
            ItemPerception pcpt_nearby("item nearby", item->GetEntity());
            ItemPerception pcpt_sensed("item sensed", item->GetEntity());
            
            iSector *sector;
            csVector3 pos;
            float yrot;
            psGameObject::GetPosition(item->GetEntity(),pos,yrot,sector);


            TriggerEvent(NULL,&pcpt_adjacent,PERSONAL_RANGE_PERCEPTION,&pos,sector);
            TriggerEvent(NULL,&pcpt_nearby,SHORT_RANGE_PERCEPTION,&pos,sector);
            TriggerEvent(NULL,&pcpt_sensed,LONG_RANGE_PERCEPTION,&pos,sector);
        }
    }
}

void psNPCClient::CatchCommand(const char *cmd)
{
    printf("Caught command: %s\n",cmd);
    network->SendConsoleCommand(cmd+1);
}



/*------------------------------------------------------------------*/

psNPCTick::psNPCTick(int offsetticks, psNPCClient *c)
: psGameEvent(0,offsetticks,"psResumeScriptEvent")
{
    client = c;
}

void psNPCTick::Trigger()
{
    if (running)
        client->Tick();
}

