#ifndef _HPP_KUNGFLOW_CHARACTER_MODELVIEW
#define _HPP_KUNGFLOW_CHARACTER_MODELVIEW

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

#include "character_moves.hpp"

#include <boost/circular_buffer.hpp>

#include <climits>

//class CharacterMove;
class KungFlowGame;
class PlayState;
class StageEntity;
class Character;

/*
struct Interaction {
  unsigned int moveID;
  StageEntity *targetEntity;
};
*/

struct Stats {
  float agilityFactor;
  float hurtThreshold;
  WalkStats walkStats;
  JabStats jabStats;
  DashStats dashStats;
  BlockStats blockStats;
  DodgeStats dodgeStats;
  ThrowStats throwStats;
  JumpStats jumpStats;
  LandingStats landingStats;
  HitStats hitStats;
};

struct PhysicsState {

  PhysicsState() {
    activeMove = nullptr;
    zPosition = 0.0f;
    zMovement = 0.0f;
    bodyAngle = 0.0f;
  }

  CharacterMove *activeMove;

  Vector2 position;
  float zPosition;
  Vector2 movement;
  float zMovement;
  radian bodyAngle;
};


class CharacterModelView {

  public:

    CharacterModelView(const KungFlowGame *game, const PlayState *playState, Character *character, int id, const Vector2 &position);
    virtual ~CharacterModelView();

    void AddMoveOwnership(e_MoveType moveType, CharacterMove *move) { assert(moves[moveType] == nullptr); moves[moveType] = move; }
    AnimatedSprite *GetCharacterSprite() { return characterSprite; }

    void SetBasePosition(const Vector2 &newPos);
    const Vector2 &GetBasePosition() const;

    void OnStepPhysicsTime();
    void InitPhysicsStateQueue(); // snapshop current situation into queuedPhysicsState
    void ApplyPhysicsStateQueue(); // copy snapshop back to reality (physicsState), and correct for environment collisions
    void PostStepPhysicsTime(); // stuff that we need to do now we know the new physics sitation (of all actors)

    void Draw(const Viewport &viewport, const Camera &camera) const;

    void GetSpriteBounds(Vector2 &center, Vector2 &radius) const;
    void OnCollide(const StageEntity *entity);

    float GetHealthPoints() const { return healthPoints; }
    void AddHealthPoints(float hp) { healthPoints += hp; }

    Vector2 GetPosition() const { return physicsState.position; }
    void SetPosition(PhysicsState &targetPhysicsState, const Vector2 &newPosition) { targetPhysicsState.position = newPosition; }
    void SetPosition(const Vector2 &newPosition) { SetPosition(this->physicsState, newPosition); }
    void SetQueuedPosition(const Vector2 &newPosition) { SetPosition(this->queuedPhysicsState, newPosition); }

    float GetZPosition() const { return physicsState.zPosition; }
    void SetZPosition(PhysicsState &targetPhysicsState, float newZPosition) { targetPhysicsState.zPosition = newZPosition; }
    void SetZPosition(float newZPosition) { SetZPosition(this->physicsState, newZPosition); }
    void SetQueuedZPosition(float newZPosition) { SetZPosition(this->queuedPhysicsState, newZPosition); }

    Vector2 GetMovement() const { return physicsState.movement; }//GetDirection() * GetVelocity(); }
    void SetMovement(PhysicsState &targetPhysicsState, const Vector2 &newMovement) { targetPhysicsState.movement = newMovement; }
    void SetMovement(const Vector2 &newMovement) { SetMovement(this->physicsState, newMovement); }
    void SetQueuedMovement(const Vector2 &newMovement) { SetMovement(this->queuedPhysicsState, newMovement); }

    float GetZMovement() const { return physicsState.zMovement; }
    void SetZMovement(PhysicsState &targetPhysicsState, float newZMovement) { targetPhysicsState.zMovement = newZMovement; }
    void SetZMovement(float newZMovement) { SetZMovement(this->physicsState, newZMovement); }
    void SetQueuedZMovement(float newZMovement) { SetZMovement(this->queuedPhysicsState, newZMovement); }

