#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>

#include "breakout.h"
#include "vector.h"

#define BALL_TexturePath   "assets/images/ball.png"
#define PADDLE_TexturePath "assets/images/paddle.png"
#define BLOCK_TexturePath  "assets/images/spritesheet.png"

#ifndef __nullptr__
#define Nullptr(type) (type *)0
#endif // __nullptr__

int BLOCK_TextureCount = 24;
int BREAKOUT_BoxWidth, BREAKOUT_BoxHeight;
SDL_Texture * BALL_Texture;
SDL_Texture * PADDLE_Texture;
SDL_Texture * BLOCK_Texture;
SDL_Rect * BALL_SourceRects;
SDL_Rect * PADDLE_SourceRects;
SDL_Rect * BLOCK_SourceRects;
Uint8 * PADDLE_MoveLeftKeys, * PADDLE_MoveRightKeys;
bool BREAKOUT_IsInit = false;
bool BALL_IsInit = false;
bool PADDLE_IsInit = false;
bool BLOCK_IsInit = false;

void BREAKOUT_INITIALIZE(SDL_Renderer * renderer, int width, int height){
    if (!BREAKOUT_IsInit) {
        printf("Initializing Game...\n");
        srand(time(NULL));
        BREAKOUT_BoxWidth = width;
        BREAKOUT_BoxHeight = height;
        BALL_Initialize(renderer);
        PADDLE_Initialize(renderer);
        BLOCK_Initialize(renderer);
        printf("Game initialized!\n");
        BREAKOUT_IsInit = true;
    } else printf("Game is already initialized!\n");
} /* BREAKOUT_INITIALIZE */

Scenery BREAKOUT_CreateDefault(){
    Scenery scenery;

    scenery.BlockCount = 60;
    scenery.ball = BALL_CreateDefault();
    scenery.paddle = PADDLE_CreateDefault();
    scenery.blocks = malloc(scenery.BlockCount * sizeof(Block));
    int index;
    for (int y = 0; y < 6; y++) {
        index = 10 * y;
        for (int x = 0; x < 10; x++) {
            scenery.blocks[x + index] = BLOCK_CreateDefault();
            scenery.blocks[x + index].TargetRect = (SDL_Rect) {.x = ((192 * x) + 4), .y = ((96 * y) + 2), .w = 184, .h = 92 };
            scenery.blocks[x + index].TextureIndex = y + x + 2;
            //     printf("Block created at index: %d\n", (x + index));
            //     printf("Block Target: %d %d %d %d\n", (  scenery.blocks[x + index].TargetRect.x), (  scenery.blocks[x + index].TargetRect.y), (  scenery.blocks[x + index].TargetRect.w), (  scenery.blocks[x + index].TargetRect.h));
            //     printf("Block Texture index: %d\n", scenery.blocks[x + index].TextureIndex);
            //     system("pause");
        }
    }
    return scenery;
} /* BREAKOUT_CreateDefault */

// This Function is obsolete! Do not use it!
void BREAKOUT_ChangeSize(int width, int height){
    BREAKOUT_BoxWidth = width;
    BREAKOUT_BoxHeight = height;
}

void BREAKOUT_Update(Scenery * scenery, const Uint8 * keystate){
    PADDLE_Update(&(scenery->paddle), keystate); // Update paddle before ball because paddle is not static!
    BALL_Update(&(scenery->ball), &(scenery->paddle), (scenery->blocks), (scenery->BlockCount));
    for (int i = 0; i < (scenery->BlockCount); i++) {
        BLOCK_Update((scenery->blocks) + i);
    }
}

void BREAKOUT_Draw(Scenery * scenery, SDL_Renderer * renderer){
    for (int i = 0; i < (scenery->BlockCount); i++) {
        BLOCK_Draw(renderer, &((scenery->blocks)[i]));
    }
    BALL_Draw(renderer, &(scenery->ball));
    PADDLE_Draw(renderer, &(scenery->paddle));
}

void BREAKOUT_DEINITIALIZE(){
    if (BREAKOUT_IsInit) {
        printf("De-initializing Game...\n");
        free(PADDLE_MoveLeftKeys);
        free(PADDLE_MoveRightKeys);
        free(BALL_SourceRects);
        free(PADDLE_SourceRects);
        free(BLOCK_SourceRects);
        BALL_Deinitialize();
        PADDLE_Deinitialize();
        BLOCK_Deinitialize();
        printf("Game de-initialized!\n");
        BREAKOUT_IsInit = false;
    } else printf("Game is already de-initialized!\n");
}

