#include "grunt.hpp"

#include "gaia/gaia.hpp"
using namespace gaia;

//#include "defines.hpp"
#include "playstate.hpp"
#include "stage.hpp"
#include "tiles/tile.hpp"
#include "enemies/grenade.hpp"
#include "../character.hpp"
#include "../character_modelview.hpp"
#include "kungflowutils.hpp" // for quantization

GruntController::GruntController(const KungFlowGame *game, const PlayState *playState, Character *character, e_GruntType gruntType) : CharacterAIController(game, playState, character), gruntType(gruntType) {

  character->GetModelView()->AddMoveOwnership( e_MoveType_Jab  , new CharacterJabMove  (playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().jabStats) ); // todo: probably should put those default stats elsewhere
  character->GetModelView()->AddMoveOwnership( e_MoveType_Dodge, new CharacterDodgeMove(playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().dodgeStats) );
  character->GetModelView()->AddMoveOwnership( e_MoveType_Jump , new CharacterJumpMove (playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().jumpStats) );

  CharacterThrowMove *throwMove = new CharacterThrowMove(playState, character->GetModelView()->GetCharacterSprite(), character->GetModelView()->GetStats().throwStats); // todo: probably should put those default stats elsewhere
  character->GetModelView()->AddMoveOwnership( e_MoveType_Throw, throwMove );
  throwConnection = throwMove->sig_Throw.connect(boost::bind(&GruntController::OnThrow, this, _1, _2));

  basePosition = character->GetPosition();

}

GruntController::~GruntController() {
  throwConnection.disconnect();
}

