#include "grenade.hpp"

#include "main.hpp"
#include "playstate.hpp"
#include "stage.hpp"

// make the grenade switch teams by using the right move
#include "character/character.hpp"
#include "character/character_modelview.hpp"
#include "character/character_moves.hpp"

#include "kungflowutils.hpp"

EnemyGrenade::EnemyGrenade(const KungFlowGame *game, const PlayState *playState, e_Team team, const Vector2 &position, float zPosition, const Vector2 &movement, float zMovement) : Enemy(game, playState, e_EnemyType_Grenade, team, position, zPosition, movement, zMovement), physicsMode(e_PhysicsMode_Realtime) {
  //printf("grenaaade!\n");
  _Init();
}

EnemyGrenade::EnemyGrenade(const KungFlowGame *game, const PlayState *playState, e_Team team, const std::vector<Position3D> &trajectory) : Enemy(game, playState, e_EnemyType_Grenade, team, trajectory.at(0).position, trajectory.at(0).zPosition, Vector2(0), 0), physicsMode(e_PhysicsMode_Precalculated), trajectory(trajectory) {
  //Enemy(game, playState, e_EnemyType_Grenade, trajectory.at(0).position, trajectory.at(0).zPosition, 0, 0)
  assert(!trajectory.empty());
  _Init();
  //::EnemyGrenade(game, playState, e_EnemyType_Grenade, trajectory.at(0).position, trajectory.at(0).zPosition, Vector2(0), 0);
}

void EnemyGrenade::_Init() {
  bodySprite = new Sprite();
  //bodySprite->LoadFile("res/gfx/note.png", e_FilterMode_Nearest);
  bodySprite->LoadFile("res/gfx/circle.png", e_FilterMode_Nearest);
  bodyObject = new Object(&playState->GetWorldNode(), bodySprite);
  bodyObject->spatial.z = 0.0f;
  //bodyObject->spatial.scale = Vector2(0.5f, 0.5f);
  bodyObject->spatial.orientation = playState->GetViewOrientation();
  //bodyObject->spatial.position = position;
  timeSinceSpawn_ms = 0;
  lifeTime_ms = 600;

  // these may be overwritten by update functions
  intensity = 1.0f;
  radius = 0.2f;

  //hitPause_ms = 0;
  hit = false;
}

EnemyGrenade::~EnemyGrenade() {
  delete bodyObject;
  delete bodySprite;
}

/*
void EnemyGrenade::GetSpriteBounds(Vector2 &center, Vector2 &radius) const {
  center = position;
  radius = Vector2(0.2f);
  //radius = GetRadius();//bodyObject->spatial.scale * GetRadius();
  //Spatial tmpSpatial = bodyObject->GetDerivedSpatial();
  //center = tmpSpatial.position;
  //radius = tmpSpatial.scale * 0.5f;
}
*/

void EnemyGrenade::OnCollide(const StageEntity* entity) {

  // expired = true

  /*
  if (entity->GetType() == e_StageEntityType_Character) {
    //if (static_cast<Character*>(entity)->GetTeamID() == 1)
    static_cast<const Character*>(entity)->GetModelView()->AddHealthPoints(-1.0f);
    Vector2 hitDirection = movement.GetNormalized(_DEFAULTVECTOR);
    static_cast<const Character*>(entity)->GetModelView()->RequestHit(hitDirection, 1.0f);
  }
  */

  float colliderVelocityInfluence = 1.2f;
  float freeBaseBoost = 3.0f;//5.0f;//3.5f;

  float velocity = entity->GetMovement().GetLength() * colliderVelocityInfluence;
  Vector2 oppDirection = entity->GetDirection();
  Vector2 impactNormal = (GetPosition() - entity->GetPosition()).GetNormalized(Vector2(0, -1));
  Vector2 mirrorDirection = GetMovement().GetNormalized(_DEFAULTVECTOR).GetReflection(impactNormal);
  // reflectors: mirror vs impact direction
  float impactBias = 0.7f; // low == more impact reflect dir, high == more impact dir
  Vector2 reflectorsDirection = (mirrorDirection * (1.0f - impactBias) + impactNormal * impactBias);
  // opponent direction vs. reflectors
  float reflectorBias = 0.6f;
  queuedMovement = (oppDirection * (1.0f - reflectorBias) + reflectorsDirection * (reflectorBias) ) * (velocity + freeBaseBoost);

  float heightDelta = (entity->GetZPosition() + entity->GetHeight() * 0.5f) - (GetZPosition() + GetHeight() * 0.5f);
  float zIntensity = 2.0f;
  float oppZInfluenceBias = 0.2f;
  queuedZMovement = zMovement * (1.0f - oppZInfluenceBias) + (-heightDelta * zIntensity) * oppZInfluenceBias;

  if (!hit) {
    float hitIntensity = NormalizedClamp((GetMovement() + entity->GetMovement()).GetLength(), 0.0f, 10.0f);
    game->JoyRumble(0, hitIntensity * _RUMBLEFACTOR, 120);
    playState->SoundPlay(e_GlobalSound_SoundHit, hitIntensity * 0.3f + 0.7f);
  }

  hit = true;
  //hitPause_ms = 100; // a moment of innocence

  // giving it a good push makes the grenade switch teams
  if (entity->GetType() == e_StageEntityType_Character && entity->GetTeam() != GetTeam()) {
    const CharacterModelView *cmv = static_cast<const Character*>(entity)->GetModelView_const();
    const e_MoveType oppMoveType = cmv->GetActiveMoveType();
    if (oppMoveType == e_MoveType_Block || oppMoveType == e_MoveType_Dodge || (oppMoveType == e_MoveType_Jab && cmv->GetActiveMove()->IsLethal())) {
      SetTeam(GetTeam() == e_Team_GoodGuys ? e_Team_BadGuys : e_Team_GoodGuys);
    }
  }

  // end trajectory mode on collision
  if (physicsMode == e_PhysicsMode_Precalculated && hit) physicsMode = e_PhysicsMode_Realtime;

}

