/*******************************************************************************************
*
*   raylib - sample game: snake
*
*   Sample game developed by Ian Eito, Albert Martos and Ramon Santamaria
*
*   This game has been created using raylib v1.3 (www.raylib.com)
*   raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
*   Copyright (c) 2015 Ramon Santamaria (@raysan5)
*
*
*   Expanded on this basis by Fan FEI
*
*
********************************************************************************************/

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


#if defined(PLATFORM_WEB)
    #include <emscripten/emscripten.h>
#endif

//----------------------------------------------------------------------------------
// Some Defines
//----------------------------------------------------------------------------------

#define SNAKE_LENGTH   256
#define SQUARE_SIZE     39

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
typedef struct Snake {
    Vector2 position;
    Vector2 size;
    Vector2 speed;
    Color color;
} Snake;

typedef struct Food {
    Vector2 position;
    Vector2 size;
    bool active;
    Color color;
} Food;

//------------------------------------------------------------------------------------
// Global Variables Declaration
//------------------------------------------------------------------------------------
static const int screenWidth = 1200; //Set the width of the game interface
static const int screenHeight = 1200; //Set the Height of the game interface

static int framesCounter = 0;
static bool gameOver = false;
static bool pause = false;

static Food fruit = { 0 };
static Food star = { 0 }; // Generate a reward
static Snake snake[SNAKE_LENGTH] = { 0 }; //Maximum length of snake
static Vector2 snakePosition[SNAKE_LENGTH] = { 0 }; //The coordinates of the snake body
static bool allowMove = false;
static Vector2 offset = { 0 };
static int counterTail = 0;

static int numWalls = 7; // Number of walls
static Rectangle walls[ 7 ];

Music soundtrack; // Add soundtrack

//------------------------------------------------------------------------------------
// Module Functions Declaration (local)
//------------------------------------------------------------------------------------
static void InitGame(void);         // Initialize game
static void UpdateGame(void);       // Update game (one frame)
static void DrawGame(void);         // Draw game (one frame)
static void UnloadGame(void);       // Unload game
static void UpdateDrawFrame(void);  // Update and Draw (one frame)
static void InitWalls(void);
static void DrawWalls(void);
static bool IsCoCollision(Vector2 coor);

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization (Note windowTitle is unused on Android)
    //---------------------------------------------------------
    InitWindow(screenWidth, screenHeight , "Raylib : snake By Fan FEI"); // Initialize the interface and title
    
    // Add soundtrack
    InitAudioDevice();
    char *music = "Music_snake_FanFEI.mp3";
    soundtrack = LoadMusicStream(music);
    PlayMusicStream(soundtrack);

    InitGame(); //Initialize the game
    InitWalls(); //Initialize the walls    

#if defined(PLATFORM_WEB)
    emscripten_set_main_loop(UpdateDrawFrame, 60, 1); // The number of frames can adjust the game speed
#else
    SetTargetFPS(60);
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update and Draw
        //----------------------------------------------------------------------------------
        UpdateDrawFrame();
        UpdateMusicStream(soundtrack);
        //----------------------------------------------------------------------------------
    }
#endif
    // De-Initialization
    //--------------------------------------------------------------------------------------
    UnloadGame();         // Unload loaded data (textures, sounds, models...)
    
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

//------------------------------------------------------------------------------------
// Module Functions Definitions (local)
//------------------------------------------------------------------------------------

// Initialize game variables
void InitGame(void)
{
    
    framesCounter = 0;
    gameOver = false; // Game state
    pause = false;
    
    counterTail = 2; // The initial length of the snake
    allowMove = false;

    offset.x = screenWidth%SQUARE_SIZE;
    offset.y = screenHeight%SQUARE_SIZE;

    for (int i = 0; i < SNAKE_LENGTH; i++)
    {
        snake[i].position = (Vector2){ offset.x/2, offset.y/2 };
        snake[i].size = (Vector2){ SQUARE_SIZE, SQUARE_SIZE };
        snake[i].speed = (Vector2){ SQUARE_SIZE, 0 }; // Initial direction to the right

        if (i == 0) snake[i].color = PINK; // Snake head color
        else if (i%3 != 0) snake[i].color = PURPLE; // Snake body color
        else snake[i].color = DARKPURPLE;
    }

    for (int i = 0; i < SNAKE_LENGTH; i++)
    {
        snakePosition[i] = (Vector2){ 0.0f, 0.0f };
    }

    fruit.size = (Vector2){ SQUARE_SIZE, SQUARE_SIZE }; // The color, coordinates and status of the fruit
    fruit.color = RED; // An apple maybe :)
    fruit.active = false;
    
    star.size = (Vector2){ SQUARE_SIZE, SQUARE_SIZE }; // The color, coordinates and status of the star
    star.color = GOLD;
    star.active = false;
}