void GruntController::OnStepPhysicsTime(CharacterModelView *characterModelView) {

  CharacterAIController::OnStepPhysicsTime(characterModelView);

/*
  if (_DISABLEFLOW == false) {
    // don't want good guy to pass us
    float flowLineStartInfluenceDistance = 6.0f;
    float flowLineFullInfluenceDistance = 2.0f;
    int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
    Vector2 flowMovement = playState->GetStage()->GetStadium()->GetFlowMovement();
    int toFlowLineDistance = playState->GetStage()->GetStadium()->GetFlowPosition().coords[flowAxis] - basePosition.coords[flowAxis];
    basePosition.coords[flowAxis] += (1.0f - NormalizedClamp(-toFlowLineDistance, flowLineFullInfluenceDistance, flowLineStartInfluenceDistance)) * (flowMovement.coords[flowAxis] * playState->GetPhysicsTimeStep_ms() * 0.001f);
  }
*/

  // some base vars

  bool dummyMode = false;
  //float skillFactor = 1.0f * _AI_SKILLFACTOR;
  float activationDistance = 2.0f;//2.0f; // from this distance and smaller, get busy
  float futureFactor = 0.25f; // aim how far in the future?
  float rotateRadius = 1.4f;//1.7f;
  float maxDeviation = 1.5f;//1.7f;
  float rotateSpeed = 1.5f;//3.0f;
  float phaseVariance = 2.0f;//2.5f;
  float backTrackIntensity = 0.2f;


  // don't want enemy to pass us -- retreat a little when needed

  float flowLineStartInfluenceDistance = 4.0f;
  float flowLineFullInfluenceDistance = -10.0f;
  if (_DISABLEFLOW == false) {
    flowLineStartInfluenceDistance = 5.0f;
    flowLineFullInfluenceDistance = 1.0f;
  }
  int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();
  int toFlowLineDistance = playState->GetStage()->GetStadium()->GetFlowPosition().coords[flowAxis] - basePosition.coords[flowAxis];
  basePosition.coords[flowAxis] += (1.0f - NormalizedClamp(-toFlowLineDistance, flowLineFullInfluenceDistance, flowLineStartInfluenceDistance)) * (playState->GetMediumVelocity() * playState->GetPhysicsTimeStep_ms() * 0.001f) * backTrackIntensity;


  // cache, for simplicity

  Vector2 flowMovement = playState->GetStage()->GetStadium()->GetFlowMovement();
  Vector2 position = character->GetPosition();
  //int flowAxis = playState->GetStage()->GetStadium()->GetFlowAxis();

  //if (position.coords[flowAxis] < playState->GetStage()->GetStadium()->GetFlowPosition().coords[flowAxis]) character->Expire();

  if (!assignedOpponent) {
    characterModelView->SetDesiredVelocity(0);
    characterModelView->SetDesiredBodyAngle((-playState->GetStage()->GetStadium()->GetFlowDirection()).GetAngle());
    return;
  }

//  float q = basePosition.coords[0] * phaseVariance; // sin/cos phase offset
//  float r = basePosition.coords[1] * phaseVariance;
  float q = character->GetUniqueID() * phaseVariance; // sin/cos phase offset
  float r = character->GetUniqueID() * 2 * phaseVariance;
  Vector2 adaptedBasePosition = basePosition + Vector2( sin(q + game->GetPhysicsTime_ms() * 0.001f * rotateSpeed) , cos(r + game->GetPhysicsTime_ms() * 0.001f * rotateSpeed) ) * rotateRadius;
  Vector2 assignedOpponentPosition = assignedOpponent->GetPosition();
  float assignedOpponentZPosition = assignedOpponent->GetZPosition();
  Vector2 toAssignedOpponent = assignedOpponentPosition - position;
  Vector2 assignedOpponentFuturePosition = assignedOpponent->GetPosition() + flowMovement * toAssignedOpponent.GetLength() * futureFactor;
  Vector2 toAssignedOpponentFuture = assignedOpponentFuturePosition - position;
  toAssignedOpponentFuture = toAssignedOpponentFuture.EnforceMaximumDeviation(adaptedBasePosition - position, maxDeviation);

  if ((toAssignedOpponentFuture * playState->GetStage()->GetStadium()->GetFlowDirection()).GetLength() > activationDistance) {
    Vector2 toBasePosition = adaptedBasePosition - position;
    characterModelView->SetDesiredDirection(toBasePosition.GetNormalized(_DEFAULTVECTOR));
    characterModelView->SetDesiredVelocity(std::min(toBasePosition.GetLength(), playState->GetMediumVelocity()));
    //characterModelView->SetDesiredBodyAngle(toBasePosition.GetNormalized(_DEFAULTVECTOR).GetAngle());
    characterModelView->SetDesiredBodyAngle(toAssignedOpponent.GetNormalized(_DEFAULTVECTOR).GetAngle());
    return;
  }

  Vector2 targetMovement;

  switch (gruntType) {

    case e_GruntType_Rusher:

      targetMovement = toAssignedOpponentFuture * 2.0f * (0.2f + 0.8f * _AI_SKILLFACTOR);//.GetNormalizedMax(playState->GetMediumVelocity());
      break;

    case e_GruntType_Defender:

      break;

    default:
      break;

  }

  //Vector2 flattenVec = Vector2(1, 1);
  //flattenVec.coords[playState->GetStage()->GetStadium()->GetFlowAxis()] *= 0.7f; // less expressive forwards/backwards movement
  //targetMovement *= flattenVec;
  targetMovement = GetQuantizedDirection(targetMovement, playState->GetQuantizationFactor());
  characterModelView->SetDesiredDirection(targetMovement.GetNormalized(_DEFAULTVECTOR));//.GetNormalizedMax(1.0f));
  characterModelView->SetDesiredVelocity(std::min(targetMovement.GetLength(), playState->GetMediumVelocity()));
  characterModelView->SetDesiredBodyAngle(toAssignedOpponent.GetNormalized(_DEFAULTVECTOR).GetAngle());

/*
  float walkPow = 0.8f; // lower: more active / lively. higher: more apathic / lazy
  walkPow *= 0.5f + 0.5f * NormalizedClamp(targetMovement.GetLength(), 2.5f, 6.0f); // farther away characters get progressively more relaxed
  //printf("%f\n", forceFieldMovement.GetLength());
  characterModelView->SetDesiredVelocity(pow(std::min(targetMovement.GetLength(), 1.0f), walkPow) * playState->GetMediumVelocity());
*/

  if (dummyMode) return;

  // attack

  float jabThresholdDistance = 2.0f;
  float throwThresholdDistance = 6.0f;
  Vector2 toAssignedOpponentTranslation = toAssignedOpponent + flowMovement * 0.2f;
  Vector2 desiredJabDirection = toAssignedOpponentTranslation.GetNormalized(0);
  Vector2 desiredThrowTranslation = toAssignedOpponent + playState->GetStage()->GetStadium()->GetFlowDirection() * 2.0f;

  float throwFrequencyFactor = 0.0f;//1.0f;

  // only if enemy close enough
  if (toAssignedOpponentTranslation.GetLength() < jabThresholdDistance && fabs(character->GetZPosition() - assignedOpponentZPosition) < 0.5f) {// && Random(0, 100) < 1.0f) {
    characterModelView->RequestJab(desiredJabDirection, Clamp(toAssignedOpponentTranslation.GetLength() / jabThresholdDistance, 0.0f, 1.0f));
  } else if (toAssignedOpponentTranslation.GetLength() < throwThresholdDistance && fabs(character->GetZPosition() - assignedOpponentZPosition) < 0.5f) {
    if (nthClosestToAssignedOpponent < 2 && Random(0.0f, 1.0f / (float)game->GetPhysicsTimeStep_ms()) < .003f * throwFrequencyFactor) {
      characterModelView->RequestThrow(character->GetPosition() + desiredThrowTranslation); //autoKickTargetPosition
    }
  }

}

