#include "karateka.hpp"

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

//#include "defines.hpp"
#include "playstate.hpp"
#include "stage.hpp"
#include "spaces/space.hpp" // for tile
#include "tiles/tile.hpp"
#include "kungflowutils.hpp" // forcefields etc

#include "../character.hpp"
#include "../character_modelview.hpp"

KaratekaController::KaratekaController(const KungFlowGame *game, const PlayState *playState, Character *character) : CharacterAIController(game, playState, character) {

  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) );

}

KaratekaController::~KaratekaController() {
}

void KaratekaController::OnStepPhysicsTime(CharacterModelView *characterModelView) {

  CharacterAIController::OnStepPhysicsTime(characterModelView);

  //if (game->GetPhysicsTimeStepsDone() % 100 != 0) return;

//  move only on the beat
//  float powerFactor = cos(playState->GetCurrentMusic()->GetBeatOffset() * 2 * pi) * 0.5f + 0.5f;
//  powerFactor = 0.3f * powerFactor + 0.7f * (1.0f - playState->GetCurrentMusic()->GetBeatOffset());
//  if (powerFactor < 0.95f) return;


  // some base vars

  bool dummyMode = false;
  float skillFactor = 8.0f * _AI_SKILLFACTOR;//10.0f;
  float dodgeFrequencyFactor = 1.0f * skillFactor;
  float jabFrequencyFactor = 1.0f * skillFactor;
  // base distances to opponent
  float closestDistanceFactor = 0.7f; // the main fighter atm
  float topXClosestDistanceFactor = 0.8f; // closest enemies stay close
  float waitInLineDistanceFactor = 1.0f; // queued fighters.. waiting..
  float lowPrioEnemyDistanceFactor = 1.0f; // don't get too close to the other enemies..
  //int opponentTeamID = closestOpponent->GetTeamID();

  Vector2 position = characterModelView->GetPosition();

  if (!assignedOpponent) {
    characterModelView->SetDesiredVelocity(0);
    return;
  }
  //float historyFactor = 0.1f; // reaction time
  Vector2 assignedOpponentPosition = assignedOpponent->GetPosition();// + playState->GetStage()->GetStadium()->GetFlowDirection() * 1;
  Vector2 toAssignedOpponent = assignedOpponentPosition - position;// - opponent->GetMovement() * historyFactor;
  characterModelView->SetDesiredBodyAngle(toAssignedOpponent.GetNormalized(_DEFAULTVECTOR).GetAngle());

  //float futureFactor = 0.1f; // prediction
  //Vector2 opponentFuturePos = opponent->GetPosition() + opponent->GetMovement() * futureFactor;
  //Vector2 toOpponentFuture = opponentFuturePos - position;

  //if (playState->GetStage()->GetClosestMate(opponentFuturePos)->GetController() == this) isClosestToOpponent = true;


  // complex position management using force fields

  std::vector<ForceSpot> forceField;

  Vector2 averageOpponentPosition;
  for (unsigned int i = 0; i < assignedOpponents.size(); i++) {
    averageOpponentPosition += assignedOpponents.at(i)->GetPosition();
  }
  if (assignedOpponents.size() > 0) {
    averageOpponentPosition /= (float)assignedOpponents.size();

    // drive a wedge between the opponents (by aiming in between them)

    ForceSpot wedgeSpot;
    wedgeSpot.origin = averageOpponentPosition;// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
    wedgeSpot.magnetType = e_MagnetType_Attract;
    wedgeSpot.decayType = e_DecayType_Constant;
    wedgeSpot.power = 0.05f;
    forceField.push_back(wedgeSpot);
  }

  // don't fall!
  float maxFallHeight = 2.0f;
  ForceSpot dontFallSpot;
  dontFallSpot.magnetType = e_MagnetType_Repel;
  dontFallSpot.decayType = e_DecayType_Variable;
  dontFallSpot.exp = 1.0f;
  dontFallSpot.power = 2.0f;
  dontFallSpot.scale = 3.0f;
  for (unsigned int i = 0; i < 4; i++) {
    Vector2 offset;
    if (i == 0) offset = Vector2(-1,  0);
    if (i == 1) offset = Vector2( 1,  0);
    if (i == 2) offset = Vector2( 0,  1);
    if (i == 3) offset = Vector2( 0, -1);
    const Tile *tile = playState->GetStage()->GetStadium()->GetTile(position + offset);
    if (tile && fabs(tile->GetZPosition() - character->GetZPosition()) > maxFallHeight) {
      dontFallSpot.origin = tile->GetPosition();
      forceField.push_back(dontFallSpot);
    }
  }


  // TEAM

  // constant attraction towards our human protagonist buddies (keep the human players the center of all action)
  float maxDistanceToHuman = 8.0f;
  const std::vector<Character*> &humans = playState->GetStage()->GetHumanCharactersRef();
  for (unsigned int i = 0; i < humans.size(); i++) {
    if (character->GetTeam() == humans.at(i)->GetTeam()) {

      // team mates are drawn to humans more
      ForceSpot spot;
      spot.origin = humans.at(i)->GetPosition();// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
      spot.magnetType = e_MagnetType_Attract;
      spot.decayType = e_DecayType_Constant;
      spot.power = 0.4f / (float)humans.size();
      if (humans.at(i)->GetPosition().GetDistance(position) > maxDistanceToHuman) spot.power = 1.0f;
      forceField.push_back(spot);

      // ..but not too close
      spot.origin = humans.at(i)->GetPosition();// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
      spot.magnetType = e_MagnetType_Repel;
      spot.decayType = e_DecayType_Variable;
      spot.exp = 0.6f;
      spot.power = 0.8f;
      spot.scale = 3.3f;//2.2f * _PHYSICSSCALE;
      forceField.push_back(spot);

    } else {
      /*
      // even all opponents are more drawn to humans specifically
      ForceSpot spot;
      spot.origin = humans.at(i)->GetPosition();// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
      spot.magnetType = e_MagnetType_Attract;
      spot.decayType = e_DecayType_Constant;
      spot.power = 0.1f / (float)humans.size();
      forceField.push_back(spot);
      */
    }

  }


  // enemy mechanics

  //if (character->GetTeamID() == 1) playState->DrawDebugLine(character->GetPosition(), closestOpponent->GetPosition(), e_CoordinateSystem_World, 0.6f, 1.0f, 0.2f, 0.1f);

  for (unsigned int i = 0; i < assignedOpponents.size(); i++) {
/*
    // constant attraction towards the opponents
    ForceSpot spot;
    spot.origin = opponents.at(i)->GetPosition();// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
    spot.magnetType = e_MagnetType_Attract;
    spot.decayType = e_DecayType_Constant;
    spot.power = 0.3f / (float)opponents.size();
    forceField.push_back(spot);
*/
    // repel when too close
    ForceSpot spot;
    spot.origin = assignedOpponents.at(i)->GetPosition() + assignedOpponents.at(i)->GetMovement().GetNormalizedMax(4.0f) * 0.2f;
    spot.magnetType = e_MagnetType_Repel;
    spot.decayType = e_DecayType_Variable;
    //spot.exp = 1.2f;//0.5f;//0.7f;
    //spot.scale = 4.5f * distanceFactor;//6.75f * _PHYSICSSCALE;//3.8f * _PHYSICSSCALE;
    if (assignedOpponents.at(i) == assignedOpponent) { // if this is our main opponent, maybe get closer

     if (nthClosestToAssignedOpponent < GetFightingCharacterCount()) {

        float baseDistance = 0.4f * closestDistanceFactor; // amount of base distance we (try to) keep from our main opponent
        float waveAmp = 0.8f; // max offset distance added to that, we usually stay at max offset, but jump closer sometimes, in waves
        float excursion = sin(game->GetPhysicsTime_ms() * 0.00213f + randomSeed * 3) * 0.5f + 0.5f;
        excursion = Clamp(excursion, 0.0f, 1.0f);
        excursion = pow(excursion, 0.3f);
        excursion = Curve(excursion, 1.0f);
        excursion = Curve(excursion, 1.0f);

        spot.exp = 0.5f;
        spot.power = 1.0f;
        spot.scale = (baseDistance + excursion * waveAmp) * _ATTACKDISTANCEFACTOR;// * _PHYSICSSCALE;//1.2f * _PHYSICSSCALE;
        forceField.push_back(spot);
        //spot.scale = 0.01f * _PHYSICSSCALE;
        //spot.scale = 1.2f * _PHYSICSSCALE;//1.2f * _PHYSICSSCALE;
        //if (this->character->GetTeamID() == 0) printf("%f\n", spot.scale);
        if (_DEBUGVISUALS) if (character->GetTeam() == e_Team_BadGuys) playState->DrawDebugLine(character->GetPosition(), spot.origin, e_CoordinateSystem_World, 0.8f, 1.0f, 0.6f, 0.08f);

     } else if (nthClosestToAssignedOpponent < GetFightingCharacterCount() + GetQueuedCharacterCount()) {

        spot.exp = 0.8f;
        spot.power = 1.0f;
        spot.scale = 2.6f * topXClosestDistanceFactor * (0.2f + 0.8f * _ATTACKDISTANCEFACTOR);//3.9f * _PHYSICSSCALE;//2.0f * _PHYSICSSCALE;
        forceField.push_back(spot);
        if (_DEBUGVISUALS) if (character->GetTeam() == e_Team_BadGuys) playState->DrawDebugLine(character->GetPosition(), spot.origin, e_CoordinateSystem_World, 0.7f, 0.8f, 0.6f, 0.03f);

      } else { // just one of the guys on hold

        spot.exp = 1.5f;
        spot.power = 1.0f;
        spot.scale = 6.0f * waitInLineDistanceFactor * (0.4f + 0.6f * _ATTACKDISTANCEFACTOR);
        forceField.push_back(spot);

      }

    } else {

      // other (unfocussed) enemies. ignore them, but stay out of their way somewhat.
      //if (closestOpponents.at(i) != closestOpponent) {
      spot.exp = 3.0f; // not so dangerous, just loosely try to avoid
      spot.power = 0.2f;
      spot.scale = 4.5f * lowPrioEnemyDistanceFactor;
      forceField.push_back(spot);
      //if (character->GetTeamID() == 1) playState->DrawDebugLine(character->GetPosition(), spot.origin, e_CoordinateSystem_World, 1.0f, 0.5f, 1.0f, 0.1f);
    }

  }


  // constant attraction towards our main opponent

  ForceSpot spot;
  spot.origin = assignedOpponentPosition + playState->GetStage()->GetStadium()->GetFlowDirection() * 5;// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
  spot.magnetType = e_MagnetType_Attract;
  spot.decayType = e_DecayType_Constant;
  spot.power = 0.25f;
  forceField.push_back(spot);


  // rotate around main opponent (as tactic, keep on movin')

  ForceSpot rotateSpot;
  float spinniness = 0.9f;
  float spinVariety = 0.8f;
  if (nthClosestToAssignedOpponent < GetFightingCharacterCount()) {
    spinniness = 1.0f;//0.9f;
    spinVariety = 1.5f;//1.2f;
  }
  signed int side = round((sin(game->GetPhysicsTime_ms() * 0.001643f * spinVariety + randomSeed) * 0.5f + sin(game->GetPhysicsTime_ms() * 0.001f * spinVariety + randomSeed * 2) * 0.5f) * spinniness);
  //printf("%i\n", side);
  rotateSpot.origin = position + (position - assignedOpponentPosition).GetNormalized(0).GetRotated(0.5f * pi) * side;
  rotateSpot.magnetType = e_MagnetType_Attract;
  rotateSpot.decayType = e_DecayType_Constant;
  rotateSpot.power = (side == 0) ? 0.0f : 0.4f; // if sine is halfway, no effect
  if (nthClosestToAssignedOpponent >= GetFightingCharacterCount()) rotateSpot.power *= 0.5f;
  if (rotateSpot.power > 0.0f) forceField.push_back(rotateSpot);


  // teammate web

  for (unsigned int i = 0; i < closestMates.size(); i++) {

    if (closestMates.at(i)->GetController() != this) {

      // constant attraction towards our team mates
      /* DISABLED, too flocky
      ForceSpot spot;
      spot.origin = mates.at(i)->GetPosition();
      spot.magnetType = e_MagnetType_Attract;
      spot.decayType = e_DecayType_Constant;
      spot.power = 0.05f;
      forceField.push_back(spot);
      */

      // repel when too close
      ForceSpot spot;
      spot.origin = closestMates.at(i)->GetPosition();
      spot.magnetType = e_MagnetType_Repel;
      spot.decayType = e_DecayType_Variable;
      spot.exp = 1.4f;//0.8f;
      spot.power = 0.3f;
      spot.scale = 2.7f;//1.8f * _PHYSICSSCALE;//1.2f * _PHYSICSSCALE;
      forceField.push_back(spot);

    }

  }

/*
  // attract to base position (very slightly)
  ForceSpot spot;
  spot.origin = characterModelView->GetBasePosition();
  spot.magnetType = e_MagnetType_Attract;
  spot.decayType = e_DecayType_Constant;
  //spot.exp = 1.0f;
  spot.power = 0.01f;
  //spot.scale = 5.0f;
  forceField.push_back(spot);
*/

/*
  // constant drive to flag
  ForceSpot flagSpot;
  flagSpot.origin = Vector2(playState->GetStage()->GetStadium()->GetDimensions()[0] / 2, -10.0f);// + opponents.at(i)->GetPosition().GetNormalized(0) * 0.4f;
  flagSpot.magnetType = e_MagnetType_Attract;
  flagSpot.decayType = e_DecayType_Constant;
  flagSpot.power = 0.1f;//0.25f;
  forceField.push_back(flagSpot);
*/

  Vector2 forceFieldMovement = GetForceFieldMovement(forceField, position);// + characterModelView->GetMovement() * 0.02f);

  // 8 way!
  forceFieldMovement = GetQuantizedDirection(forceFieldMovement, playState->GetQuantizationFactor());

  characterModelView->SetDesiredDirection(forceFieldMovement.GetNormalized(0));//.GetNormalizedMax(1.0f));

  //float walkPow = 0.6f; // lower: more active / lively. higher: more apathic / lazy
  //walkPow += NormalizedClamp(toClosestOpponent.GetLength(), 1.5f * _PHYSICSSCALE, 4.0f * _PHYSICSSCALE) * 1.0f; // farther away characters get progressively more relaxed

  float walkPow = 0.8f; // lower: more active / lively. higher: more apathic / lazy
  //if (nthClosestToAssignedOpponent < GetFightingCharacterCount()) walkPow *= 0.7f;//0.5f; // lower: more active / lively. higher: more apathic / lazy
  //else if (nthClosestToAssignedOpponent < GetFightingCharacterCount() + GetQueuedCharacterCount()) walkPow *= 0.9f;//0.7f; // lower: more active / lively. higher: more apathic / lazy
  walkPow *= 0.5f + 0.5f * NormalizedClamp(toAssignedOpponent.GetLength(), 2.5f, 6.0f); // farther away characters get progressively more relaxed

  //printf("%f\n", forceFieldMovement.GetLength());
  characterModelView->SetDesiredVelocity(pow(std::min(forceFieldMovement.GetLength(), 1.0f), walkPow) * playState->GetMediumVelocity());

  if (dummyMode) return;


  Vector2 desiredKickDirection = toAssignedOpponent.GetNormalized(0); //GetQuantizedDirection(toOpponent.GetNormalized(0), playState->GetQuantizationFactor());


  // defend

  // release button first
  // if (characterModelView->GetActiveMoveType() == e_MoveType_Block) characterModelView->ReleaseBlock();

  if (characterModelView->GetActiveMoveType() == e_MoveType_Walk) {

  float blockThresholdDistance = 3.0f;//2.0f * _PHYSICSSCALE;

    // only when we are closest bad guy
    if (nthClosestToAssignedOpponent < GetFightingCharacterCount()) {

      // only if enemy is close enough
      if (toAssignedOpponent.GetLength() < blockThresholdDistance) {// && Random(0, 100) < 1.0f) {

        unsigned int reflexDurationTime_ms = 20;
        if (Random(0.0f, 1.0f / (float)game->GetPhysicsTimeStep_ms()) < .0014f * dodgeFrequencyFactor ||
            (assignedOpponent->GetModelView_const()->GetActiveMoveType() == e_MoveType_Jab && assignedOpponent->GetModelView_const()->GetActiveMove()->GetTimeSinceLastActivation_ms() > reflexDurationTime_ms) ||
            assignedOpponent->GetModelView_const()->GetActiveMoveType() == e_MoveType_Dodge) {

          if (Random(0.0f, 1.0f) < 0.95f) {
            characterModelView->RequestDodge(characterModelView->GetMovement().GetNormalized(_DEFAULTVECTOR));
          } else {
            characterModelView->RequestJump(characterModelView->GetMovement(), 1.0f);
          }

        }

      }

    }

  }


  // attack

  float jabThresholdDistance = 2.0f;//2.2f;

  // only when we are closest bad guy
  if (nthClosestToAssignedOpponent < GetFightingCharacterCount()) {

    // only if enemy close enough
    if (toAssignedOpponent.GetLength() < jabThresholdDistance) {// && Random(0, 100) < 1.0f) {

      // hard
      if (Random(0.0f, 1.0f / (float)game->GetPhysicsTimeStep_ms()) < .0028f * jabFrequencyFactor)
        characterModelView->RequestJab(desiredKickDirection, Clamp(toAssignedOpponent.GetLength() / jabThresholdDistance, 0.0f, 1.0f));

    //} else if (nthClosestToAssignedOpponent < GetQueuedCharacterCount() + GetFightingCharacterCount()) {

    //  if (Random(0.0f, 1.0f / (float)game->GetPhysicsTimeStep_ms()) < .0006f * jabFrequencyFactor) characterModelView->RequestJab(desiredKickDirection);

    }

  }


/* kick disabled!
  // kick?
  float hitThresholdDistance = 2.2f;
  if (characterModelView->GetActiveMoveState() == e_MoveState_Physics) {

    // only if enemy close enough
    if (toOpponentFuture.GetLength() < hitThresholdDistance) {// && Random(0, 100) < 1.0f) {

      // only when we are closest bad guy
      if (stage->GetClosestMate(opponentFuturePos)->GetController() == this) {

        // only when angle to enemy is almost into one of the 8 way directions (or opp is very close)
        //if (toOpponent.GetLength() < 1.4f || fabs(desiredKickDirection.GetAngle(toOpponentFuture.GetNormalized(0))) < 0.03f * pi) {

          //characterModelView->SetDesiredDirection(GetQuantizedDirection(toOpponentFuture.GetNormalized(0), 1.0f));
          characterModelView->RequestKickBuffer(desiredKickDirection);

        //}

      }

    }

  }

  // add kick power..
  if (characterModelView->GetActiveMoveState() == e_MoveState_KickBuffer) {
    characterModelView->AddKickBufferPower(desiredKickDirection);
    float desiredPowerFactor = Clamp(toOpponent.GetLength() / hitThresholdDistance, 0.2f, 0.99f);
    if (characterModelView->GetStateData().kickBufferData.powerFactor > desiredPowerFactor) {
      // released kick button
      characterModelView->RequestKick();
    }
  }
*/

  //characterModelView->SetDesiredVelocity(0);
}
