#include "AnimatedEffectObject.h"

#include "Utils/Attachments.h"
#include "Utils/ParticleUtils.h"
#include "Utils/ObjectUtils.h"
#include "Utils/RangeFor.h"

#include "EventSystem/EventService.h"
#include "EventSystem/SystemEventTypes.h"

#include <Game.h>
#include <Events/RKEvent.h>
#include <Graphics/Model.h>

using namespace bne;
using namespace CasualCore;

AnimatedEffectObject::CreateCallback *AnimatedEffectObject::m_pCreateCallback = nullptr;

namespace
{
  static const uint32_t c_millisecondsInSecond = 1000;
}

namespace details
{
  struct YMatrix
  {
    YMatrix() { matrix.MakeRotationYMatrix(-RKPI/2); }
    operator RKMatrix() const { return matrix; }
    RKMatrix matrix;
  };
  static YMatrix s_pfxMatrix;
} // namespace details

SERIALIZE_BEGIN(RKAnimEvent)
SERIALIZERKLIST(m_desc.m_animationPFXs, AnimationPfxDesc, "Animation event PFX", "The PFX to spawn for each animation event", "Event", RKSERIAL_FLAG_RKLIST);
SERIALIZERKLIST(m_desc.m_animationObjects, AnimationObjectDesc, "Animation event objects", "The objects to spawn for each animation event", "Event", RKSERIAL_FLAG_RKLIST);
SERIALIZERKLIST(m_desc.m_animationSFXs, AnimationSfxDesc, "Animation event SFX", "The SFX to play for each animation event", "Event", RKSERIAL_FLAG_RKLIST);
SERIALIZE_END()


AnimatedEffectObject::AnimatedEffectObject()
{
  EventService::GetInstance()->AddReceiver(this, EventType_ObjectSelfDestroy);
}

AnimatedEffectObject::~AnimatedEffectObject()
{
  EventService::GetInstance()->RemoveReceiver(this);
#if defined(BNE_OBJECT_CROSSOVER)
  ClearData(false);
#else // (!)defined(BNE_OBJECT_CROSSOVER)
  Clear();
#endif // defined(BNE_OBJECT_CROSSOVER)
}

void AnimatedEffectObject::ReceiveEvent(int a_eventID, void* userData)
{
  void *pObject = userData;
  if(a_eventID == EventType_ObjectSelfDestroy)
  {
    // Check for object in child list
    for(size_t c = 0; c < m_animationEventObjects.Size(); c++)
    {
      const ChildObject<> &childObject = m_animationEventObjects.GetAt(c);
      if(childObject.Get() == pObject)
      {
        // If we found it in child list then remove child object from list (without destroying actual CasualCore::Object contained inside, ie. Don't call Clear(), as it is about to be destroyed by the sender of this event)
        m_animationEventObjects.EraseFastAt(c);
        break;
      }
    }
  }
}

