#include "shape.h"
#include "camera.h"
#include "debug.h"
#include "render.h"
#include "list.h"
#include <SDL2/SDL_render.h>
#include <stdlib.h>

struct Shape {
    List points;

    Vector mean;
    int is_convex;
};

static
Vector* _shape_get_furthest_in_direction(Shape* self, Vector direction) {
    // ensure direction is normalized
    direction = vnormalizedf(direction);
    float furthest_dot = vdotf(direction, vsubf(*list_at_as(Vector, &self->points, 0), self->mean));
    Vector* furthest = list_at(&self->points, 0);

    float dot;
    list_foreach(Vector*, point, &self->points) {
        dot = vdotf(direction, vsubf(*point, self->mean));
        if(dot > furthest_dot) {
            furthest = point;
        }
    }

    return furthest;
}

// go through each point,
// in order to be convex, none of the points in the shape can be "inset".
// This means that if one of the points is not the furthest in it's own direction
// measured from the median.
static
int _shape_calculate_is_convex(Shape* self) {
    // point relative to mean
    Vector relative;
    list_foreach(Vector*, point, &self->points) {
        relative = vsubf(*point, self->mean);
        if(point != _shape_get_furthest_in_direction(self, relative)) {
            return 0;
        }
    }

    return 1;
}

static
Vector _shape_calculate_mean(Shape* self) {
    Vector avg = ZeroVector;
    size_t count = 0;
    list_foreach(Vector*, point, &self->points) {
        ++count;
        avg = vaddf(avg, vmulff(*point, 1.0/count));
    }

    return avg;
}

Shape* shape_new(const Vector* points, size_t points_len) {
    // allocate required space for shape and points array
    Shape* self = malloc(sizeof(Shape));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate enough space for a shape object.");

    self->points = list_from_type(Vector);
    list_reserve(&self->points, points_len);

    if(points != NULL) {
        memcpy(list_iterator_begin_as(Vector, &self->points), points, points_len * sizeof(Vector));
        self->points.len = points_len;

        // derive metadata
        self->mean = _shape_calculate_mean(self);
        self->is_convex = _shape_calculate_is_convex(self);
    } else {
        self->is_convex = 1;
        self->mean = ZeroVector;
    }

    return self;
}

Shape* shape_new_square(Vector size) {
    return shape_new((Vector[4]){
        ZeroVector,
        (Vector){size.x, 0.f},
        size,
        (Vector){0.f, size.y},
    }, 4);
}

Shape* shape_clone(const Shape* source) {
    Shape* self = malloc(sizeof(Shape));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for shape object.");

    self->points = list_copy(&source->points);

    // copy data from source
    self->mean = source->mean;
    self->is_convex = source->is_convex;

    return self;
}

void shape_destroy(Shape* self) {
    list_empty(&self->points);
    free(self);
}

Vector* shape_get_start(Shape* self) {
    return list_iterator_begin(&self->points);
}

Vector* shape_get_end(Shape* self) {
    return list_iterator_end(&self->points);
}

size_t shape_get_points_count(const Shape* self) {
    return self->points.len;
}

Vector shape_get_point(Shape* self, size_t at) {
    ASSERT_RETURN(at < self->points.len, self->mean, "Point index %zu out of bounds for shape", at);
    return *list_at_as(Vector, &self->points, at);
}

Vector shape_get_point_transformed(Shape* self, size_t at, Transform transform) {
    return transform_point(&transform, shape_get_point(self, at));
}

void shape_set_point(Shape* self, size_t at, Vector point) {
    ASSERT_RETURN(at < self->points.len,, "Point index %zu out of bounds for shape", at);
    *list_at_as(Vector, &self->points, at) = point;
}

void shape_add_point(Shape* self, Vector point) {
    list_add(&self->points, &point);
}

void shape_insert_point(Shape* self, size_t at, Vector point) {
    ASSERT_RETURN(at < self->points.len + 1,, "Point index %zu out of bounds for shape", at);
    list_insert(&self->points, &point, at);
}

List* shape_get_points(Shape* self) {
    return &self->points;
}

Vector shape_remove_point(Shape* self, size_t at) {
    Vector point = *list_at_as(Vector, &self->points, at);
    list_erase(&self->points, at);
    return point;
}

Vector shape_get_median_point(Shape* self) {
    return self->mean;
}

int shape_is_convex(Shape* self) {
    return self->is_convex;
}

void shape_draw(Shape* self, Transform transform) {
    Vector lhs, rhs, normal;
    for(size_t i = 0; i < self->points.len; ++i) {
        lhs = shape_get_point_transformed(self, i, transform);
        rhs = shape_get_point_transformed(self, (i + 1) % self->points.len, transform);
        normal = vnormalizedf(vperpendicularf(vsubf(rhs, lhs)));
        lhs = camera_world_to_pixel_point(&g_camera, lhs);
        rhs = camera_world_to_pixel_point(&g_camera, rhs);
        normal = transform_direction(&g_camera.transform, normal);

        SDL_SetRenderDrawColor(g_renderer, 255, 255, 255, 255);
        SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);

        lhs = vlerpf(lhs, rhs, 0.5f);
        rhs = vaddf(lhs, vmulff(normal, 10.f));
        SDL_SetRenderDrawColor(g_renderer, 0, 0, 255, 255);
        SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y);
    }
}
