#include "player.h"

Player::Player(const b2WorldId world_id, const b2BodyId ball_id, const bool is_left)
  : player_id(b2_nullBodyId), player_shape_id(b2_nullShapeId), is_left(is_left), ball_id(ball_id), on_ground(false) {
  b2BodyDef player_def = b2DefaultBodyDef();
  player_def.type = b2_dynamicBody;
  player_def.position = (b2Vec2) {is_left ? -8.0f : 8.0f, 0.2f};
  player_def.fixedRotation = true;
  player_def.gravityScale = 1.5f;

  const b2BodyId new_player_id = b2CreateBody(world_id, &player_def);
  this->player_id = new_player_id;

  constexpr float RADIUS = 1.0f;
  constexpr float Y_SCALE = 1.0f;

  b2Vec2 vertices[7];
  vertices[0] = b2Vec2{-RADIUS, 0};
  vertices[1] = b2Vec2{std::cos(static_cast<float>(std::numbers::pi) * 5.0f / 6.0f) * RADIUS,
                       std::sin(static_cast<float>(std::numbers::pi) * 5.0f / 6.0f) * RADIUS * Y_SCALE};
  vertices[2] = b2Vec2{std::cos(static_cast<float>(std::numbers::pi) * 4.0f / 6.0f) * RADIUS,
                       std::sin(static_cast<float>(std::numbers::pi) * 4.0f / 6.0f) * RADIUS * Y_SCALE};
  vertices[3] = b2Vec2{RADIUS, 0};
  vertices[4] = b2Vec2{std::cos(static_cast<float>(std::numbers::pi) * 2.0f / 6.0f) * RADIUS,
                       std::sin(static_cast<float>(std::numbers::pi) * 2.0f / 6.0f) * RADIUS * Y_SCALE};
  vertices[5] = b2Vec2{std::cos(static_cast<float>(std::numbers::pi) * 1.0f / 6.0f) * RADIUS,
                       std::sin(static_cast<float>(std::numbers::pi) * 1.0f / 6.0f) * RADIUS * Y_SCALE};
  vertices[6] = b2Vec2{0, RADIUS * Y_SCALE};

  const b2Hull hull = b2ComputeHull(vertices, 7);
  const b2Polygon player_polygon = b2MakePolygon(&hull, 0);

  b2ShapeDef player_shape_def = b2DefaultShapeDef();
  player_shape_def.density = 20.0f;
  player_shape_def.material.friction = 0.0f;
  player_shape_def.filter.categoryBits = 0x0002;
  player_shape_def.enableHitEvents = true;

  this->player_shape_id = b2CreatePolygonShape(new_player_id, &player_shape_def, &player_polygon);

  this->controller_function = [](const Info& info) {
    Action action{};
    action.jump = true;
    action.move_to_x = 10.0f;
    return action;
  };

  player_texture = LoadTexture("assets/player_left.png");
}

Player::~Player() {
  UnloadTexture(player_texture);
}

void Player::update() {
  Info info{};

  const b2Vec2 pos = b2Body_GetPosition(player_id);
  info.player_x = is_left ? -pos.x : pos.x;
  info.player_y = pos.y;

  const b2Vec2 ball_pos = b2Body_GetPosition(ball_id);
  info.ball_x = is_left ? -ball_pos.x : ball_pos.x;
  info.ball_y = ball_pos.y;

  const b2Vec2 ball_vel = b2Body_GetLinearVelocity(ball_id);
  info.ball_vel_x = is_left ? -ball_vel.x : ball_vel.x;
  info.ball_vel_y = ball_vel.y;

  Action action = controller_function(info);
  action.move_to_x = is_left ? -action.move_to_x : action.move_to_x;

  if (action.jump) {
    jump();
  }

  move_to(action.move_to_x);

  if (pos.y < 0.01f) {
    on_ground = true;
  }
}

void Player::set_controller_function(std::function<Action(const Info&)> func) {
  this->controller_function = std::move(func);
}

void Player::jump() {
  if (on_ground) {
    constexpr b2Vec2 jumpImpulse = {0.0f, 150.0f};
    b2Body_ApplyLinearImpulseToCenter(player_id, jumpImpulse, true);
    on_ground = false;
  }
}

void Player::move_to(const float x) const {
  const b2Vec2 pos = b2Body_GetPosition(player_id);

  const float targetVelX = [&] {
    const float tmp = (x - pos.x) * 10.0f;
    return std::clamp(tmp, -10.0f, 10.0f);
  }();

  const b2Vec2 vel = b2Body_GetLinearVelocity(player_id);
  const float mass = b2Body_GetMass(player_id);
  const float velChange = targetVelX - vel.x;
  const float impulse = mass * velChange;

  b2Body_ApplyLinearImpulseToCenter(player_id, b2Vec2{impulse, 0.0f}, true);
}

b2ShapeId Player::get_shape_id() const { return this->player_shape_id; }

void Player::draw() const {
  const b2Transform transform = b2Body_GetTransform(player_id);

  DrawTexturePro(player_texture,
                 (Rectangle) {0.0f, 0.0f, is_left ? static_cast<float>(player_texture.width)
                                                  : -static_cast<float>(player_texture.width),
                              static_cast<float>(player_texture.height)},
                 (Rectangle) {transform.p.x * PIXELS_PER_METER, -transform.p.y * PIXELS_PER_METER,
                              2.0f * PIXELS_PER_METER, 1.0f * PIXELS_PER_METER},
                 (Vector2) {1.0f * PIXELS_PER_METER, 1.0 * PIXELS_PER_METER}, 0, WHITE);
}