#if defined(BNE_OBJECT_CROSSOVER)
void AnimatedEffectObject::ClearData(bool bDestroyChildObject) {
  // Destory any animation objects
  if(bDestroyChildObject)
  {
    for (ChildObject<>& cObject : m_animationEventObjects) {
      cObject.Clear();
    }
  }
  m_animationEventObjects.Clear();
#else // (!)defined(BNE_OBJECT_CROSSOVER)
void AnimatedEffectObject::Clear() {
  // Destory any animation objects
  for (ChildObject<>& cObject : m_animationEventObjects) {
    cObject.Clear();
  }
  m_animationEventObjects.Clear();
#endif // defined(BNE_OBJECT_CROSSOVER)

  // Stop any SFX
  for (SoundInfo& soundInfo : m_sfxInfo)
  {
    SOUND->Stop(soundInfo.m_handle);
  }
  m_sfxInfo.Clear();
}


void AnimatedEffectObject::SetParent(Object* a_pParent, PFXCallback *pCallback) {
  m_pParent = a_pParent;
  m_pParticleEffectCallback = pCallback;
  RKASSERT(pCallback, "NULL PFX Callback specified for AnimatedEffectObject");
}

void AnimatedEffectObject::SetEffects(const AnimationEventsDesc* a_pDesc) {
#if defined(BNE_OBJECT_CROSSOVER)
  ClearData(true);
#else // (!)defined(BNE_OBJECT_CROSSOVER)
  Clear();
#endif // defined(BNE_OBJECT_CROSSOVER)
  m_pDesc = a_pDesc;
}

void AnimatedEffectObject::Update(float a_elapsedTime) {
  if (m_pDesc == nullptr || m_pParent == nullptr) return;

  for (ChildObject<>& cObject : m_animationEventObjects) {
    Object* pObject = cObject.Get();
    if (pObject != nullptr && pObject->IsRenderable()) {
      for (const AnimationObjectDesc& objectEvent : m_pDesc->m_animationObjects) {
        if (pObject->GetName().StartsWith(objectEvent.m_objectTemplateName)) {
          RKMatrix m = GetAttachmentPointLocalTransform(m_pParent, objectEvent.AttachmentBoneName().GetString());
          pObject->SetTransform(m);
        }
      }
    }
  }

  // update SFX 3D location.
  const RKVector parentWorldPosition = m_pParent->GetWorldPosition();
  for (SoundInfo& soundInfo : m_sfxInfo) {
    SOUND->SetEmitterPosition(soundInfo.m_handle, parentWorldPosition.x, parentWorldPosition.y, parentWorldPosition.z);
  }
}

void AnimatedEffectObject::OnAnimEvent(const RKString* a_pHandle) {
  if (m_pDesc == nullptr) return;

  // loop over the SFX events
  for (const AnimationSfxDesc& sfxEvent : m_pDesc->m_animationSFXs)
  {
    RKEventObject* pEventObject = static_cast<RKEventObject*>(sfxEvent.GetEventObject());
    if (pEventObject != nullptr && pEventObject->m_EventString.ICompare(*a_pHandle) == 0)
    {
      // Retrieve sound handle for event
      int32_t infoIndex = -1;
      for (uint32_t i = 0; i < m_sfxInfo.Size(); ++i)
      {
        if (m_sfxInfo[i].m_sfxName.ICompare(sfxEvent.m_sfxName) == 0)
        {
          infoIndex = i;
          break;
        }
      }

      float fFadeTime = sfxEvent.m_fadeTime / static_cast<float>(c_millisecondsInSecond);

      if (sfxEvent.m_bPlay)
      {
        const bool bHasSoundHandle = (infoIndex != -1) && !SOUND->IsPlaying(m_sfxInfo[infoIndex].m_handle);

        // if applicable, set initial SFX 3D location.
        if (m_pParent)
        {
          const RKVector parentWorldPosition = m_pParent->GetWorldPosition();
          if (bHasSoundHandle)
          {
            SOUND->Play(m_sfxInfo[infoIndex].m_handle, parentWorldPosition.x, parentWorldPosition.y, parentWorldPosition.z, fFadeTime);
          }
          else
          {
            SoundHandle sfxHandle = SOUND->Play(sfxEvent.m_sfxName.GetString(), parentWorldPosition.x, parentWorldPosition.y, parentWorldPosition.z, fFadeTime);
            m_sfxInfo.Append(SoundInfo{ sfxEvent.m_sfxName, sfxHandle });
          }
        }
        else
        {
          if (bHasSoundHandle)
          {
            SOUND->Play(m_sfxInfo[infoIndex].m_handle, fFadeTime);
          }
          else
          {
            SoundHandle sfxHandle = SOUND->Play(sfxEvent.m_sfxName.GetString(), fFadeTime);
            m_sfxInfo.Append(SoundInfo{ sfxEvent.m_sfxName, sfxHandle });
          }
        }
      }
      else
      {
        if (infoIndex != -1)
        {
          SOUND->Stop(m_sfxInfo[infoIndex].m_handle, fFadeTime);
        }
      }
    }
  }

  // loop over the PFX events
  for (const AnimationPfxDesc& pfxEvent : m_pDesc->m_animationPFXs) {
    RKEventObject* pEventObject = static_cast<RKEventObject*>(pfxEvent.GetEventObject());
    if (pEventObject != nullptr && pEventObject->m_EventString.ICompare(*a_pHandle) == 0) {
      CreateEffect(pfxEvent.GetPFXName().GetString(), pfxEvent.AttachmentBoneName().GetString());
    }
  }

  // loop over the mesh events
  for (const AnimationObjectDesc& objectEvent : m_pDesc->m_animationObjects) {
    RKEventObject* pEventObject = static_cast<RKEventObject*>(objectEvent.GetEventObject());
    if (pEventObject != nullptr && pEventObject->m_EventString.ICompare(*a_pHandle) == 0) {
      if (objectEvent.IsShown()) {
        // show (mesh) object
        Object* pAffectedObject = nullptr;
        for (ChildObject<>& cObject : m_animationEventObjects) {
          Object* pObject = cObject.Get();
          if (pObject != nullptr && pObject->GetName().StartsWith(objectEvent.m_objectTemplateName)) {
            pAffectedObject = pObject;
            break;
          }
        }
        // if object is not found - recreate
        if (pAffectedObject == nullptr) {
          ChildObject<> cObject = CreateObject(objectEvent.ObjectTemplateName().GetString());
          pAffectedObject = cObject.Get();
          if (pAffectedObject != nullptr) {
            m_animationEventObjects.Append(cObject);
          }
        }
        if (pAffectedObject != nullptr) {
          pAffectedObject->SetRenderable(true, false);

          if (!objectEvent.AttachmentBoneName().IsEmpty())
          {
            RKMatrix m = GetAttachmentPointLocalTransform(m_pParent, objectEvent.AttachmentBoneName().GetString());
            pAffectedObject->SetTransform(m);
          }

          if (!objectEvent.m_objectAnimationName.IsEmpty() && pAffectedObject->GetRenderable() != nullptr) {
            Model* model = pAffectedObject->GetRenderable()->GetModel();
            if (model != nullptr) {
              model->PlayAnimationEx(objectEvent.m_objectAnimationName.GetString(), 1.0f);
            }
          }
        }
      } else {
        // hide (mesh) object
        for (ChildObject<>& cObject : m_animationEventObjects) {
          Object* pObject = cObject.Get();
          if (pObject != nullptr && pObject->GetName().StartsWith(objectEvent.m_objectTemplateName)) {
            pObject->SetRenderable(false, false);
            break;
          }
        }

      }
    }
  }
}

void AnimatedEffectObject::HideAnimationObjects() {
  for (ChildObject<>& cObject : m_animationEventObjects) {
    Object* pObject = cObject.Get();
    if (pObject != nullptr) {
      pObject->SetRenderable(false, false);
    }
  }
}

void AnimatedEffectObject::CreateEffect(const char* a_templateName, const char* a_attachmentBoneName) {
  if (m_pParent == nullptr) return;

  Object* effect = nullptr;
  if(m_pParticleEffectCallback)
  {
    effect = m_pParticleEffectCallback(a_templateName);
  }

  if (effect != nullptr) {
    effect->SetRenderable(true, false);
#if defined(BNE_OBJECT_CROSSOVER)
    bne::object::AttachToParent(effect, m_pParent, GetAttachmentPointIndex(m_pParent, a_attachmentBoneName), true);
#else // (!)defined(BNE_OBJECT_CROSSOVER)
    effect->SetParent(m_pParent, GetAttachmentPointIndex(m_pParent, a_attachmentBoneName), true);
#endif // defined(BNE_OBJECT_CROSSOVER)
    effect->SetTransform(details::s_pfxMatrix);
  }
  else {
    RKLOGt_ERROR("animEvent", "Failed to create PFX: '%s'", a_templateName);
  }

  // Run user callback on newly created object
  if(m_pCreateCallback && effect)
  {
    m_pCreateCallback(effect);
  }
}

ChildObject<> AnimatedEffectObject::CreateObject(const char* a_templateName) {
  ChildObject<> childObject{};
  if (m_pParent != nullptr) {
    if (!childObject.Create(m_pParent, a_templateName)) {
      RKLOGt_ERROR("animEvent", "Unable to create animation event object '%s'.", a_templateName);
    }
  }

  // Run user callback on newly created object
  if(m_pCreateCallback)
  {
    CasualCore::Object *pObject = childObject.Get();
    if(pObject)
    {
      m_pCreateCallback(pObject);
    }
  }

  return childObject;
}