void EnemyGrenade::OnStepPhysicsTime() {

  //hitPause_ms = std::max(0.0, hitPause_ms - playState->GetPhysicsTimeStep_ms());

  // end trajectory!
  if (physicsMode == e_PhysicsMode_Precalculated && timeSinceSpawn_ms / playState->GetPhysicsTimeStep_ms() >= trajectory.size()) {
    physicsMode = e_PhysicsMode_Realtime;
    trajectory.clear();
  }

  if (physicsMode == e_PhysicsMode_Precalculated) {

    bool calculateTrajectoryMovement = true; // false == just don't set movement at all, only position

    // TRAJECTORY MODE
    unsigned int trajIndex = timeSinceSpawn_ms / playState->GetPhysicsTimeStep_ms();
    assert(trajIndex >= 0 && trajIndex < trajectory.size());
    const Position3D &position3D = trajectory.at(trajIndex);
    position = position3D.position;
    zPosition = position3D.zPosition;
    if (trajIndex > 0 && calculateTrajectoryMovement) {
      movement = (trajectory.at(trajIndex).position - trajectory.at(trajIndex - 1).position) / ((float)playState->GetPhysicsTimeStep_ms() * 0.001f);
      zMovement = (trajectory.at(trajIndex).zPosition - trajectory.at(trajIndex - 1).zPosition) / ((float)playState->GetPhysicsTimeStep_ms() * 0.001f);
    }

  } else {

    // PHYSICS MODE
    ApplyGravity(zMovement, 7.0f * _GRAVITYFACTOR, playState->GetPhysicsTimeStep_ms());
    //ApplyFriction(movement, 0.15f, playState->GetPhysicsTimeStep_ms());
    ApplyFriction(movement, 0.1f, playState->GetPhysicsTimeStep_ms());

  }

  bool outOfBounds = false;
  bool hitFloor = ApplyFloor(position, zPosition, zMovement, playState->GetStage()->GetStadium(), 0.7f, &outOfBounds);

  if (physicsMode == e_PhysicsMode_Precalculated && hitFloor) physicsMode = e_PhysicsMode_Realtime;

  if (physicsMode == e_PhysicsMode_Realtime) {
    position += movement * playState->GetPhysicsTimeStep_ms() * 0.001f * _PHYSICSSCALE;
    zPosition += zMovement * playState->GetPhysicsTimeStep_ms() * 0.001f * _PHYSICSSCALE;
  }

  if (timeSinceSpawn_ms >= lifeTime_ms || outOfBounds) {
    //this->movement.coords[1] = -this->movement.coords[1] * 0.7f;
    //this->position.coords[1] = 0.0f;
    Expire();
  }

  //if (position.coords[1] == 0.0f) expired = true;
  //if (position.coords[1] < -20) expired = true;

  timeSinceSpawn_ms += playState->GetPhysicsTimeStep_ms();

  queuedPosition = position;
  queuedZPosition = zPosition;
  queuedMovement = movement;
  queuedZMovement = zMovement;
}

void EnemyGrenade::PostStepPhysicsTime() {

  position = queuedPosition;
  zPosition = queuedZPosition;
  movement = queuedMovement;
  zMovement = queuedZMovement;

  bodyObject->spatial.position = position - zPosition;
  //bodyObject->spatial.position -= zPosition;

  float timeFactor = std::max(0.0f, 1.0f - (timeSinceSpawn_ms / (float)lifeTime_ms));
  intensity = pow(timeFactor, 0.8f);
  radius = 0.2f;// 0.2f + pow(1.0f - timeFactor, 4.0f) * 0.2f;
}

void EnemyGrenade::Draw(const Viewport &viewport, const Camera &camera) const {
  bodyObject->spatial.scale = Vector2(radius, radius);
  bodySprite->SetAlpha(1.0f * intensity);
  float lightRadius = 0.5f + (1.0f - intensity) * 0.5f; //1.2f
  //playState->GetStage()->DrawLight(GetPosition(), GetZPosition(), lightRadius, 0.2f * intensity, 0.3f * intensity, 0.5f * intensity);
  if (GetTeam() == e_Team_GoodGuys) {
    playState->GetStage()->DrawLight(GetPosition(), GetZPosition(), lightRadius, 1.2f * intensity, 0.3f * intensity, 0.2f * intensity);
  } else {
    playState->GetStage()->DrawLight(GetPosition(), GetZPosition(), lightRadius, 0.2f * intensity, 0.3f * intensity, 1.2f * intensity);
  }
  //playState->GetStage()->DrawShadow(GetPosition(), 1.0f);
  playState->GetStage()->DrawShadow(GetPosition(), GetZPosition(), 1.2f * radius, 1.0f * intensity);
  bodyObject->Draw(viewport, camera);
}

float EnemyGrenade::GetDamagePotentialFactor() const {
  return intensity;
}