void GruntController::OnThrow(const Vector2 &desiredMovement, const Vector2 &toTargetTranslation) {

  // physics version
  Vector2 throwDirection = toTargetTranslation.GetNormalized(playState->GetStage()->GetStadium()->GetFlowDirection());
  float throwPower = 4.0f;
  float safeDistance = (character->GetRadius() + 0.2f);

/*
  // single shot
  playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, e_Team_BadGuys, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 0.7f, throwDirection * throwPower, 1.5f);
  playState->SoundPlay(e_GlobalSound_SoundBlast, 0.4f);
*/

  //float throwPower = NormalizedClamp(toTargetTranslation.GetLength(), 0, character->GetModelView_const()->GetStats().throwStats.maxDistance);
  float distanceBeforeTarget = 0.8f; // aim before player, so they can properly hit it

  Vector2 startPosition = character->GetPosition() + _DEFAULTVECTOR.GetRotated(character->GetModelView_const()->GetBodyAngle()) * character->GetRadius() * 1.3f;
  float startZPosition = character->GetZPosition() + character->GetHeight() * 1.2f;
  Vector2 targetPosition = character->GetPosition() - character->GetDirection() * 0.35f + toTargetTranslation; // corrects for throw move translation
  targetPosition += -toTargetTranslation.GetNormalized(0) * distanceBeforeTarget;
  float targetZPosition = character->GetZPosition() + character->GetHeight() * 0.5f;
  if (Random(0, 1) < 0.1f) targetZPosition += int(floor(Random(0.0f, 3.0f) * 0.7f)) * 1.2f; // height levels: ground, jump, double jump
  //float unroundedNumBeats = std::max(1.0f, startPosition.GetDistance(targetPosition) * 0.2f);
  //int numBeats = std::max(1, (int)(startPosition.GetDistance(targetPosition) * 0.2f) );
  //float remainder = unroundedNumBeats - numBeats;
  float numBeatsUnrounded = startPosition.GetDistance(targetPosition) * 0.4f;
  float numBeats = round(startPosition.GetDistance(targetPosition) * 0.4f);
  float remainder = numBeatsUnrounded - numBeats; // lower value for remainder == slower ball (than intended) == make arc higher to make up for that
  //printf("%f\n", remainder);
  float arcFactor = 0.25f - remainder * 0.45f;
  //arcFactor *= 0.6f; // shallow arc
  numBeats += 1.0f - playState->GetCurrentMusic()->GetBeatOffset(); // what's left of the current beat
  int duration_ms = std::max(1.5f, numBeats) * playState->GetCurrentMusic()->GetBeatDuration_ms();
  std::vector<Position3D> trajectory;
  CalculateTrajectory(startPosition, startZPosition, targetPosition, targetZPosition, arcFactor, duration_ms / playState->GetPhysicsTimeStep_ms(), trajectory);

  EnemyGrenade *enemyGrenade = new EnemyGrenade(game, playState, e_Team_BadGuys, trajectory);
  playState->GetStage()->AddEntityOwnership(enemyGrenade);
  playState->SoundPlay(e_GlobalSound_SoundBlast, 0.4f);

  return;


  // hellfire!
  //radian spreadAngle = 0.125f * pi;
  //float step = spreadAngle / 4;
  radian spreadAngle = 0.05f * pi;
  float step = spreadAngle / 2;
/*
  // shooting while aiming backwards
  if (desiredMovement.coords[playState->GetStage()->GetStadium()->GetFlowAxis()] < -0.25f) {
    spreadAngle = 1.0f * pi;
    step = spreadAngle / 4;
  }
*/
  //if (playState->GetCurrentMusic()->GetBeat() % 2 == 0) spreadAngle = 0;
  for (radian rot = -spreadAngle * 0.5f; rot <= spreadAngle * 0.5f; rot += step) {
    float zMovement = -1.0;

    const Tile *tile = playState->GetStage()->GetStadium()->GetTile(character->GetPosition());
    if (tile) {
      zMovement -= NormalizedClamp(character->GetZPosition() - tile->GetZPosition(), 0.0f, 2.0f) * 8.0f;
    }
    playState->GetStage()->SpawnEnemy(e_EnemyType_Grenade, e_Team_BadGuys, character->GetPosition() + throwDirection * safeDistance, character->GetZPosition() + character->GetHeight() * 0.7f, throwDirection.GetRotated(rot) * throwPower, zMovement);
    //entity->SetPosition(physicsState.position + shotDirection * (GetRadius() + entity->GetRadius())); // make sure there's no collision with body
  }

  playState->SoundPlay(e_GlobalSound_SoundBlast, 0.4f);

}