    Vector2 GetDirection() const { return physicsState.movement.GetNormalized(0); }//Vector2(1, 0).GetRotated(GetAngle()); }
    float GetVelocity() const { return physicsState.movement.GetLength(); }//velocity; }
    radian GetAngle() const { return physicsState.movement.GetAngle(); }//return angle; }
    radian GetBodyAngle() const { return physicsState.bodyAngle; }
    float GetRadius() const { return 0.3f; } //0.65f; }
    float GetHeight() const { return GetRadius() * 2.0f; }

    unsigned int GetActiveMoveID() const { return physicsState.activeMove->GetUniqueID(); }
    e_MoveType GetActiveMoveType() const { return physicsState.activeMove->GetMoveType(); }
    const CharacterMove *GetActiveMove() const { return physicsState.activeMove; }
    const Stats &GetStats() const { return stats; }
    //const MoveStateData &GetStateData() const { return moveStateData; }

    void SetDesiredDirection(const Vector2 &desiredDirection) { this->desiredDirection = desiredDirection.GetNormalized(0); }
    void SetDesiredBodyAngle(radian desiredBodyAngle) { this->desiredBodyAngle = desiredBodyAngle; }
    void SetDesiredVelocity(float desiredVelocity);
    Vector2 GetDesiredDirection() const { return this->desiredDirection; }
    float GetDesiredVelocity() const { return this->desiredVelocity; }

    // activate modes
    // could also set these from controller, but this way we have control whether it's legal, considering the character's context, to use these moves at all
    bool RequestJab(const Vector2 &direction, float distanceFactor = 1.0f);
    bool RequestDash(const Vector2 &target);
    bool RequestDodge(const Vector2 &direction);
    bool RequestThrow(const Vector2 &target);
    bool RequestJump(const Vector2 &direction, float powerFactor = 1.0f);
    bool RequestLanding(float durationMultiplier = 1.0f);
    bool RequestHit(PhysicsState &targetPhysicsState, const Vector2 &translation, float sustainMultiplier = 1.0f);//, float powerFactor = 1.0f);
    bool RequestHit(const Vector2 &translation, float sustainMultiplier = 1.0f) { return RequestHit(this->queuedPhysicsState, translation, sustainMultiplier); }

  private:

    void SetActiveMoveType(PhysicsState &targetPhysicsState, const e_MoveType &newType);
    void OnChangeMoveType(const e_MoveType &oldType, const e_MoveType &newType);
    void UpdateMoves();
    bool IsOnProcessedMoveIDList(unsigned int moveID) const;
//    int GetCharacterSequenceIndex(e_CharacterSequence sequence) {
//      return sequence * 8; // every sequence makes for [number of directions] offset :)
//    }

    const KungFlowGame *game;
    const PlayState *playState;
    Character *character;

    unsigned int id;
    std::string name;

    Object *playerObject;
    //AnimatedSprite *playerSprite; // debug sprite
    Object *graphicObject;
    AnimatedSprite *characterSprite;
    Object *neonObject;
    Sprite *neonSprite;
    //Object *modeObject;
    //Sprite *modeSprite[e_MoveState_SIZE];

    Object *nameObject;
    Sprite *nameSprite;
    Vector2 basePosition;

    Vector2 desiredDirection;
    //Vector2 desiredBodyDirection;
    radian desiredBodyAngle;
    float desiredVelocity;

    // defaults
    //float maxChangeMPS;

    Stats stats;
    float healthPoints_max;
    float healthPoints;

    // states
    //MoveStateData moveStateData;
    PhysicsState previousPhysicsState;
    PhysicsState physicsState;
    PhysicsState queuedPhysicsState;

    //std::vector<CharacterMove*> moves;
    CharacterMove *moves[e_MoveType_SIZE];

    //static unsigned int _moveID; // increments with every new move
    boost::circular_buffer<unsigned int> processedMoveIDList; // add (enemy) moves that have already interacted with me; can't get hit twice by the same kick, for example

};

#endif