void BREAKOUT_DestroyObject(Scenery * scenery){
    for (size_t i = 0; i < (scenery->BlockCount); i++) {
        BLOCK_DestroyObject((scenery->blocks) + i);
    }
    BALL_DestroyObject(&(scenery->ball));
    PADDLE_DestroyObject(&(scenery->paddle));
    free((scenery->blocks));
}

void BALL_Initialize(SDL_Renderer * renderer){
    if (!BALL_IsInit) {
        printf("Initializing Ball...\n");
        BALL_Texture = IMG_LoadTexture(renderer, BALL_TexturePath);
        if (!BALL_Texture) printf("Ball texture failed to load!\n");
        BALL_SourceRects = (SDL_Rect *)malloc(1 * sizeof(SDL_Rect));
        if (!BALL_SourceRects) printf("FATAL! Memory allocation failed!\n");
        BALL_SourceRects[0] = (SDL_Rect) {.x = 0, .y = 0, .w = 512, .h = 512 };
        printf("Ball initialized!\n");
        BALL_IsInit = true;
    } else printf("Ball is already initialized!\n");
}

Ball BALL_CreateDefault(){
    double rotation = (double)(rand() % 360);

    return (Ball) {
               .Location = (Vector) {.x = BREAKOUT_BoxWidth / 2 + 300, .y = BREAKOUT_BoxHeight / 2 },
               .Momentum = (Vector) {.x = 0.0f, .y = 15.0f },
               .TargetRect = (SDL_Rect) {.x = 0, .y = 0, .w = 50, .h = 50 },
               .Size = 25.0f,
               .Rotation = rotation,
               .RotationValue = 2,
               .TextureIndex = 0,
               .Speed = 15.0f
    }; // Objekt für die Eigenschaften des Balls
}

void BALL_Draw(SDL_Renderer * renderer, Ball * obj){
    // printf("Ball drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).x);
    SDL_RenderCopyEx(renderer, BALL_Texture, BALL_SourceRects + (obj->TextureIndex), &(obj->TargetRect), obj->Rotation, NULL, SDL_FLIP_NONE);
}

bool BALL_CollideWithRect(Ball * obj, SDL_Rect * rect){
    if (!RECT_Collide(&(obj->TargetRect), rect)) return false;
    // Already returned with false if square ball hitbox didnt collide with rect
    SDL_Point ballCenter = BALL_GetCenter(obj);
    Vector center = (Vector) {.x = ballCenter.x, .y = ballCenter.y };
    Vector corner;
    bool left, right, top, bottom, yMid = false, xMid = false;
    left = (ballCenter.x) < (rect->x);
    right = (ballCenter.x) > (rect->x) + (rect->w);
    top = (ballCenter.y) < (rect->y);
    bottom = (ballCenter.y) > (rect->y) + (rect->h);
    yMid = !(top || bottom);
    xMid = !(left || right);
    if (yMid) // Hit left or right
        (obj->Momentum).x = -(obj->Momentum).x;
    if (xMid) // Hit bottom or top
        (obj->Momentum).y = -(obj->Momentum).y;
    if (xMid || yMid) return true;
    // double oldAngle = fmod((double)(VECTOR_GetRotation(obj->Momentum)), 360.0f);

    if (left) {
        corner.x = (rect->x);
    } else if (right) {
        corner.x = ((rect->x) + (rect->w));
    }     // Other case will not appear since returned above
    if (top) {
        corner.y = (rect->y);
    } else if (bottom) {
        corner.y = ((rect->y) + (rect->h));
    }
    // printf("Hit corner at %.0f|%.0f!\n", corner.x, corner.y);
    // printf("Ball center at %.0f|%.0f!\n", center.x, center.y);
// TODO: Reflection at lot
    (obj->Momentum) = VECTOR_GetScaledVectorFromTo(corner, center, (obj->Speed));
    // printf("New Magnitude = %.2f\n", VECTOR_GetMagnitude((obj->Momentum)));
    return true;
} /* BALL_CollideWithRect */

