#include "factory_objects.h"
#include "utils.h"
#include "camera.h"

typedef struct object_s
{
    struct object_s *previos;
    struct object_s *next;
    void            *actor;
    actor_type_t     type;
    object_layer_t   layer;
    bool             remove;
    void (*funcGetInfo)  (void *actor, object_info_t *info);
    void (*funcCollision)(void *actor, void *actor_collision, actor_type_t actor_type);
    void (*funcUpdate)   (void *actor, float dt);
    void (*funcDraw)     (SDL_Renderer *renderer, void *actor);
    void (*funcFree)     (void *actor);
} object_t;

static object_t *list_object_first = NULL;
static object_t *list_object_last  = NULL;
static bool      need_sort_objects = false;

static void _FactoryObjects_ActorGetInfo(object_t *object, object_info_t *info)
{
    if (object == NULL)
        return;

    if (object->actor == NULL)
        return;

    if (object->funcGetInfo != NULL)
        object->funcGetInfo(object->actor, info);
}

static void _FactoryObjects_ActorFree(object_t *object)
{
    if (object == NULL)
        return;

    if (object->actor == NULL)
        return;

    if (object->funcFree != NULL)
        object->funcFree(object->actor);
}

static void _FactoryObjects_RemoveSigned(void)
{
    object_t *object = list_object_first;

    if (object == NULL)
        return;

    while(object != NULL)
    {
        object_t *object_previos = object->previos;
        object_t *object_next    = object->next;

        if (object->remove)
        {
            if (object == list_object_last)
                list_object_last = object_previos;

            if (object == list_object_first)
                list_object_first = object_next;

            if (object_previos != NULL)
                object_previos->next = object_next;

            if (object_next != NULL)
                object_next->previos = object_previos;

            _FactoryObjects_ActorFree(object);

            free(object);
            object = NULL;

            FactoryObjects_NeedSort();
        }

        object = object_next;
    }
}

static void _FactoryObjects_SortLayers()
{
    if (need_sort_objects == false)
         return;

    bool      repeat = false;
    object_t *object = list_object_first;

    if (object == NULL)
        return;

    do
    {
        repeat   = false;
        object = list_object_first;

        while(object != NULL)
        {
            object_t *previos = object->previos;
            object_t *last    = object->next;

            if (previos != NULL)
            {
                object_t *first = previos->previos;

                if (object->layer < previos->layer)
                {
                    object->previos  = first;
                    object->next     = previos;
                    previos->previos = object;
                    previos->next    = last;

                    if (first != NULL)
                        first->next = object;

                    if (last != NULL)
                        last->previos = previos;

                    if (previos == list_object_first)
                        list_object_first = object;

                    if (object == list_object_last)
                        list_object_last = previos;

                    repeat = true;
                }
            }

            object = last;
        }
    } while(repeat);

    need_sort_objects = false;
}

bool FactoryObjects_IsGuardActor(object_t *object, actor_type_t type)
{
    bool result = false;

    if (object != NULL)
    {
        if (object->actor != NULL)
        {
            if (object->type == type)
                result = true;
        }
    }

    return result;
}

int FactoryObjects_GetCount(void)
{
    int count_objects = 0;
    object_t *object  = list_object_first;

    if (object != NULL)
    {
        while(object != NULL)
        {
            count_objects++;
            object = object->next;
        }
    }

    return count_objects;
}

object_t* FactoryObjects_Create()
{
    object_t *object = (object_t*) malloc(sizeof(object_t));

    object->previos       = list_object_last;
    object->next          = NULL;
    object->actor         = NULL;
    object->type          = AT_NONE;
    object->layer         = 0;
    object->remove        = false;
    object->funcGetInfo   = NULL;
    object->funcCollision = NULL;
    object->funcUpdate    = NULL;
    object->funcDraw      = NULL;
    object->funcFree      = NULL;

    if (list_object_first == NULL)
        list_object_first = object;

    if (list_object_last != NULL)
        list_object_last->next = object;

    list_object_last = object;

    return object;
}

