/******************************************************************
  
  Module:  rule.cpp
  
  Author: Sean Craig
                    
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "rule.h"
#include "rule_condition.h"
#include "titan.h"
#include "registry.h"
//---------------------------------------------- Class Declarations

//--------------------------------------------------------- Globals
static bool l_logEnabled=false;
static int  l_logIndent = 0;
static int  l_logHiddenLevel = 0; // indent level of hidden log
//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions

cRule::cRule() :
  mDisabled(false),
  mpTitan(NULL)
{
}

void 
cRule::ProcessPacket(cRulePacket *packet)
{
  SyAssertf(mpTitan != NULL, "Rule needs to have titan ptr set.");
  if (mDisabled)
  {
    return;
  }

  SyAssert(packet != NULL);
  SyAssertf(mpTitan != NULL,"Rules must be added to a rule container before being used");


  switch (packet->mPacketType)
  {
    case cRulePacket::CALC_HEALTH_MAX:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcHealthMax(calcPacket);
    }
    break;
    case cRulePacket::CALC_HEALTH_REGEN:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcHealthRegen(calcPacket);
    }
    break;
    case cRulePacket::CALC_MANA_MAX:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcPowerMax(calcPacket);
    }
    break;
    case cRulePacket::CALC_MANA_REGEN:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcPowerRegen(calcPacket);
    }
    break;
    case cRulePacket::CALC_BLOCK_MAX:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcBlockMax(calcPacket);
    }
    break;
    case cRulePacket::CALC_BLOCK_REGEN:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcBlockRegen(calcPacket);
    }
    break;
    case cRulePacket::CALC_STRIKETHROUGH:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcDamageRaw(damagePacket);
    }
    break;
    case cRulePacket::CALC_DAMAGE_INITIAL:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcDamageInitial(damagePacket);
    }
    break;
    case cRulePacket::CALC_DAMAGE_RAW:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcDamageRaw(damagePacket);
    }
    break;
    case cRulePacket::CALC_DAMAGE_REDUCED:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcDamageReduced(damagePacket);
    }
    break;
    case cRulePacket::CALC_CRITICAL_CHANCE:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcCriticalChance(damagePacket);
    }
    break;
    case cRulePacket::CALC_DEFENSE:
    { 
      cDamagePacket *damagePacket = (cDamagePacket*)packet;
      CalcDefense(damagePacket);
    }
    break;
    case cRulePacket::CALC_EXPERIENCE_GAIN:
    { 
      cCalcPacket *calcPacket = (cCalcPacket*)packet;
      CalcExperienceGain(calcPacket);
    }
    break;
    case cRulePacket::EVENT_HIT:
    { 
      cHitPacket *hitPacket = (cHitPacket*)packet;
      OnHit(hitPacket);
    }
    break;
    case cRulePacket::EVENT_APPLY_DAMAGE:
    { 
      cDamagePacket *damagePacket = (cDamagePacket *)packet;
      OnApplyDamage(damagePacket);
    }
    break;
    case cRulePacket::QUERY_FLAG:
    { 
      cFlagPacket *flagPacket = (cFlagPacket *)packet;
      QueryFlag(flagPacket);
    }
    break;
    default:
      SyAssert(0); // unknown packet
    break;
  }
}

void 
cRule::SetDisabled()
{
  mDisabled = true;
}
//------------------------------------ cRuleContainer

  
cRuleContainer::cRuleContainer() : cRule(),
  mOwnerID(ID_NONE)
{
};

cRuleContainer::~cRuleContainer()
{
  Clear();
}

void
cRuleContainer::SetOwnerID(tGameObjectID ownerID)
{
  mOwnerID = ownerID;
}

bool    
cRuleContainer::Add(cRule *rule)
{
  SyAssertf(mpTitan != NULL,"Must set pointer to titan before adding rules to container");

  // first, allow other rules to combine with the new rule (for example, same spell cast twice : some rules don't stack)
  for (int ii=0;ii<mRules.Size();++ii)
  {
    if (mRules(ii)->Combine(rule))
    {
      return false;
    }
  }
  rule->SetTitan(mpTitan);
  mRules.Add(rule);
  rule->OnAdded(mOwnerID);
  return true;
}

bool    
cRuleContainer::Remove(const char *name)
{
  bool retval = false;
  for (int ii=0;ii<mRules.Size();++ii)
  {
    if (SyStr::Stricmp(mRules(ii)->GetName(),name)==0)
    {
      mRules(ii)->SetDisabled(); // will actually get removed next update
      retval = true;
    }
  }
  return retval;
}

void    
cRuleContainer::Clear()
{
  while (mRules.Size() > 0)
  {
    delete mRules(mRules.Size()-1);
    mRules.Erase();
  }
}

void    
cRuleContainer::Update(float time)
{
  for (int ii=0;ii<mRules.Size();++ii)
  {
    mRules(ii)->Update(time);
  }
  ClearDisabled();
}

void
cRuleContainer::ClearDisabled()
{
  for (int ii=0;ii<mRules.Size();++ii)
  {
    if (mRules(ii)->IsDisabled())
    {
      mRules(ii)->OnRemoved(mOwnerID);
      delete mRules(ii);
      ii=mRules.ReplaceLast(ii);
      --ii;
    }
  }
}
 
void  
cRuleContainer::ProcessPacket(cRulePacket *packet)
{
  for (int ii=0;ii<mRules.Size();++ii)
  {
    mRules(ii)->ProcessPacket(packet);
  }
}

bool    
cRuleContainer::Contains(const char *name)
{
  for (int ii=0;ii<mRules.Size();++ii)
  {
    if (!mRules(ii)->IsDisabled() && SyStr::Stricmp(mRules(ii)->GetName(),name)==0)
    {
      return true;
    }
  }
  return false;
}


//-------------------------------------------------------------------------------------------- Global functions


void 
LogEnable(bool log)
{
  if (log == true && l_logEnabled == false)
  {
    static const char *filename = "log.txt";
    // clear out log file...
    FILE *logfile = fopen(filename,"w");
    fclose(logfile);
  }
  l_logEnabled = log;
}

bool 
IsLogEnabled()
{
  return l_logEnabled;
}

void 
Log(const char *format,...)
{
  if (l_logEnabled && l_logIndent < l_logHiddenLevel)
  {
    static const char *filename = "log.txt";
    // 
    FILE *logfile = fopen(filename,"a+");
    if (logfile != NULL)
    {
      va_list ap;
      va_start (ap,format);

      for (int ii=0;ii<l_logIndent;++ii)
      {
        fprintf(logfile,"\t");
      }
      vfprintf(logfile,format,ap);
      va_end(ap);
      fclose(logfile);
    }
  }
}

const char *l_PacketNames[] = 
{
  "CALC_HEALTH_MAX",
  "CALC_HEALTH_REGEN",
  "CALC_MANA_MAX",
  "CALC_MANA_REGEN",
  "CALC_DAMAGE_INITIAL", // called on Attacker
  "CALC_DAMAGE_RAW",     // called on Attacker
  "CALC_DAMAGE_REDUCED", // called on Defender
  "CALC_BLOCK_MAX",      // called on Defender
  "CALC_BLOCK_REGEN",
  "CALC_STRIKETHROUGH",  // called on Attacker

  "CALC_CRITICAL_CHANCE",

  "CALC_DEFENSE",
  "CALC_CARRYING_CAPACITY",
  "CALC_EXPERIENCE_GAIN",
  "QUERY_FLAG",

  "EVENT_HIT",               // hit someone else (called on Attacker)
  "EVENT_APPLY_DAMAGE"      // took damage (called on Defender)
};

#define NUM_PACKET_NAMES  (sizeof(l_PacketNames)/sizeof(l_PacketNames[0]))

void 
LogPacketStart(cRulePacket *packet, Titan *titan)
{

  SyAssertf(NUM_PACKET_NAMES == cRulePacket::NUM_PACKET_TYPES, "Forgot to name a packet type?");


  switch(packet->mPacketType)
  {
    // these are the packets we want to log
    case cRulePacket::CALC_DAMAGE_INITIAL:
    case cRulePacket::CALC_DAMAGE_RAW:
    case cRulePacket::CALC_DAMAGE_REDUCED:
    case cRulePacket::CALC_STRIKETHROUGH:
    case cRulePacket::CALC_DEFENSE:
      break;
    default:
    // don't log these packets
      l_logHiddenLevel = l_logIndent;
      ++l_logIndent;
      return;
  }
  
  if (l_logEnabled && l_logIndent < l_logHiddenLevel)
  {

    cGameObject *obj = titan->GetRegistry()->Fetch(packet->mObjectID);
    const char *name = "Unknown";
    if (obj != NULL && obj->GetName() != NULL && obj->GetName()[0] != '\0')
    {
      name = obj->GetName();
    }
    switch(packet->GetClass())
    {
    case cRulePacket::CLASS_BASE:
      Log("Enter %s: %s\n",l_PacketNames[packet->mPacketType],name);
      break;
    case cRulePacket::CLASS_CALC:
      Log("Enter %s: %s Initial Value: %d\n",l_PacketNames[packet->mPacketType],name,((cCalcPacket*)packet)->GetTotal());
      break;
    case cRulePacket::CLASS_HIT:
      {
        cGameObject *attacker = titan->GetRegistry()->Fetch(((cHitPacket*)packet)->mAttackerID);
        const char *attacker_name = "Unknown";
        if (attacker != NULL && attacker->GetName() != NULL && attacker->GetName()[0] != '\0')
        {
          attacker_name = attacker->GetName();
        }
        cGameObject *defender = titan->GetRegistry()->Fetch(((cHitPacket*)packet)->mDefenderID);
        const char *defender_name = "Unknown";
        if (defender != NULL && defender->GetName() != NULL && defender->GetName()[0] != '\0')
        {
          defender_name = defender->GetName();
        }
        Log("Enter %s: %s; %s attacks %s; Initial Value: %d\n",
            l_PacketNames[packet->mPacketType],name,attacker_name,defender_name,((cCalcPacket*)packet)->GetTotal());
      }

      break;
    case cRulePacket::CLASS_DAMAGE:
      {
        cGameObject *attacker = titan->GetRegistry()->Fetch(((cHitPacket*)packet)->mAttackerID);
        const char *attacker_name = "Unknown";
        if (attacker != NULL && attacker->GetName() != NULL && attacker->GetName()[0] != '\0')
        {
          attacker_name = attacker->GetName();
        }
        cGameObject *defender = titan->GetRegistry()->Fetch(((cHitPacket*)packet)->mDefenderID);
        const char *defender_name = "Unknown";
        if (defender != NULL && defender->GetName() != NULL && defender->GetName()[0] != '\0')
        {
          defender_name = defender->GetName();
        }
        Log("Enter %s: %s; %s attacks %s; Initial Value: %d\n",
            l_PacketNames[packet->mPacketType],name,attacker_name,defender_name,((cCalcPacket*)packet)->GetTotal());
      }
      break;
    case cRulePacket::CLASS_FLAG:
      {
      }
      break;
    default:
      SyAssertf(0,"Unknown packet type");
    }

  }
  ++l_logIndent;
}

void 
LogPacketEnd(cRulePacket *packet,Titan *titan)
{
  if (l_logEnabled)
  {
    --l_logIndent;
      
    if (l_logIndent < l_logHiddenLevel)
    {
      cGameObject *obj = titan->GetRegistry()->Fetch(packet->mObjectID);
      const char *name = "Unknown";
      if (obj != NULL && obj->GetName() != NULL && obj->GetName()[0] != '\0')
      {
        name = obj->GetName();
      }
      switch(packet->GetClass())
      {
      case cRulePacket::CLASS_BASE:
        Log("Exit %s: %s\n",l_PacketNames[packet->mPacketType],name);
        break;
      case cRulePacket::CLASS_CALC:
        Log("Exit %s: %s Total: %d\n",l_PacketNames[packet->mPacketType],name,((cCalcPacket*)packet)->GetTotal());
        break;
      case cRulePacket::CLASS_HIT:
        {
          Log("Exit %s: %s; Total %d\n",l_PacketNames[packet->mPacketType],name,((cCalcPacket*)packet)->GetTotal());
        }

        break;
      case cRulePacket::CLASS_DAMAGE:
        {
          Log("Exit %s: %s; Total: %d\n",l_PacketNames[packet->mPacketType],name,((cCalcPacket*)packet)->GetTotal());
        }
        break;
      case cRulePacket::CLASS_FLAG:
        {
        }
        break;
      default:
        SyAssertf(0,"Unknown packet type");
      }
    }
    if (l_logHiddenLevel == l_logIndent)
    {
      l_logHiddenLevel = 1000;
    }
  }
}

//------------------------------------------------------ cRuleSys
cRuleSys::cRuleSys()
{
}

cRuleSys::~cRuleSys()
{
  for (int i=mRules.Begin(); i!=mRules.End(); i=mRules.Next(i))
  {
    delete mRules(i);
  }
  mRules.Clear();
}

void cRuleSys::Init()
{
  Add(new cRule_Invisible());
  Add(new cRule_Invulnerable());
  Add(new cRule_PartialInvulnerability());
  Add(new cRule_Stunned());
}

cRule_Condition* cRuleSys::CreateCondition(const char* conditionName,
                                           int numParams,
                                           cell* pParams)
{
  tGameID conditionID = SyHashResourceID(conditionName);

  int index = mRules.Find(conditionID);
  
  SyAssertf(index != mRules.End(), "Condition name '%s' not found in rulesys", conditionName);

  if (index != mRules.End())
  {
    cRule_Condition* pRule = mRules(index);
    SyAssertf(pRule!=NULL, "Matching rule for condition name '%s is bad'", conditionName);

    if (pRule)
    {
      cRule_Condition* pNewRule = pRule->Clone();
      SyAssertf(pNewRule!=NULL && strcmp(pNewRule->GetName(), pRule->GetName())==0, "cRuleSys bad rule create for '%s' (no overloaded Clone() member?)", conditionName);

      if (pNewRule)
      {
        pNewRule->Init(numParams, pParams);
        return pNewRule;
      }
    }
  }

  return NULL;
}

void cRuleSys::Add(cRule_Condition* pRule)
{
  SyAssert(pRule!=NULL);

  if (!pRule)
  {
    return;
  }

  tGameID id = SyHashResourceID(pRule->GetName());
  mRules.Insert(id, pRule);
}



// EOF