bool RECT_Collide(SDL_Rect * rect1, SDL_Rect * rect2){
    if ((rect1->x) + (rect1->w) < (rect2->x)) {
        return false;
    }
    if ((rect1->x) > (rect2->x) + (rect2->w)) {
        return false;
    }
    if ((rect1->y) + (rect1->w) < (rect2->y)) {
        return false;
    }
    if ((rect1->y) > (rect2->y) + (rect2->h)) {
        return false;
    }
    return true;
}

void BALL_SteerMomentum(Ball * obj, Paddle * paddle){
    double paddleHalfLen = ((double)((paddle->TargetRect).w) / 2.0f);
    double offset = (((obj->TargetRect).x) + (obj->Size)) - ((paddle->TargetRect).x + paddleHalfLen);

    offset /= paddleHalfLen;
    offset *= (paddle->SteeringAngle);
    DOUBLE_Constrain(&offset, -(paddle->SteeringAngle), (paddle->SteeringAngle));
    (obj->Momentum) = VECTOR_GetDirectionalUnitVector(offset);
}

void RECT_SetTargetPos(SDL_Rect * rect, Vector * Location){
    rect->x = (int)round(Location->x);
    rect->y = (int)round(Location->y);
}

SDL_Point BALL_GetCenter(Ball * obj){
    return (SDL_Point) {.x = ((obj->TargetRect).x) + (obj->Size), .y = ((obj->TargetRect).y) + (obj->Size) };
}

void BALL_Update(Ball * obj, Paddle * paddle, Block * blocks, int BlockCount){
    Vector oldMomentum = obj->Momentum;
    Vector oldLocation = obj->Location;
    SDL_Point ballCenter = BALL_GetCenter(obj);

    (obj->Rotation) += (obj->RotationValue); // No effect on physics
    (obj->Location) = VECTOR_Add((obj->Location), oldMomentum);

    if (RECT_Collide(&(obj->TargetRect), &(paddle->TargetRect))) {
        (obj->Location) = oldLocation;   // Maybe remove this
        BALL_SteerMomentum(obj, paddle); // Sets it to unit vector!
        // Following assumes that the paddle position was udated before the ball was updated
        // BUG/GLITCH: Make sure that the postition of the ball is not shifted into the borders of the game!
        while (RECT_Collide(&(obj->TargetRect), &(paddle->TargetRect))) { // Move away from rect in small steps
            (obj->Location) = VECTOR_Add((obj->Location), (obj->Momentum));
            RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location));
        }
        (obj->Momentum) = VECTOR_ChangeScaleTo((obj->Momentum), (obj->Speed));
    }
    for (size_t i = 0; i < BlockCount; i++) {
        if (blocks[i].HP <= 0) continue;
        oldMomentum = obj->Momentum;
        oldLocation = obj->Location;
        if (BALL_CollideWithRect(obj, &(blocks[i].TargetRect))) {
            BLOCK_DealDamage(blocks + i, 1);
            (obj->Location) = VECTOR_Add(oldLocation, (obj->Momentum));
            RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location));
        }
    }

    if ((obj->Location).y > BREAKOUT_BoxHeight)                                                       // Collide with box boundaries
        (obj->Location) = (Vector) {.x = BREAKOUT_BoxWidth / 2 + 300, .y = BREAKOUT_BoxHeight / 2 };  // Dead
    else if ((obj->Location).y < 0.0f)
        (obj->Momentum).y = -(obj->Momentum).y;
    if ((obj->Location).x < 0.0f || (obj->Location).x > BREAKOUT_BoxWidth - (2 * (obj->Size)))
        (obj->Momentum).x = -(obj->Momentum).x;
    RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location));
} /* BALL_Update */

void BALL_DestroyObject(Ball * obj){
}

void BALL_Deinitialize(){
    if (BALL_IsInit) {
        printf("De-initializing Ball...\n");
        SDL_DestroyTexture(BALL_Texture);
        printf("Ball de-initialized!\n");
        BALL_IsInit = false;
    } else printf("Ball is already de-initialized!\n");
}

