/*
* npcbehave.cpp by 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 <csutil/csstring.h>
#include <csgeom/transfrm.h>
#include <iutil/document.h>
#include <iutil/vfs.h>
#include <csutil/randomgen.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>

#include <propclass/mesh.h>
#include <physicallayer/entity.h>
#include <physicallayer/propclas.h>
#include <propclass/linmove.h>

#include "net/msghandler.h"
#include "net/npcmessages.h"
#include "npcbehave.h"
#include "perceptions.h"
#include "npc.h"
#include "npcclient.h"
#include "networkmgr.h"
#include "globals.h"
#include "util/log.h"



Reaction::Reaction()
{
    delta_desire = 0;
    affected   = NULL;
    range      = 0;
    inactive_only = false;
}

bool Reaction::Load(iDocumentNode *node,BehaviorSet& behaviors)
{
    delta_desire = node->GetAttributeValueAsFloat("delta");

    // Handle hooking up to the right behavior
    csString name = node->GetAttributeValue("behavior");
    affected = behaviors.Find(name);
    if (!affected)
    {
        Error2("Reaction specified unknown behavior of '%s'. Error in XML.\n",(const char *)name);
        return false;
    }

    // Handle hooking up to the perception
    event_type    = node->GetAttributeValue("event");
    range         = node->GetAttributeValueAsFloat("range");
    weight        = node->GetAttributeValueAsFloat("weight");
    faction_diff  = node->GetAttributeValueAsInt("faction_diff");
    oper          = node->GetAttributeValue("oper");
    time          = node->GetAttributeValueAsInt("value");
    inactive_only = node->GetAttributeValueAsBool("inactive_only");

    return true;
}

void Reaction::DeepCopy(Reaction& other,BehaviorSet& behaviors)
{
    delta_desire  = other.delta_desire;
    affected      = behaviors.Find(other.affected->GetName());
    event_type    = other.event_type;
    range         = other.range;
    faction_diff  = other.faction_diff;
    oper          = other.oper;
    weight        = other.weight;
    time          = other.time;
    inactive_only = other.inactive_only;
}

void Reaction::React(NPC *who,EventManager *eventmgr,Perception *pcpt)
{
    if (!who)
        CPrintf(CON_ERROR, "ERROR: perception for NULL npc\n");

    if (!who->GetEntity())
        return;

    if (!pcpt->ShouldReact(this,who))
        return;

    // When inactive_only flag is set we should do nothing
    // if the affected behaviour is ative.
    if (inactive_only && affected->GetActive() )
        return;

    who->Printf("Adding %1.1f need to behavior %s for npc %s.\n",
        delta_desire, affected->GetName(), who->GetEntity()->GetName() );

    if (delta_desire>0 || delta_desire<-1)
    {
        affected->ApplyNeedDelta(delta_desire);
    }
    else if (fabs(delta_desire+1)>SMALL_EPSILON)  // -1 in delta means "don't react"
    {
        // zero delta means guarantee that this affected
        // need becomes the highest (and thus active) one.
        if (!who->GetCurrentBehavior())
            return;

        float highest = who->GetCurrentBehavior()->CurrentNeed();
        affected->ApplyNeedDelta( highest - affected->CurrentNeed() + 25);
        affected->SetCompletionDecay(-1);
    }
    
    pcpt->ExecutePerception(who,weight);

    who->DumpBehaviorList( );

    Perception *p = pcpt->MakeCopy();
    who->SetLastPerception(p);
}


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

Perception *Perception::MakeCopy()
{
    Perception *p = new Perception(name);
    return p;
}

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

bool RangePerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType() &&
        range < reaction->GetRange())
        return true;
    else
        return false;
}

Perception *RangePerception::MakeCopy()
{
    RangePerception *p = new RangePerception(name,range);
    return p;
}

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


bool FactionPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        CPrintf(CON_DEBUG, "FactionPerception Should react? -> ");
        if (reaction->GetOp() == '>' )
        {
            CPrintf(CON_DEBUG, "Checking %d > %d.",faction_delta,reaction->GetFactionDiff() );
            if (faction_delta > reaction->GetFactionDiff() )
            {
                CPrintf(CON_DEBUG, "True\n");
                return true;
            }
            else
            {
                CPrintf(CON_DEBUG, "False\n");
                return false;
            }
        }
        else
        {
            CPrintf(CON_DEBUG, "Checking %d < %d.",faction_delta,reaction->GetFactionDiff() );
            if (faction_delta < reaction->GetFactionDiff() )
            {
                CPrintf(CON_DEBUG, "True\n");
                return true;
            }
            else
            {
                CPrintf(CON_DEBUG, "False\n");
                return false;
            }
        }
    }
    return false;
}

bool FactionPerception::GetLocation(csVector3& pos)
{
    if (player)
    {
        iSector *sector;
        float rot;
        psGameObject::GetPosition(player,pos,rot,sector);
        CPrintf(CON_DEBUG, "Perception location was %1.2f, %1.2f, %1.2f\n",pos.x,pos.y,pos.z);
        return true;
    }
    return false;
}

Perception *FactionPerception::MakeCopy()
{
    FactionPerception *p = new FactionPerception(name,faction_delta,player);
    return p;
}

void FactionPerception::ExecutePerception(NPC *npc,float weight)
{
    npc->AddToHateList(player,weight*-faction_delta);
}

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


bool ItemPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        return true;
    }
    return false;
}

bool ItemPerception::GetLocation(csVector3& pos)
{
    if (item)
    {
        iSector *sector;
        float rot;
        psGameObject::GetPosition(item,pos,rot,sector);
        CPrintf(CON_DEBUG, "Perception location was %1.2f, %1.2f, %1.2f\n",pos.x,pos.y,pos.z);
        return true;
    }
    return false;
}

Perception *ItemPerception::MakeCopy()
{
    ItemPerception *p = new ItemPerception(name,item);
    return p;
}

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



bool AttackPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        return true;
    }
    return false;
}

Perception *AttackPerception::MakeCopy()
{
    AttackPerception *p = new AttackPerception(name,attacker);
    return p;
}

void AttackPerception::ExecutePerception(NPC *npc,float weight)
{
    npc->AddToHateList(attacker,weight);
}
//---------------------------------------------------------------------------------

bool GroupAttackPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        return true;
    }
    return false;
}

Perception *GroupAttackPerception::MakeCopy()
{
    GroupAttackPerception *p = new GroupAttackPerception(name,attacker_ents,bestSkillSlots);
    return p;
}

void GroupAttackPerception::ExecutePerception(NPC *npc,float weight)
{
    for(size_t i=0;i<attacker_ents.Length();i++)
        npc->AddToHateList(attacker_ents[i],bestSkillSlots[i]*weight);
}

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

bool DamagePerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        CPrintf(CON_DEBUG, "Damage of %1.1f from player %s.\n",damage,attacker->GetName() );
        return true;
    }
//    CPrintf(CON_DEBUG, "No\n");
    return false;
}

Perception *DamagePerception::MakeCopy()
{
    DamagePerception *p = new DamagePerception(name,attacker,damage);
    return p;
}

void DamagePerception::ExecutePerception(NPC *npc,float weight)
{
    npc->AddToHateList(attacker,damage*weight);
}

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

SpellPerception::SpellPerception(const char *name,
                                 iCelEntity *caster,iCelEntity *target, 
                                 const char *type,float severity)
                                 : Perception(name)
{
    this->caster = caster;
    this->target = target;
    this->spell_severity = severity;
    this->spell_type_str = type;
}

bool SpellPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    csString event(spell_type_str);
    event.Append(':');

    if (npc->GetEntityHate(caster) || npc->GetEntityHate(target))
        event.Append("target");
    else if (target == npc->GetEntity())
        event.Append("self");
    else
        event.Append("unknown");

    if (event == reaction->GetEventType())
    {
        CPrintf(CON_DEBUG, "%s spell cast by %s on %s, severity %1.1f.\n",
            event.GetData(), (caster)?caster->GetName():"(Null caster)", (target)?target->GetName():"(Null target)", spell_severity);

        return true;
    }
    return false;
}

Perception *SpellPerception::MakeCopy()
{
    SpellPerception *p = new SpellPerception(name,caster,target,spell_type_str,spell_severity);
    return p;
}

void SpellPerception::ExecutePerception(NPC *npc,float weight)
{
    npc->AddToHateList(caster,spell_severity*weight);
}

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

bool TimePerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType() )
    {
        CPrintf(CON_DEBUG, "Time is now %d o'clock and I need %d o'clock.",time,reaction->GetReactTime() );
        if (time == reaction->GetReactTime() )
        {
            CPrintf(CON_DEBUG, "Reacting.\n");
            return true;
        }
        else
        {
            CPrintf(CON_DEBUG, "Skipping.\n");
            return false;
        }
    }
    return false;
}

Perception *TimePerception::MakeCopy()
{
    TimePerception *p = new TimePerception(name,time);
    return p;
}

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



bool DeathPerception::ShouldReact(Reaction *reaction,NPC *npc)
{
    if (name == reaction->GetEventType())
    {
        CPrintf(CON_DEBUG, "Reacting to death perception.\n");
        return true;
    }
    return false;
}

Perception *DeathPerception::MakeCopy()
{
    DeathPerception *p = new DeathPerception(name,who);
    return p;
}

void DeathPerception::ExecutePerception(NPC *npc,float weight)
{
    npc->RemoveFromHateList(who);
}

/// NPC Pet Perceptions ===========================================================
//---------------------------------------------------------------------------------

OwnerCmdPerception::OwnerCmdPerception( const char *name  ,
                                        int command,
                                        iCelEntity *owner ,
                                        iCelEntity *pet   ) : Perception(name)
{
    this->command = command;
    this->owner = owner;
    this->pet = pet;
}

bool OwnerCmdPerception::ShouldReact( Reaction *reaction, NPC *npc )
{
    csString event("ownercmd");
    event.Append(':');

    switch ( this->command )
    {
    case OwnerCmdPerception::OCP_FOLLOW: event.Append( "follow" );
        break;
    case OwnerCmdPerception::OCP_STAY : event.Append( "stay" );
        break;
    case OwnerCmdPerception::OCP_SUMMON : event.Append( "summon" );
        break;
    case OwnerCmdPerception::OCP_DISMISS : event.Append( "dismiss" );
        break;
    case OwnerCmdPerception::OCP_ATTACK : event.Append( "attack" );
        break;
    case OwnerCmdPerception::OCP_STOPATTACK : event.Append( "stopattack" );
        break;
    default: event.Append("unknown");
        break;
    }

    if (event == reaction->GetEventType())
    {
        CPrintf(CON_NOTIFY,"Matched reaction %s to perception %s...\n",reaction->GetEventType(), event.GetData() );
        return true;
    }
    return false;
}

Perception *OwnerCmdPerception::MakeCopy()
{
    OwnerCmdPerception *p = new OwnerCmdPerception( name, command, owner, pet );
    return p;
}

void OwnerCmdPerception::ExecutePerception( NPC *pet, float weight )
{
    switch ( this->command )
    {
    case OwnerCmdPerception::OCP_SUMMON : // Summon
        break;
    case OwnerCmdPerception::OCP_DISMISS : // Dismiss
        break;
    case OwnerCmdPerception::OCP_ATTACK : // Attack

        pet->AddToHateList(pet->GetTarget(), 1 * weight );
        break;

    case OwnerCmdPerception::OCP_STOPATTACK : // StopAttack
        break;
    }
}

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

OwnerActionPerception::OwnerActionPerception( const char *name  ,
                                              int action,
                                              iCelEntity *owner ,
                                              iCelEntity *pet   ) : Perception(name)
{
    this->action = action;
    this->owner = owner;
    this->pet = pet;
}

bool OwnerActionPerception::ShouldReact( Reaction *reaction, NPC *npc )
{
    csString event("ownercmd");
    event.Append(':');

    switch ( this->action )
    {
    case 1: event.Append("attack");
        break;
    case 2: event.Append("damage");
        break;
    case 3: event.Append("logon");
        break;
    case 4: event.Append("logoff");
        break;
    default: event.Append("unknown");
        break;
    }

    if (event == reaction->GetEventType())
    {
        CPrintf(CON_NOTIFY,"Matched reaction %s to perception %s...\n",reaction->GetEventType(), event.GetData() );
        return true;
    }
    return false;
}

Perception *OwnerActionPerception::MakeCopy()
{
    OwnerActionPerception *p = new OwnerActionPerception( name, action, owner, pet );
    return p;
}

void OwnerActionPerception::ExecutePerception( NPC *pet, float weight )
{
    switch ( this->action )
    {
    case 1: // Find and Set owner
        break;
    case 2: // Clear Owner and return to astral plane
        break;
    }
}

//---------------------------------------------------------------------------------
/// NPC Pet Perceptions ===========================================================
