#include "rigidbody.h"
#include "camera.h"
#include "debug.h"
#include "program.h"
#include "collision.h"
#include "transformable.h"

struct RigidBody {
    PhysicsEntity owner;

    float mass;
    float bounce;

    Vector last_linear_force;
    Vector next_linear_force;
    Vector linear_velocity;
    Transform internal_transform;

    PhysicsMask layers;
    PhysicsMask collision_mask;
    
    List colliders;

    int is_static;

    List contacts;
};

RigidBody* rigidbody_make(PhysicsEntity owner) {
    RigidBody* self = malloc(sizeof(RigidBody));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for rigidbody");
    *self = (RigidBody){
        .owner = owner,
        .mass = 1.0f,
        .bounce = 0.0f,

        .linear_velocity = ZeroVector,
        .next_linear_force = ZeroVector,
        .last_linear_force = ZeroVector,

        .internal_transform = *owner.transformable->get_transform(owner.data),

        .layers = 0x1,
        .collision_mask = 0x1,
        
        .colliders = list_from_type(Collider*),
        
        .is_static = 0,
        
        .contacts = list_from_type(Contact),
    };
    self->internal_transform.scale = OneVector;
    return self;
}

void rigidbody_destroy(RigidBody* self) {
    list_empty(&self->contacts);
    free(self);
}

void rigidbody_add_contact(RigidBody* self, Collision hit) {
    list_add(&self->contacts,
    &(Contact) {
        .hit = hit,
        .duration = delta_time()
    });
}

void rigidbody_collect_contacts(RigidBody* self) {
    list_empty(&self->contacts);
}

List* rigidbody_get_contacts(RigidBody* self) {
    return &self->contacts;
}

static inline
void _internal_debug_draw_collision_edge(RigidBody* self, Contact* contact) {
#if !NDEBUG
    Vector left = contact->hit.edge_left;
    Vector right = contact->hit.edge_right;
    Vector point = transform_point(&self->internal_transform, contact->hit.point);
    Vector a = camera_world_to_pixel_point(&g_camera, left);
    Vector b = camera_world_to_pixel_point(&g_camera, right);
    Vector n = transform_direction(&g_camera.transform, contact->hit.normal);
    SDL_SetRenderDrawColor(g_renderer, 255, 2, 255, 255);
    SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y);
    a = camera_world_to_pixel_point(&g_camera, point);
    b = vaddf(a, vmulff(n, 100.f));
    SDL_SetRenderDrawColor(g_renderer, 255, 0, 0, 255);
    SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y);
#endif
}

void rigidbody_integrate_forces(RigidBody* self) {
    if(self->is_static)
        return;
    
    const float dt = delta_time();

    Vector position = self->internal_transform.position;
    Vector velocity = self->linear_velocity;
    Vector acceleration = vmulff(self->last_linear_force, 0.5f * dt * dt);

    position = vaddf(position, vmulff(velocity, dt));
    position = vaddf(position, acceleration);

    acceleration = vmulff(vaddf(self->next_linear_force, acceleration), dt * 0.5f);
    velocity = vaddf(velocity, acceleration);

    self->linear_velocity = velocity;
    self->internal_transform.position = position;
    Transform* owner_trans = self->owner.transformable->get_transform(self->owner.data);
    owner_trans->position = position;

    self->last_linear_force = self->next_linear_force;
    self->next_linear_force = ZeroVector;
}

float rigidbody_get_mass(const RigidBody* self) {
    return self->mass;
}

void rigidbody_set_mass(RigidBody* self, float mass) {
    self->mass = mass;
}

float rigidbody_get_bounce(const RigidBody* self) {
    return self->bounce;
}

void rigidbody_set_bounce(RigidBody* self, float bounce) {
    self->bounce = bounce;
}

void rigidbody_add_impulse(RigidBody* self, Vector force, int use_mass) {
    rigidbody_accelerate(self, vmulff(force, 1.0f/delta_time()), use_mass);
}

void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass) {
    if(use_mass)
        force = vmulff(force, 1.0f / self->mass);
    if(vsqrmagnitudef(force) > powf(0.01f, 2))
        self->next_linear_force = vaddf(self->next_linear_force, force);
}

PhysicsMask rigidbody_get_layers(RigidBody* self) {
    return self->layers;
}

void rigidbody_set_layers(RigidBody* self, PhysicsMask layers) {
    self->layers = layers;
}

PhysicsMask rigidbody_get_collision_mask(RigidBody* self) {
    return self->collision_mask;
}

void rigidbody_set_collision_mask(RigidBody* self, PhysicsMask mask) {
    self->collision_mask = mask;
}

int rigidbody_is_static(RigidBody* self) {
    return self->is_static;
}

void rigidbody_set_static(RigidBody* self, int value) {
    self->is_static = value != 0;
}

Vector rigidbody_get_velocity(const RigidBody* self) {
    return self->linear_velocity;
}

void rigidbody_set_velocity(RigidBody* self, Vector velocity) {
    self->next_linear_force = vaddf(self->next_linear_force, vsubf(velocity, self->next_linear_force));
    self->linear_velocity = velocity;
}

Vector rigidbody_get_force(RigidBody* self) {
    return self->next_linear_force;
}

void rigidbody_add_collider(RigidBody *self, Collider* collider) {
    list_add(&self->colliders, &collider);
}

void rigidbody_remove_collider(RigidBody *self, Collider* collider) {
    for(size_t i = 0; i < self->colliders.len; ++i) {
        if(collider == *list_at_as(Collider*, &self->colliders, i)) {
            list_erase(&self->colliders, i);
            return;
        }
    }
}

List* rigidbody_get_colliders(RigidBody* self) {
    return &self->colliders;
}

void rigidbody_debug_draw_contacts(RigidBody* self) {
    list_foreach(Contact*, contact, &self->contacts) {
        _internal_debug_draw_collision_edge(self, contact);
    }
}

Transform* rigidbody_get_transform(RigidBody* self) {
    return &self->internal_transform;
}
