#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>

#include "canvas.h"

typedef struct Ball Ball;
typedef struct Paddle Paddle;

typedef enum state {InvalidSide, Left, Right} PaddleSide;

#include "program.h"




struct Ball {
    float  x; // Position: x-coordinate
    float  y; // Position: y-coordinate
    float vx; // Velocity: x direction
    float vy; // Velocity: y direction
};


struct Paddle {
    PaddleSide  side; // The side of paddle: Left or Right

    float  x; // Position: x-coordinate
    float  y; // Position: y-coordinate
    float vx; // Velocity: x direction
    float vy; // Velocity: y direction

    float height;
};



// NOTE ABOUT X AND Y:
//      Due to how the canvas is oriented, y is flipped 
//      compared to a normal coordinate-system:
//      Larger y means more towards the bottom, i.e. downwards.
//      x-coordinates are the same as normal:
//      Larger x means more towards the right.
//      Thus (0,0) is the top-left corner 
//      and (c->width-1, c->height-1) is the bottom-right corner.

// NOTE ABOUT PADDLE: 
//      The x and y-coordinates are the center of the paddle.
//      The height is the total height, thus half of the height
//      is above the y-coordinate and half is below.


int main(int argc, char** argv) {
    // Create a canvas of size 80x24
    // Access these numbers by c->width and c->height
    Canvas* c = canvas_create(80, 24);

    /*
     * The winner:
     *   0: No winner yet
     *  -1: Left paddle
     *   1: Right paddle
     */
    int winner = 0;


    // Create ball
    Ball* ball = create_ball();

    // Create paddles
    Paddle* pad_left  = create_paddle(c, Left);
    Paddle* pad_right = create_paddle(c, Right);

    // Put the ball in motion
    start_ball(c, ball);

    // Main loop
    while (winner == 0) {
        canvas_clear(c);

        // --- Update ---

        // Update ball
        update_ball(c, ball);
        update_paddle(c, pad_left,  ball);
        update_paddle(c, pad_right, ball);

        // Check for winning
        winner = check_winning(c, ball);


        // --- Draw ---

        // Draw ball
        draw_ball(c, ball);
        draw_paddle(c, pad_left);
        draw_paddle(c, pad_right);

        // Draw surrounding border
        canvas_rect(c, 0, 0, c->width-1, c->height-1);

        // If there is a winner, write it on the screen
        if (winner != 0) {
            declare_winner(c, winner);
        }

        // Draw the canvas to the terminal and wait for next frame
        canvas_present(c);
        usleep(ANIMATION_TIMEOUT);
    }
    return 0;
}


/*
 * Place the ball somewhere and put it in motion.
 */
void start_ball(Canvas* c, Ball* ball) {
    // TODO: Edit here
    // Choose where the ball starts and in what direction
    ball->x  = c->width/2;
    ball->y  = c->height/2;
    ball->vx = 0.0;
    ball->vy = 0.0;
}

/*
 * Update ball position and check for collision with walls
 */
void update_ball(Canvas* c, Ball* ball) {
    // Update ball with new position
    ball->x += ball->vx;
    ball->y += ball->vy;

    // TODO: Edit here

    // Check for hits against walls and bounce off

    // Bottom wall

    // Top wall

    // Right wall (optional)

    // Left wall (optional)
}

/*
 * Draw ball as 'o'.
 */
void draw_ball(Canvas* c, Ball* ball) {
    canvas_cell(c, to_int(ball->x), to_int(ball->y), 'o');
}

/*
 * Update paddle position, check for collision with walls and with the ball
 */
void update_paddle(Canvas* c, Paddle* paddle, Ball* ball) {
    // Request the AI to choose in which direction to move the paddle
    // And limit the speed such that it cannot just teleport to save the ball.
    float desired_vy = paddle_AI(c, paddle, ball);
    desired_vy = limit_topspeed(desired_vy, SPEED_LIMIT); // Speed limit is 0.1 by default

    // Update paddle position
    paddle->vx = 0;
    paddle->vy = desired_vy;
    paddle->x += paddle->vx;
    paddle->y += paddle->vy;

    // TODO: Edit here

    // Check for collision between paddle and floor/ceiling

    // Bottom wall

    // Top wall


    // Check for collision between paddle and ball
}

/*
 * Draws a paddle as a series of '#'.
 */
void draw_paddle(Canvas* c, Paddle* paddle) {
    float px = paddle->x;
    float py = paddle->y;
    int h = paddle->height/2;

    for (int y = to_int(py - h); y < to_int(py + h); y++) {
        canvas_cell(c, to_int(px), to_int(y), '#');
    }
}

/*
 * Paddle AI
 * Returns the desired y velocity the paddle should go in:
 *   Positive is downwards, negative is upwards.
 * The speed will be capped to 0.1 in either directions by update_paddle.
 *
 * The AI is somewhat naive:
 *   If the ball is above the paddle, move up with full speed.
 *   If the ball is below the paddle, move down with full speed.
 */
float paddle_AI(Canvas* c, Paddle* paddle, Ball* ball) {
    if (ball->y > paddle->y) {
        return  0.1;
    }
    if (ball->y < paddle->y) {
        return -0.1;
    }
    return 0;
}


/*
 * Check to see if someone has won
 * Return:
 *  -1: Left paddle won
 *   0: No winner yet
 *   1: Right paddle won
 */
int check_winning(Canvas* c, Ball* ball) {
    // TODO: Edit here

    // Check if ball touches right wall

    // Check if ball touches left wall

    return 0;
}

/*
 * Once a winner is found, write it in the center of the screen.
 */
void declare_winner(Canvas* c, int winner) {
    char* left_str  = "Left";
    char* right_str = "Right";
    char* winner_str = (winner == -1 ? left_str : right_str);
    char* win_label_str = "Wins!";

    int w = c->width/2;
    int h = c->height/2;

    canvas_text(c, w-strlen(winner_str)/2,    h,   winner_str);
    canvas_text(c, w-strlen(win_label_str)/2, h+1, win_label_str);
}



/* ===== Helper functions ===== */

float min(float a, float b) {
    return a < b ? a : b;
}

float max(float a, float b) {
    return a > b ? a : b;
}

float abs_val(float x) {
    return x < 0 ? -x : x;
}

float remap(float value, float istart, float istop, float ostart, float ostop) {
    return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}

float limit_topspeed(float x, float a) {
    return max(min(x, a), -a);
}

/*
 * Check if some (x,y) lie within some rectangle 
 * given by top-left corner and bottom-right corner
 */
bool intersect_rect(
    float obj_x,   float obj_y, 
    float rect_x1, float rect_y1,
    float rect_x2, float rect_y2) {

    if (rect_x1 <= obj_x && obj_x <= rect_x2) {
        if (rect_y1 <= obj_y && obj_y <= rect_y2) {
            return true;
        }
    }

    return false;
}



/* ===== Constructors ===== */

Ball* create_ball() {
    Ball* ball = calloc(1, sizeof(Ball));
    return ball;
}

Paddle* create_paddle(Canvas* c, PaddleSide side) {
    Paddle* paddle = calloc(1, sizeof(Paddle));
    paddle->side = side;

    // Place the paddles in the right places
    if (side == Left) {
        paddle->x = 2;
        paddle->y = c->height/2;
    } else if (side == Right) {
        paddle->x = c->width-3;
        paddle->y = c->height/2;
    } else {
        printf("Error: Invalid paddle side.\n");
        exit(1);
    }

    paddle->vx = 0;
    paddle->vy = 0;

    paddle->height = PADDLE_HEIGHT; // Paddle height is 8 by default

    return paddle;
}
