/******************************************************************
  
  Module:  aigoal_cast.cpp
  
  Author: Borut Pfeifer
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "aigoal_cast.h"

#include "../intel.h"
#include "../stats.h"
#include "../graphic.h"
#include "../registry.h"
#include "../titan.h"
#include "../areaeffect.h"

#include "aidebugdraw.h"
#include "aiblackboard.h"
#include "aigoal_attack.h"

//-------------------------------------------------------- cGoalCast
cGoal_Cast::cGoal_Cast()
: mTargetID(ID_NONE),
  mbCasting(false),
  mCastingTime(0.0f)
{
}

void cGoal_Cast::Enter()
{
  mbCasting = false;
  mCastingTime = 0.0f;

  if (RequiresTarget())
  {
    mTargetID = SelectTarget();
  }
  else
  {
    mTargetID = ID_NONE;
  }
}

cGameObject* cGoal_Cast::GetTarget()
{
  return GetRegistry()->Fetch(mTargetID);
}

bool cGoal_Cast::IsTargetValid()
{
  if (ID_NONE == mTargetID || 
      GetTarget() == NULL || 
      GetTarget()->GetStats()->IsDead() || 
      mpOwner->GetOwner()->GetStats()->IsDead())
  {
    return false;
  }

  SyAssert(prop_cast<cStatsCharacter*>(GetTarget()->GetStats())!=NULL);
  if (static_cast<cStatsCharacter*>(GetTarget()->GetStats())->HasCondition("Invisible"))
  {
    return false;
  }

  return true;
}

void cGoal_Cast::Update(float time)
{
  bool bRequiresTarget = RequiresTarget();

  if (bRequiresTarget && !IsTargetValid())
  {
    mTargetID = SelectTarget();
//    mpOwner->Pop(this);
//    return;
  }

  float speed = ((cStatsCharacter*)GetGameObject()->GetStats())->GetMovementSpeed();
  bool bDoneMoving = MoveToCastPosition(speed);

  if (bDoneMoving)
  {
    if (Cast(time) && bRequiresTarget) // only pop if this is a target based spell, otherwise we keep doing it
    {
      mpOwner->Pop(this);
      return;
    }
  }
}

void cGoal_Cast::Exit()
{
  mTargetID = ID_NONE;
}

//-------------------------------------------------------- cGoal_CastNearFriendlyTarget
cGoal_CastNearFriendlyTarget::cGoal_CastNearFriendlyTarget()
: cGoal_Cast()
{
}

bool cGoal_CastNearFriendlyTarget::MoveToCastPosition(float speed)
{
  static const float CAST_HEAL_DISTANCE_SQR = 4.0f * 4.0f;

  cGameObject* pSpellTarget = GetTarget();

  if (!pSpellTarget)
  {
    cGameObject* pAttackTarget = mpOwner->GetAttackTarget();
    if (!pAttackTarget)
    {
      pAttackTarget = GetIntel()->FindClosestObject(0.0f, cGoal_Ranged::GetFleeDistance(), true, true, false, false);
    }

    if (pAttackTarget && 
        (pAttackTarget->GetLocation().DistanceSquared(GetGameObject()->GetLocation())
         <= cGoal_Ranged::GetFleeDistance()*cGoal_Ranged::GetFleeDistance()))
    {
      if (!GetIntel()->IsAvoiding())
      {
        SyVect3 fleeDir;
        mpOwner->CalculateFleeDirection(pAttackTarget, fleeDir);
        GetIntel()->GoTo(GetGameObject()->GetLocation()+fleeDir, speed);
      }
      return false;
    }
    else
    {
      GetIntel()->Stop();
      return true;
    }
  }
  else if (pSpellTarget->GetLocation().DistanceSquared(GetGameObject()->GetLocation()) < 
           CAST_HEAL_DISTANCE_SQR)
  {
    GetIntel()->Stop();
    return true;
  }
  else
  {
    // todo add navigation around enemies in way to get to friendly
    GetIntel()->GoTo(pSpellTarget->GetLocation(), speed);
    return false;
  }
}

//-------------------------------------------------------- cGoal_CastAreaEffect
cGoal_CastAreaEffect::cGoal_CastAreaEffect()
: cGoal_Cast(),
  mSpellTargetLOS(),
  mSpellTargetPos(0.0f, 0.0f, 0.0f),
  mbHasSpellTarget(false),
  mTargetLOSWaitTime(0.0f)
{
}

void cGoal_CastAreaEffect::Enter()
{
  mSpellTargetLOS.Clear();
  mbHasSpellTarget = false;
  mTargetLOSWaitTime = 0.0f;
  cGoal_Cast::Enter();
}

bool cGoal_CastAreaEffect::IsValidTarget(cGameObject* pTarget)
{
  if (GetIntel()->IsTargetable(pTarget) &&
      GetGameObject()->GetDistance(pTarget) < 15.0f)
  {
    return true;
  }
  else
  { 
    return false;
  }
}

struct TestTargetPosition
{
  SyVect3 mCenter;
  int mNumTargets;
};

bool cGoal_CastAreaEffect::SelectTargetPosition(SyVect3& targetPos)
{
  float effectRadius = 5.0f; //todo, find this somewhere else
  float minRadius = effectRadius*0.707f; 
  cGameObjectRegistry* pRegistry = GetRegistry();
  SyAssert(pRegistry != NULL);

  cGameObject* pObj;
  int endReg = pRegistry->End();
  bool bMatchedTargetPos;


  std::vector<TestTargetPosition> targetPositions;
  for (int i=pRegistry->Begin(); i!=endReg; i=pRegistry->Next(i))
  {
    pObj = (*pRegistry)(i);

    if (!pObj || !IsValidTarget(pObj))
    {
      continue;
    }

    bMatchedTargetPos = false;

    if (targetPositions.size() > 0)
    {
      for (unsigned int i=0; i<targetPositions.size() && !bMatchedTargetPos; ++i)
      {
        if (targetPositions[i].mCenter.DistanceSquared(pObj->GetLocation()) <= minRadius*minRadius)
        {
          targetPositions[i].mCenter *= (float)(targetPositions[i].mNumTargets);
          targetPositions[i].mCenter += pObj->GetLocation();
          targetPositions[i].mCenter /= (float)(++targetPositions[i].mNumTargets);
          bMatchedTargetPos = true;
          break;
        }
      }
    }

    if (!bMatchedTargetPos)
    {
      TestTargetPosition t;
      t.mCenter = pObj->GetLocation();
      t.mNumTargets = 1;
      targetPositions.push_back(t);
    }
  }

  if (targetPositions.size() > 0)
  {
    targetPos = targetPositions[0].mCenter;
    return true;
  }

  return false;
}

void cGoal_CastAreaEffect::Update(float time)
{
  if (mbHasSpellTarget)
  {
    mSpellTargetLOS.Update(GetGameObject(), time);
    if (mSpellTargetLOS.mbUseTargetPos && !mSpellTargetLOS.CanSeeTarget())
    {
      mTargetLOSWaitTime += time;

      static const float CASTAREAEFFECT_MAX_LOS_WAITTIME = 5.0f;

      if (mTargetLOSWaitTime > CASTAREAEFFECT_MAX_LOS_WAITTIME)
      {
        mbHasSpellTarget = false;
        mSpellTargetLOS.Clear();
      }
    }
  }

  cGoal_Cast::Update(time);
}

bool cGoal_CastAreaEffect::MoveToCastPosition(float speed)
{
  cGameObject* pAttackTarget = mpOwner->GetAttackTarget();

  if (pAttackTarget && !pAttackTarget->GetStats()->IsDead())
  {
    if (GetGameObject()->GetDistance(pAttackTarget) < cGoal_Ranged::GetFleeDistance())
    {
      if (!GetIntel()->IsAvoiding())
      {
        SyVect3 fleeDir;
        mpOwner->CalculateFleeDirection(pAttackTarget, fleeDir);
        fleeDir *= 2.0f;
        GetIntel()->GoTo(GetGameObject()->GetLocation()+fleeDir, speed);
      }
      return false;
    }
  }

  if (mbHasSpellTarget)
  {
    if (mSpellTargetLOS.CanSeeTarget())
    {
      GetIntel()->Stop();
      return true; // will cause NPC to cast spell
    }
    else
    {
      GetIntel()->GoTo(mSpellTargetPos, speed);
      return false;
    }
  }
  else
  {
    mbHasSpellTarget = SelectTargetPosition(mSpellTargetPos);

    if (mbHasSpellTarget)
    {
      mSpellTargetPos.Y += 0.5f;
      mSpellTargetLOS.SetTargetPosition(mSpellTargetPos);
      mTargetLOSWaitTime = 0.0f;
    }

    return false;
  }
}

//-------------------------------------------------------- cGoal_CastExplosion
cGoal_CastExplosion::cGoal_CastExplosion()
: cGoal_CastAreaEffect(),
  mCastTimer(0.0f)
{
}

bool cGoal_CastExplosion::Cast(float time)
{
  mCastTimer += time;

  GetIntel()->TurnTo(mSpellTargetPos);

  if (mCastTimer < 4.0f)
  {
    return false;
  }

  mCastTimer = 0.0f;
  Titan* pTitan = GetGameObject()->GetTitan();

  cAreaEffect_Burst *pBurst = new cAreaEffect_Burst(pTitan);
  pBurst->SetSource(GetGameObject()->GetID());
  pBurst->SetMaxRadius(8.0f);
  pBurst->SetSpeed(20.0f);
  pBurst->SetLocation(GetGameObject()->GetLocation());

  cGameEffect_Damage *pEffect = new cGameEffect_Damage(pTitan);
  pEffect->SetSource(GetGameObject()->GetID());
  pEffect->SetDamage(50,NDT_NONE, MDT_FIRE);
  pBurst->AddGameEffect(pEffect);

  pTitan->GetAreaEffectSys()->Add(pBurst);

  mbHasSpellTarget = false;
  mSpellTargetLOS.Clear();

  return true;
}
//-------------------------------------------------------- cGoal_CastHeal
cGoal_CastHeal::cGoal_CastHeal()
{
}


tGameObjectID cGoal_CastHeal::SelectTarget()
{
  cGameObject *pClosestFriend = GetIntel()->FindClosestObject(0.0f, 25.0f, true, true, false, true, true);

  if (pClosestFriend)
  {
    return pClosestFriend->GetID();
  }

  return ID_NONE;
}


bool cGoal_CastHeal::Cast(float time)
{
  cGameObject* pTarget = GetTarget();

  if (!pTarget)
  {
    return false;
  }

  SyAssert(prop_cast<cStatsCharacter*>(pTarget->GetStats())!=NULL);
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(pTarget->GetStats());
  int heal = pStats->CalcMaxHealth()-pStats->GetHealth();
  heal = SY_MAX(0, heal);

  cDamagePacket damage;
  damage.mAttackerID = ID_NONE;
  damage.mDefenderID = GetGameObject()->GetID();
  damage.SetTotal(-heal, "AI Cast Heal", "");
  damage.mPacketType = cRulePacket::EVENT_HIT;
  pTarget->GetStats()->ApplyDamage(&damage);

  mTargetID = SelectTarget();
  return false;
}

cGoal_CastSummon::cGoal_CastSummon()
: mCastTimer(0.0f)
{
}

bool cGoal_CastSummon::MoveToCastPosition(float speed)
{
  cGameObject* pAttackTarget = mpOwner->GetAttackTarget();
  if (!pAttackTarget)
  {
    pAttackTarget = GetIntel()->FindClosestObject(0.0f, cGoal_Ranged::GetFleeDistance(), true, true, false, false);
  }

  if (pAttackTarget && 
      (pAttackTarget->GetLocation().DistanceSquared(GetGameObject()->GetLocation())
       <= cGoal_Ranged::GetFleeDistance()*cGoal_Ranged::GetFleeDistance()))
  {
    if (!GetIntel()->IsAvoiding())
    {
      SyVect3 fleeDir;
      mpOwner->CalculateFleeDirection(pAttackTarget, fleeDir);
      GetIntel()->GoTo(GetGameObject()->GetLocation()+fleeDir, speed);
    }
    return false;
  }

  GetIntel()->Stop();
  return true;
}

bool cGoal_CastSummon::Cast(float time)
{
  if (0.0f == mCastTimer)
  {
    cGameObjectRegistry* pRegistry = GetRegistry();

    tGameObjectID  spawnID = pRegistry->Create(cGameObject::OBJ_NPC);
    cGameObject *pSpawn = pRegistry->Fetch(spawnID);
    pSpawn->SetLocation(GetGameObject()->GetLocation()+SyVect3(1.0f, 0.0f, 0.0f));
    pSpawn->SetHPR(GetGameObject()->GetHPR());
    ((cStatsCharacter*)pSpawn->GetStats())->SetMaster("Large_Orc");
    pRegistry->InitObject(spawnID,false);
  }

  mCastTimer += time;

  static const float CAST_TIME = 10.0f;
  if (mCastTimer > CAST_TIME)
  {
    mCastTimer = 0.0f;
  }

  return false;
}



// EOF