void FactoryObjects_SetActor(object_t *object, void *actor, actor_type_t type, object_layer_t layer)
{
    if (object == NULL)
        return;

    object->actor = actor;
    object->type  = type;
    object->layer = layer;

    FactoryObjects_NeedSort();
}

void FactoryObjects_SetFunc(object_t *object, void *func, object_func_t type_func)
{
    if (object == NULL)
        return;

    switch(type_func)
    {
        case OFUNC_GETINFO   : object->funcGetInfo   = func; break;
        case OFUNC_COLLISION : object->funcCollision = func; break;
        case OFUNC_UPDATE    : object->funcUpdate    = func; break;
        case OFUNC_DRAW      : object->funcDraw      = func; break;
        case OFUNC_FREE      : object->funcFree      = func; break;
    }
}

void FactoryObjects_SignRemove(object_t *object)
{
    if (object != NULL)
        object->remove = true;
}

void FactoryObjects_NeedSort(void)
{
    need_sort_objects = true;
}

void FactoryObjects_Update(float dt)
{
    object_t *object1 = list_object_first;

    if (object1 != NULL)
    {
        while(object1 != NULL)
        {
            if (object1->actor != NULL)
            {
                if (object1->funcUpdate != NULL)
                    object1->funcUpdate(object1->actor, dt);
            }

            // collisions
            object_t *object2 = list_object_first;
            while(object2 != NULL)
            {
                if (object1 != object2)
                {
                    SDL_Rect intersect;

                    object_info_t info1;
                    object_info_t info2;

                    _FactoryObjects_ActorGetInfo(object1, &info1);
                    _FactoryObjects_ActorGetInfo(object2, &info2);

                    if (SDL_IntersectRect(&info1.collision_rect, &info2.collision_rect, &intersect) == SDL_TRUE)
                    {
                        if ((object1->actor != NULL) && (object2->actor != NULL))
                        {
                            if (object1->funcCollision != NULL)
                                object1->funcCollision(object1->actor, object2->actor, object2->type);
                        }
                    }
                }

                object2 = object2->next;
            }

            object1 = object1->next;
        }
    }

    _FactoryObjects_RemoveSigned();
    _FactoryObjects_SortLayers();
}

void FactoryObjects_Draw(SDL_Renderer *renderer)
{
    object_t *object = list_object_first;

    if (object != NULL)
    {
        while(object != NULL)
        {
            if (object->actor != NULL)
            {
                if (object->funcDraw != NULL)
                    object->funcDraw(renderer, object->actor);
            }

            #ifdef DEBUG_DRAW_ACTORS
            object_info_t info;
            vector_t      camera_pos = Camera_GetPosition();

            _FactoryObjects_ActorGetInfo(object, &info);

            info.box_rect.x       -= camera_pos.x;
            info.box_rect.y       -= camera_pos.y;
            info.collision_rect.x -= camera_pos.x;
            info.collision_rect.y -= camera_pos.y;
            info.position.x       -= camera_pos.x;
            info.position.y       -= camera_pos.y;

            SDL_SetRenderDrawColor(renderer, 70, 70, 70, 255);
            SDL_RenderDrawRect(renderer, &info.box_rect);
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
            SDL_RenderDrawRect(renderer, &info.collision_rect);
            SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
            SDL_RenderDrawPoint(renderer, info.position.x, info.position.y);
            SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
            #endif // DEBUG_DRAW_ACTORS

            object = object->next;
        }
    }
}

void FactoryObjects_Clear(void)
{
    object_t *object = list_object_first;

    if (object != NULL)
    {
        while(object != NULL)
        {
            object_t *object_next = object->next;

            _FactoryObjects_ActorFree(object);
            free(object);

            object = object_next;
        }
    }

    list_object_first = NULL;
    list_object_last  = NULL;
}
