import raylib;

import std.string: toStringz;				// for converting D string into C string
import std.random: uniform;				// random number generator

immutable int windowSize = 320;				// window width and height
immutable int blockSize = 16;				// entity size

enum GameStates { play, exit }				// game states
enum Direction { up, down, left, right }		// direction

// Fruit struct -> generates a fruit at random location
struct Fruit {
	Rectangle rect;			// rectangle

	bool eaten = true;		// if eaten == false, generate new fruit

	void generateFruit() {
		// randomize fruit's position
		rect = Rectangle(uniform(0, windowSize/blockSize)*blockSize,
							uniform(0, windowSize/blockSize)*blockSize, blockSize, blockSize);
	}

	// update the fruit
	void update() {
		if(eaten) {
			generateFruit();
			eaten = false;
		}
	}

	// draw the fruit, if eaten, skip
	void render() {
		if(eaten) {
			return;
		}

		DrawRectangleRec(rect, Colors.GREEN);
	}
}

// Snake struct -> takes care of snake entity (updating snake logic, growing, movement, etc...)
struct Snake {
	Rectangle[100] rect = Rectangle(-blockSize, -blockSize, blockSize, blockSize);	// the snake: array of maximum snake length
	Direction dir = Direction.up;							// direction

	int length = 5;									// snake length (max is windowSize^2 = 400 entities (16x16 size))

	// init
	this(int posX, int posY) {
		rect[0] = Rectangle(posX, posY, blockSize, blockSize);
	}

	// game logic
	void update() {
		// moving the tail
		for(int i = length; i > 0; i--) {
			rect[i].x = rect[i-1].x;
			rect[i].y = rect[i-1].y;
		}

		// moving the snake head
		if(dir == Direction.up) {
			rect[0].y -= blockSize;
		} else if(dir == Direction.down) {
			rect[0].y += blockSize;
		} else if(dir == Direction.left) {
			rect[0].x -= blockSize;
		} else if(dir == Direction.right) {
			rect[0].x += blockSize;
		}

		// keeping the snake within the bounds of our window
		if(rect[0].x < 0) {
			rect[0].x = windowSize-blockSize;
		} else if (rect[0].x > windowSize) {
			rect[0].x = 0;
		} else if(rect[0].y < 0) {
			rect[0].y = windowSize-blockSize;
		} else if (rect[0].y > windowSize) {
			rect[0].y = 0;
		}
	}

	// drawing
	void render() {
		for(int i = 0; i < length; i++) {
			DrawRectangleRec(rect[i], Colors.RED);
		}
	}

        // grow tail
	void grow() {
		length++;
	}
}

// Game struct -> manages gameplay logic, events, drawing
struct Game {
    GameStates gstate = GameStates.play;

	Snake snake = Snake(windowSize/2, windowSize/2);
	Fruit fruit = Fruit();

	bool gameOver = false;
	double time = 0;

    // initializing the game
    this(string title) {
        InitWindow(windowSize, windowSize, title.toStringz);
        SetTargetFPS(30);
    }

    // freing resources in the end of the game
    ~this() {
        CloseWindow();
    }

    // processing game events
    void processEvents() {
        if(WindowShouldClose()) {
            gstate = GameStates.exit;
        } else if(IsKeyPressed(KeyboardKey.KEY_UP)) {
			snake.dir = Direction.up;
		} else if(IsKeyPressed(KeyboardKey.KEY_DOWN)) {
			snake.dir = Direction.down;
		} else if(IsKeyPressed(KeyboardKey.KEY_LEFT)) {
			snake.dir = Direction.left;
		} else if(IsKeyPressed(KeyboardKey.KEY_RIGHT)) {
			snake.dir = Direction.right;
		}
    }

	// updating game logic
	void update() {
		if(!gameOver) {
			// update the snake
	        time += GetFrameTime();
			if(time > 0.12) {
				time = 0;

				// check whether the head of the snake has bumbed into its tail
				for(int i = 1; i < snake.length; i++) {
					if(snake.rect[0].x == snake.rect[i].x && snake.rect[0].y == snake.rect[i].y) {
						gameOver = true;
					}
				}

				// check if the snake has eaten the fruit
				if(snake.rect[0].x == fruit.rect.x && snake.rect[0].y == fruit.rect.y){
					fruit.eaten = true;

					snake.grow();
				}

				// update the fruit
				fruit.update();

				// update the snake
				snake.update();
			}
		}
    }

	// drawing
	void render() {
        BeginDrawing();
		ClearBackground(Colors.WHITE);

        // drawing the grid
        for(int i = 1; i < windowSize/blockSize; i++) {
            DrawLineEx(Vector2(blockSize*i, 0), Vector2(blockSize*i, windowSize), 1, Colors.BLACK);
            DrawLineEx(Vector2(0, blockSize*i), Vector2(windowSize, blockSize*i), 1, Colors.BLACK);
        }

		fruit.render();
		snake.render();

		if(gameOver) {
			DrawRectangleRec(Rectangle(0, 0, windowSize, windowSize), Color(0, 0, 0, 150));
			DrawText("Game Over!", 30, windowSize/2-48, 48, Colors.WHITE);
		}

		DrawFPS(10, 10);
        EndDrawing();
    }
}


void main() {
	// init
	Game game = Game("Dlang Snake Game");

	// game loop
	while(game.gstate != GameStates.exit) {
		game.processEvents();
		game.update();
		game.render();
	}
}