void PADDLE_Initialize(SDL_Renderer * renderer){
    if (!PADDLE_IsInit) {
        printf("Initializing Paddle...\n");
        PADDLE_Texture = IMG_LoadTexture(renderer, PADDLE_TexturePath);
        if (!PADDLE_Texture) printf("Paddle texture failed to load!\n");
        PADDLE_SourceRects = (SDL_Rect *)malloc(1 * sizeof(SDL_Rect));
        if (!PADDLE_SourceRects) printf("FATAL! Memory allocation failed!\n");
        PADDLE_SourceRects[0] = (SDL_Rect) {.x = 0, .y = 0, .w = 1000, .h = 100 };
        PADDLE_MoveLeftKeys = (Uint8 *)malloc(2 * sizeof(Uint8));
        if (!PADDLE_MoveLeftKeys) printf("FATAL! Memory allocation failed!\n");
        PADDLE_MoveRightKeys = (Uint8 *)malloc(2 * sizeof(Uint8));
        if (!PADDLE_MoveRightKeys) printf("FATAL! Memory allocation failed!\n");
        PADDLE_MoveLeftKeys[0] = 2; // Erster wert gibt größe des arrays an
        PADDLE_MoveLeftKeys[1] = SDL_SCANCODE_LEFT;
        PADDLE_MoveLeftKeys[2] = SDL_SCANCODE_A;
        PADDLE_MoveRightKeys[0] = 2;
        PADDLE_MoveRightKeys[1] = SDL_SCANCODE_RIGHT;
        PADDLE_MoveRightKeys[2] = SDL_SCANCODE_D;
        printf("Paddle initialized!\n");
        PADDLE_IsInit = true;
    } else printf("Paddle is already initialized!\n");
} /* PADDLE_Initialize */

Paddle PADDLE_CreateDefault(){
    int defaultpaddlewidth = 300;

    return (Paddle) {
               .TargetRect = (SDL_Rect) {.x = (BREAKOUT_BoxWidth -  defaultpaddlewidth) / 2, .y = BREAKOUT_BoxHeight - 100, .w = defaultpaddlewidth, .h = 30 },
               .TextureIndex = 0,
               .Speed = 10,
               .SteeringAngle = 40.0f,
               .Mode = KeyboardControl
    }; // Objekt für die Eigenschaften des Balls
}

void PADDLE_Draw(SDL_Renderer * renderer, Paddle * obj){
    // printf("Paddle drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).x);
    SDL_RenderCopy(renderer, PADDLE_Texture, PADDLE_SourceRects + (obj->TextureIndex), &(obj->TargetRect));
    // SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    // SDL_RenderDrawRect(renderer, &(obj->TargetRect));
}

bool KeyPressed(const Uint8 * keystate, Uint8 * keyArray){
    for (int i = 0; i < (*keyArray); i++) {
        if (keystate[keyArray[(i + 1)]]) return true;
    }
    return false;
}

void INT_Constrain(int * variable, int min, int max){
    if (*variable > max)
        *variable = max;
    else if (*variable < min)
        *variable = min;
}

void DOUBLE_Constrain(double * variable, double min, double max){
    if (*variable > max)
        *variable = max;
    else if (*variable < min)
        *variable = min;
}

void PADDLE_Update(Paddle * obj, const Uint8 * keystate){
    bool leftKeyPressed = false, rightKeyPressed = false;
    int paddleXMid = (obj->TargetRect).x + ((obj->TargetRect).w / 2);
    int mouseX;

    switch (obj->Mode) {
        case MouseControl:
            SDL_GetMouseState(&mouseX, NULL);
            if (abs(mouseX - paddleXMid) > (obj->Speed)) {
                if (mouseX > paddleXMid)
                    rightKeyPressed = true;
                else
                    leftKeyPressed = true;
            }
            break;
        case KeyboardControl:
            leftKeyPressed = KeyPressed(keystate, PADDLE_MoveLeftKeys);
            rightKeyPressed = KeyPressed(keystate, PADDLE_MoveRightKeys);
            break;
        default:
            printf("Unknown Paddle Control Mode: %d!\n", obj->Mode);
            break;
    }

    if (leftKeyPressed && (!rightKeyPressed)) {
        ((obj->TargetRect).x) -= (obj->Speed);
    } else if ((!leftKeyPressed) && rightKeyPressed) {
        ((obj->TargetRect).x) += (obj->Speed);
    }
    INT_Constrain(&((obj->TargetRect).x), 0, (BREAKOUT_BoxWidth - ((obj->TargetRect).w)));
} /* PADDLE_Update */

void PADDLE_DestroyObject(Paddle * obj){
}