// Update game (one frame)
void UpdateGame(void)
{
    if (!gameOver)
    {
        if (IsKeyPressed('P')){
            pause = !pause; // Keyboard input p pause
            SetMusicVolume(soundtrack, 1);
        }
        
        if (!pause)
        {
            // Player control
            // Only when the snake is moving in the vertical direction can it be turned horizontally
            if (IsKeyPressed(KEY_RIGHT) && (snake[0].speed.x == 0) && allowMove)
            {
                snake[0].speed = (Vector2){ SQUARE_SIZE, 0 };
                allowMove = false;
            }
            if (IsKeyPressed(KEY_LEFT) && (snake[0].speed.x == 0) && allowMove)
            {
                snake[0].speed = (Vector2){ -SQUARE_SIZE, 0 };
                allowMove = false;
            }
            if (IsKeyPressed(KEY_UP) && (snake[0].speed.y == 0) && allowMove)
            {
                snake[0].speed = (Vector2){ 0, -SQUARE_SIZE };
                allowMove = false;
            }
            if (IsKeyPressed(KEY_DOWN) && (snake[0].speed.y == 0) && allowMove)
            {
                snake[0].speed = (Vector2){ 0, SQUARE_SIZE };
                allowMove = false;
            }

            // Snake movement
            for (int i = 0; i < counterTail; i++) snakePosition[i] = snake[i].position;

            if ((framesCounter%5) == 0) // The snake advances by one step every five frames
            {
                for (int i = 0; i < counterTail; i++)
                {
                    if (i == 0)
                    {
                        snake[0].position.x += snake[0].speed.x;
                        snake[0].position.y += snake[0].speed.y;
                        allowMove = true;
                    }
                    else snake[i].position = snakePosition[i-1];
                }
            }

            // Wall behaviour

            if ((snake[0].position.x) > (screenWidth - offset.x))
                snake[0].position.x = offset.x/2;
            if ((snake[0].position.y) > (screenHeight - offset.y))
                snake[0].position.y = offset.y/2;
            if ((snake[0].position.x < 0))
                snake[0].position.x += (screenWidth - offset.x);
            if ((snake[0].position.y < 0))
                snake[0].position.y += (screenHeight - offset.y);

            for(int i = 0;i < numWalls;i++){
                if(CheckCollisionPointRec(snake[0].position, walls[i])){
                    gameOver = true;
                    }
            }
            
            // Collision with yourself
            for (int i = 1; i < counterTail; i++)
            {
                if ((snake[0].position.x == snake[i].position.x) && (snake[0].position.y == snake[i].position.y)) 
                    gameOver = true;
            }

            // Fruit position calculation
            if (!fruit.active)
            {
                fruit.active = true; //Randomly generate a fruit on the map
                fruit.position = (Vector2){ GetRandomValue(0, (screenWidth/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.x/2, GetRandomValue(0, (screenHeight/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.y/2 };

                for (int i = 0; i < counterTail; i++) //The position of the fruit will not overlap with the snake and walls
                {
                    while (((fruit.position.x == snake[i].position.x) && (fruit.position.y == snake[i].position.y)) || (IsCoCollision(fruit.position)))
                    {
                        fruit.position = (Vector2){ GetRandomValue(0, (screenWidth/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.x/2, GetRandomValue(0, (screenHeight/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.y/2 };
                    }
                }
            }
            
            if (!star.active || (framesCounter%300 == 0))
            {
                star.active = true; //Randomly generate a star on the map
                star.position = (Vector2){ GetRandomValue(0, (screenWidth/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.x/2, GetRandomValue(0, (screenHeight/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.y/2 };

                for (int i = 0; i < counterTail; i++) //The position of the star will not overlap with the snake and walls
                {
                    while (((star.position.x == snake[i].position.x) && (star.position.y == snake[i].position.y)) || (IsCoCollision(star.position)))
                    {
                        star.position = (Vector2){ GetRandomValue(0, (screenWidth/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.x/2, GetRandomValue(0, (screenHeight/SQUARE_SIZE) - 1)*SQUARE_SIZE + offset.y/2 };
                    }
                }
            }

            // Collision
            if ((snake[0].position.x < (fruit.position.x + fruit.size.x) && (snake[0].position.x + snake[0].size.x) > fruit.position.x) &&
                (snake[0].position.y < (fruit.position.y + fruit.size.y) && (snake[0].position.y + snake[0].size.y) > fruit.position.y))
            {
                snake[counterTail].position = snakePosition[counterTail - 1];
                counterTail += 1;
                fruit.active = false;
            }
            
            if ((snake[0].position.x < (star.position.x + star.size.x) && (snake[0].position.x + snake[0].size.x) > star.position.x) &&
                (snake[0].position.y < (star.position.y + star.size.y) && (snake[0].position.y + snake[0].size.y) > star.position.y))
            {
                snake[counterTail].position = snakePosition[counterTail - 1];
                counterTail += 2;
                star.active = false;
            }
            
            framesCounter++;
        }
    }
    else
    {
        StopMusicStream(soundtrack);
        if (IsKeyPressed(KEY_ENTER)) //Press enter to start the game
        {
            InitGame();
            gameOver = false;
            PlayMusicStream(soundtrack);
        }
    }
}

// Draw walls
void InitWalls(void){
    
    int randNum;
    int randDirection;
    int randX;
    int randY;
    int maxWalls = (screenWidth/SQUARE_SIZE)/4;
    int minWalls = 3;
    
    for(int i = 0;i < numWalls;i++){
        
        
        randNum = rand() % (maxWalls - minWalls) + minWalls;
        randX = rand() % (screenWidth/SQUARE_SIZE); // ((maxWalls ) - minWalls) + minWalls;
        randY = rand() % (screenWidth/SQUARE_SIZE); // ((maxWalls ) - minWalls) + minWalls;
            
        walls[i].x = randX*SQUARE_SIZE + offset.x/2;
        walls[i].y = randY*SQUARE_SIZE + offset.y/2;
           
        if(i%2 == 0){
            walls[i].width = (SQUARE_SIZE - 1);
            walls[i].height = (SQUARE_SIZE*randNum - 1);
        }else{
            walls[i].width = (SQUARE_SIZE*randNum - 1);
            walls[i].height = (SQUARE_SIZE - 1);
        }
        
        /*
        If i do not subtract 1 from the height and the width, 
        then after the judgment of function bool CheckCollisionPointRec(Vector2 point, Rectangle rec),
        a game bug will occur.
        */
    }
}

void DrawWalls(void){
    for(int i = 0;i < numWalls;i++){
        DrawRectangleRec(walls[i],GRAY);
    }
}

bool IsCoCollision(Vector2 coor){
    for(int i = 0;i < numWalls;i++){
        if(CheckCollisionPointRec(coor, walls[i])){
            return true;
        }
    }
    return false;
}

// Draw game (one frame)
void DrawGame(void)
{
    BeginDrawing();

        ClearBackground(RAYWHITE);
        
        if (!gameOver)
        {
            // Draw grid lines
            for (int i = 0; i < screenWidth/SQUARE_SIZE + 1; i++)
            {
                DrawLineV((Vector2){SQUARE_SIZE*i + offset.x/2, offset.y/2}, (Vector2){SQUARE_SIZE*i + offset.x/2, screenHeight - offset.y/2}, LIGHTGRAY);
            }

            for (int i = 0; i < screenHeight/SQUARE_SIZE + 1; i++)
            {
                DrawLineV((Vector2){offset.x/2, SQUARE_SIZE*i + offset.y/2}, (Vector2){screenWidth - offset.x/2, SQUARE_SIZE*i + offset.y/2}, LIGHTGRAY);
            }

            // Draw snake
            for (int i = 0; i < counterTail; i++) DrawRectangleV(snake[i].position, snake[i].size, snake[i].color);
            
            // Draw walls
            DrawWalls();
            
            // Draw fruit to pick
            DrawRectangleV(fruit.position, fruit.size, fruit.color);
            DrawRectangleV(star.position, star.size, star.color);
            
            // Show time and score
            DrawText(TextFormat("TIME  : %.1f s", (float)framesCounter/60), screenWidth-SQUARE_SIZE*8, SQUARE_SIZE, SQUARE_SIZE, DARKGRAY);
            DrawText(TextFormat("SCORE: %03d", counterTail-2), screenWidth-SQUARE_SIZE*8, SQUARE_SIZE*2, SQUARE_SIZE, DARKGRAY);

            // Show pause
            if (pause){
                DrawText("GAME PAUSED", screenWidth/2 - MeasureText("GAME PAUSED", 40)/2, screenHeight/2 - 50, 50, BLACK);
                DrawText(TextFormat("SCORE:    %03d", counterTail-2), screenWidth/2 - MeasureText("GAME PAUSED", 40)/2, screenHeight/2 - 100, 50, BLACK);
                SetMusicVolume(soundtrack, 0); // Set volume for music (1.0 is max level)
            }
        }
        else{
            DrawText("PRESS [ENTER] TO PLAY AGAIN", GetScreenWidth()/2 - MeasureText("PRESS [ENTER] TO PLAY AGAIN", 40)/2, GetScreenHeight()/2 - 50, 40, GRAY);
            DrawText(TextFormat("SCORE: %03d", counterTail-2), screenWidth/2 - MeasureText("SCORE: %03d", 40)/2, screenHeight/2 - 100, 40, GRAY);          
        }

    EndDrawing();
}

// Unload game variables
void UnloadGame(void)
{
    UnloadMusicStream(soundtrack);
    // TODO: Unload all dynamic loaded data (textures, sounds, models...)
}

// Update and Draw (one frame)
void UpdateDrawFrame(void)
{
    UpdateGame();
    DrawGame();
}
