#include "camera.h"
#include "utils.h"
#include "tween.h"

typedef struct camera_s
{
    int            level_width;
    int            level_height;
    int            view_width;
    int            view_height;
    vector_t       position;
    vector_t       follow;
    point_t        offset;
    bool           on_shake;
    int            count_shake;
    vector_t       shake;
    tween_param_t *shake_x_tween;
    tween_param_t *shake_y_tween;
} camera_t;

static camera_t camera;

static void _Camera_CalculatePosition()
{
    camera.position.x = camera.follow.x - camera.offset.x;
    camera.position.y = camera.follow.y - camera.offset.y;

    if (camera.position.x < 96)
        camera.position.x = 96;

    if (camera.position.y < 96)
        camera.position.y = 96;

    int border_right  = camera.level_width  - camera.view_width;
    int border_bottom = camera.level_height - camera.view_height;

    if (camera.position.x > border_right - 96)
        camera.position.x = border_right - 96;

    if (camera.position.y > border_bottom - 96)
        camera.position.y = border_bottom - 96;
}

void Camera_Init(void)
{
    camera.on_shake      = false;
    camera.count_shake   = 0;
    camera.shake         = Utils_GetVector(0, 0);
    camera.shake_x_tween = Tween_ParamCreate(60, 0, 1, EASE_OUT_CUBIC);
    camera.shake_y_tween = Tween_ParamCreate(60, 0, 1, EASE_OUT_CUBIC);
}

void Camera_SetLevelBounds(const int level_width, const int level_height)
{
    camera.level_width  = level_width;
    camera.level_height = level_height;

    _Camera_CalculatePosition();
}

void Camera_SetViewport(const int width, const int height)
{
    camera.view_width  = width;
    camera.view_height = height;
    camera.offset.x    = (int)(camera.view_width / 2);
    camera.offset.y    = (int)(camera.view_height / 2);

    _Camera_CalculatePosition();
}

void Camera_SetFollowPosition(vector_t position)
{
    camera.follow = position;
}

vector_t Camera_GetFollowPosition(void)
{
    return camera.follow;
}

void Camera_Shake(void)
{
    camera.on_shake    = true;
    camera.count_shake = 0;

    Tween_ParamStop(camera.shake_x_tween, true);
    Tween_ParamStop(camera.shake_y_tween, true);
}

vector_t Camera_GetPosition(void)
{
    return camera.position;
}

// ToDo: Follow
void Camera_Update(const float dt)
{
    _Camera_CalculatePosition();

    if (camera.on_shake)
    {
        if (Tween_ParamIsFinish(camera.shake_x_tween))
        {
            camera.count_shake++;

            if (camera.count_shake > 4)
            {
                camera.count_shake = 0;
                camera.on_shake    = 0;

                camera.shake = Utils_GetVector(0, 0);
            }
            else
            {
                int rnd_max_x = Utils_GetRandomRange(0, 6);
                int rnd_max_y = Utils_GetRandomRange(0, 6);

                Tween_ParamResetValues(camera.shake_x_tween, 0, rnd_max_x);
                Tween_ParamResetValues(camera.shake_y_tween, 0, rnd_max_y);
                Tween_ParamRun(camera.shake_x_tween);
                Tween_ParamRun(camera.shake_y_tween);
            }
        }
        else
        {
            camera.shake.x = Tween_ParamGetValue(camera.shake_x_tween);
            camera.shake.y = Tween_ParamGetValue(camera.shake_y_tween);
        }
    }
}

void Camera_Draw(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect)
{
    SDL_Rect draw_rect = Utils_GetRect(dstrect->x, dstrect->y, dstrect->w, dstrect->h);

    draw_rect.x = draw_rect.x - (Uint32)camera.position.x + (Uint32)camera.shake.x;
    draw_rect.y = draw_rect.y - (Uint32)camera.position.y + (Uint32)camera.shake.y;

    if ((draw_rect.x >= -48) && (draw_rect.x+draw_rect.w <= camera.view_width+48) &&
        (draw_rect.y >= -48) && (draw_rect.y+draw_rect.h <= camera.view_height+48))
        SDL_RenderCopy(renderer, texture, srcrect, &draw_rect);
}

void Camera_DrawEx(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect, const float angle, const SDL_Point *center, const SDL_RendererFlip flip)
{
    SDL_Rect draw_rect = Utils_GetRect(dstrect->x, dstrect->y, dstrect->w, dstrect->h);

    draw_rect.x = draw_rect.x - (Uint32)camera.position.x + (Uint32)camera.shake.x;
    draw_rect.y = draw_rect.y - (Uint32)camera.position.y + (Uint32)camera.shake.y;

    if ((draw_rect.x >= -48) && (draw_rect.x+draw_rect.w <= camera.view_width+48) &&
        (draw_rect.y >= -48) && (draw_rect.y+draw_rect.h <= camera.view_height+48))
        SDL_RenderCopyEx(renderer, texture, srcrect, &draw_rect, angle, center, flip);
}

void Camera_DrawNotCulling(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect)
{
    SDL_Rect draw_rect = Utils_GetRect(dstrect->x, dstrect->y, dstrect->w, dstrect->h);

    draw_rect.x = draw_rect.x - (Uint32)camera.position.x + (Uint32)camera.shake.x;
    draw_rect.y = draw_rect.y - (Uint32)camera.position.y + (Uint32)camera.shake.y;

    SDL_RenderCopy(renderer, texture, srcrect, &draw_rect);
}

void Camera_PlaySound(Mix_Chunk *sound, vector_t position)
{
    if (sound == NULL)
        return;

    if ((position.x >= camera.position.x) &&
        (position.x <= camera.position.x + camera.view_width) &&
        (position.y >= camera.position.y) &&
        (position.y <= camera.position.y + camera.view_height))
        Mix_PlayChannel(-1, sound, 0);
}

void Camera_Quit(void)
{
    Tween_ParamDelete(camera.shake_x_tween);
    Tween_ParamDelete(camera.shake_y_tween);
}