void PADDLE_Deinitialize(){
    if (PADDLE_IsInit) {
        printf("De-initializing Paddle...\n");
        SDL_DestroyTexture(PADDLE_Texture);
        printf("Paddle de-initialized!\n");
        PADDLE_IsInit = false;
    } else printf("Paddle is already de-initialized!\n");
}

void BLOCK_Initialize(SDL_Renderer * renderer){
    if (!BLOCK_IsInit) {
        printf("Initializing Block...\n");
        BLOCK_Texture = IMG_LoadTexture(renderer, BLOCK_TexturePath);
        if (!BLOCK_Texture) printf("Block texture failed to load!\n");
        BLOCK_SourceRects = (SDL_Rect *)malloc(BLOCK_TextureCount * sizeof(SDL_Rect));
        if (!BLOCK_SourceRects) printf("FATAL! Memory allocation failed!\n");
        BLOCK_SourceRects[0] = (SDL_Rect) {.x = 2000, .y = 1500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[1] = (SDL_Rect) {.x = 2000, .y = 2000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[2] = (SDL_Rect) {.x = 2000, .y = 2500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[3] = (SDL_Rect) {.x = 0, .y = 3000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[4] = (SDL_Rect) {.x = 1000, .y = 3000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[5] = (SDL_Rect) {.x = 2000, .y = 3000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[6] = (SDL_Rect) {.x = 0, .y = 3500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[7] = (SDL_Rect) {.x = 1000, .y = 3500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[8] = (SDL_Rect) {.x = 2000, .y = 3500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[9] = (SDL_Rect) {.x = 0, .y = 500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[10] = (SDL_Rect) {.x = 2000, .y = 0, .w = 1000, .h = 500 };
        BLOCK_SourceRects[11] = (SDL_Rect) {.x = 0, .y = 1000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[12] = (SDL_Rect) {.x = 0, .y = 1500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[13] = (SDL_Rect) {.x = 1000, .y = 0, .w = 1000, .h = 500 };
        BLOCK_SourceRects[14] = (SDL_Rect) {.x = 1000, .y = 500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[15] = (SDL_Rect) {.x = 1000, .y = 1000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[16] = (SDL_Rect) {.x = 1000, .y = 1500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[17] = (SDL_Rect) {.x = 0, .y = 2000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[18] = (SDL_Rect) {.x = 1000, .y = 2000, .w = 1000, .h = 500 };
        BLOCK_SourceRects[19] = (SDL_Rect) {.x = 0, .y = 2500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[20] = (SDL_Rect) {.x = 1000, .y = 2500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[21] = (SDL_Rect) {.x = 0, .y = 0, .w = 1000, .h = 500 };
        BLOCK_SourceRects[22] = (SDL_Rect) {.x = 2000, .y = 500, .w = 1000, .h = 500 };
        BLOCK_SourceRects[23] = (SDL_Rect) {.x = 2000, .y = 1000, .w = 1000, .h = 500 };
        printf("Block initialized!\n");
        BLOCK_IsInit = true;
    } else printf("Block is already initialized!\n");
} /* PADDLE_Initialize */

Block BLOCK_CreateDefault() {
    return (Block) {
               .TargetRect = (SDL_Rect) {.x = 0, .y = 0, .w = 100, .h = 50 },
               .TextureIndex = (rand() % BLOCK_TextureCount),
               .HP = 1
    };   // Objekt für die Eigenschaften des Balls
}

void BLOCK_Draw(SDL_Renderer * renderer, Block * obj){
    if ((obj->HP) > 0) {
        // printf("Block drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).y);
        SDL_RenderCopy(renderer, BLOCK_Texture, (BLOCK_SourceRects + (obj->TextureIndex)), &(obj->TargetRect));
    }
}

void BLOCK_DealDamage(Block * obj, int dmg){
    if (((obj->HP) -= dmg) <= 0) printf("Block was destroyed!\n");
}

void BLOCK_Update(Block * obj){
// Do nothing currently
}
void BLOCK_DestroyObject(Block * obj){
}

void BLOCK_Deinitialize(){
    if (BLOCK_IsInit) {
        printf("De-initializing Block...\n");
        SDL_DestroyTexture(BLOCK_Texture);
        printf("Block de-initialized!\n");
        BLOCK_IsInit = false;
    } else printf("Block is already de-initialized!\n");
}
