#ifdef ENABLE_MODULE
import std;
#endif

#ifndef ENABLE_MODULE
#include <algorithm>
#include <iostream>
#include <conio.h>
#include <ctime>
#include <deque>
#include <fstream>
#include <string>
#include <sstream>
#include <Windows.h>
#include <vector>
#endif

#define WIDTH 40
#define HEIGHT 20

static const char numPattern[] = {'5', '2', '0', '1', '3', '1', '4'};

enum State {
    STOP = 1, UP, LEFT, DOWN, RIGHT
};
enum Mode {
    BASIC, FUN
};
struct Coordinate {
    int x, y;
    bool operator == (Coordinate a) {
        return x == a.x && y == a.y;
    }
};
struct User {
    std :: string username;
    std :: string password;
};

User currentUser;
bool loggedIn = false;

void gotoxy(int, int);
void setCursorVisible(bool);
void setColor(WORD);
std :: string getHiddenInput();
bool findUser(const std :: string&, std :: string&);
bool registerUser();
bool login();
void showLoginMenu();
void saveScoreToUser(int, const std :: string&, const std :: string&);
void showPostGameMenu();
bool isFood(Coordinate, const std :: vector < Coordinate>&);
void generateFoods(std :: vector < Coordinate>&, bool [WIDTH][HEIGHT], Mode);

int main() {
    // system("chcp 65001 > nul");
    setCursorVisible(false);
    srand((unsigned)time(NULL));
    while (true) {
        if (!loggedIn) {
            showLoginMenu();
        }
        system("cls");
        WORD defaultColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
        setColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
        gotoxy(0, 0);
        std :: cout << "Welcome to Snake Game!\n";
        setColor(defaultColor);
        std :: cout << "Select Difficulty:\n";
        std :: cout << "1. Hard\n";
        std :: cout << "2. Medium\n";
        std :: cout << "3. Easy\n";
        std :: cout << "4. Exit\n";
        std :: cout << "Press 1, 2, 3 or 4\n";
        int sleepTime = 75;
        std :: string difficulty = "Medium";
        bool whetherChoose = false;
        char choice;
        while (!whetherChoose) {
            choice = _getch();
            switch (choice) {
                case '1':
                    sleepTime = 60;
                    difficulty = "Hard";
                    whetherChoose = true;
                    break;
                case '2':
                    sleepTime = 75;
                    difficulty = "Medium";
                    whetherChoose = true;
                    break;
                case '3':
                    sleepTime = 90;
                    difficulty = "Easy";
                    whetherChoose = true;
                    break;
                case '4':
                    exit(0);
            }
        }
        system("cls");
        setColor(defaultColor);
        std :: cout << "Select Mode:\n";
        std :: cout << "1. Basic\n";
        std :: cout << "2. Fun\n";
        std :: cout << "Press 1 or 2\n";
        Mode mode = BASIC;
        whetherChoose = false;
        while (!whetherChoose) {
            choice = _getch();
            switch (choice) {
                case '1':
                    mode = BASIC;
                    whetherChoose = true;
                    break;
                case '2':
                    mode = FUN;
                    whetherChoose = true;
                    break;
            }
        }
        system("cls");
        bool gameOver = false;
        State state = STOP;
        std :: deque < Coordinate> snake;
        bool grid[WIDTH][HEIGHT] = {false};
        Coordinate start = {WIDTH / 2, HEIGHT / 2};
        snake.push_back(start);
        grid[start.x][start.y] = true;
        std :: vector < Coordinate> foods;
        generateFoods(foods, grid, mode);
        unsigned int score = 0;
        setColor(FOREGROUND_BLUE | FOREGROUND_INTENSITY);
        for (int i = 0; i < WIDTH + 2; i++)
            std :: cout << '#';
        std :: cout << '\n';
        for (int i = 0; i < HEIGHT; i++) {
            std :: cout << '#';
            for (int j = 0; j < WIDTH; j++)
                std :: cout << ' ';
            std :: cout << "#\n";
        }
        for (int i = 0; i < WIDTH + 2; i++)
            std :: cout << '#';
        std :: cout << "\nScore: 0";
        for (auto& f : foods) {
            gotoxy(f.x + 1, f.y + 1);
            setColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
            std :: cout << '@';
        }
        while (!gameOver) {
            if (_kbhit()) {
                char key = _getch();
                if (key == 0 || key == -32)
                    key = _getch();
                if ((key == 'a' || key == 75) && state != RIGHT)
                    state = LEFT;
                if ((key == 'd' || key == 77) && state != LEFT)
                    state = RIGHT;
                if ((key == 'w' || key == 72) && state != DOWN)
                    state = UP;
                if ((key == 's' || key == 80) && state != UP)
                    state = DOWN;
                if (key == 'q' || key == 'Q')
                    gameOver = true;
            }
            if (state == STOP) {
                Sleep(55);
                continue;
            }
            Coordinate head = snake.front();
            switch (state) {
                case LEFT: head.x--; break;
                case RIGHT: head.x++; break;
                case UP: head.y--; break;
                case DOWN: head.y++; break;
            }
            if (head.x < 0 || head.x >= WIDTH || head.y < 0 || head.y >= HEIGHT || grid[head.x][head.y]) {
                gameOver = true;
                break;
            }
            snake.push_front(head);
            grid[head.x][head.y] = true;
            for (size_t i = 0; i < snake.size(); i++) {
                auto& p = snake[i];
                gotoxy(p.x + 1, p.y + 1);
                if (i == 0)
                    setColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
                else setColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
                std :: cout << numPattern[i % 7];
            }
            bool eat = isFood(head, foods);
            if (eat) {
                score += 10;
                gotoxy(WIDTH + 3, HEIGHT + 2);
                setColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
                std :: cout << "Score: " << score;
                foods.erase(std :: remove(foods.begin(), foods.end(), head), foods.end());
                if (foods.empty()) {
                    generateFoods(foods, grid, mode);
                    for (auto& f : foods) {
                        gotoxy(f.x + 1, f.y + 1);
                        setColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
                        std :: cout << '@';
                    }
                }
            } else {
                Coordinate tail = snake.back();
                snake.pop_back();
                grid[tail.x][tail.y] = false;
                gotoxy(tail.x + 1, tail.y + 1);
                setColor(defaultColor);
                std :: cout << ' ';
            }
            Sleep(sleepTime);
        }
        setColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        setCursorVisible(true);
        gotoxy(0, HEIGHT + 4);
        std :: cout << "Game Over! Final score: " << score << "\n";
        saveScoreToUser(score, difficulty, (mode == BASIC ? "Basic" : "Fun"));
        showPostGameMenu();
    }
    return 0;
}

void gotoxy(int x, int y) {
    COORD coordinate = {(SHORT)x, (SHORT)y};
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coordinate);
}

void setCursorVisible(bool visible) {
    HANDLE tmp = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(tmp, &cursorInfo);
    cursorInfo.bVisible = visible;
    SetConsoleCursorInfo(tmp, &cursorInfo);
}

void setColor(WORD color) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}

std :: string getHiddenInput() {
    std :: string input;
    char ch;
    while ((ch = _getch()) != '\r') {
        if (ch == '\b') {
            if (!input.empty()) {
                input.pop_back();
                std :: cout << "\b \b";
            }
        } else {
            input.push_back(ch);
            std :: cout << '*';
        }
    }
    std :: cout << std :: endl;
    return input;
}

bool findUser(const std :: string& username, std :: string& storedPassword) {
    std :: ifstream file("users.txt");
    std :: string line;
    while (std :: getline(file, line)) {
        std :: stringstream ss(line);
        std :: string name, pwd;
        ss >> name >> pwd;
        if (name == username) {
            storedPassword = pwd;
            return true;
        }
    }
    return false;
}

bool registerUser() {
    std :: string username, password, confirm;
    std :: cout << "Enter new username: ";
    std :: cin >> username;
    std :: string dummy;
    if (findUser(username, dummy)) {
        std :: cout << "Username already exists!\n";
        return false;
    }
    std :: cout << "Enter password: ";
    password = getHiddenInput();
    std :: cout << "Confirm password: ";
    confirm = getHiddenInput();
    if (password != confirm) {
        std :: cout << "Passwords do not match!\n";
        return false;
    }
    std :: ofstream out("users.txt", std :: ios::app);
    out << username << " " << password << std :: endl;
    std :: cout << "Registration successful! Please login.\n";
    return true;
}

bool login() {
    std :: string username, password, storedPwd;
    for (int attempt = 1; attempt <= 3; ++attempt) {
        std :: cout << "Username: ";
        std :: cin >> username;
        std :: cout << "Password: ";
        password = getHiddenInput();
        if (findUser(username, storedPwd) && storedPwd == password) {
            currentUser.username = username;
            currentUser.password = password;
            loggedIn = true;
            std :: cout << "Login success. Welcome, " << username << "!\n";
            return true;
        } else {
            std :: cout << "Invalid username or password. (" << attempt << "/3)\n";
            if (attempt < 3)
                std :: cout << "Please try again.\n";
        }
    }
    std :: cout << "Return to the menu\n";
    Sleep(1000);
    return false;
}

void showLoginMenu() {
    while (!loggedIn) {
        setColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        std :: cout << "\n===== Snake Game =====\n";
        setColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
        std :: cout << "1. Login\n";
        std :: cout << "2. Register\n";
        std :: cout << "3. Exit\n";
        std :: cout << "Choice: ";
        char op = _getch();
        std :: cout << op << std :: endl;
        if (op == '1') {
            if (login())
                break;
        } else if (op == '2')
            registerUser();
        else if (op == '3')
            exit(0);
        else std :: cout << "Invalid choice.\n";
        Sleep(1000);
        system("cls");
    }
    system("cls");
}

void saveScoreToUser(int score, const std :: string& difficulty, const std :: string& mode) {
    std :: ofstream file("user_scores.txt", std :: ios :: app);
    if (file.is_open()) {
        time_t now = time(nullptr);
        tm* localTime = localtime(&now);
        char timeStr[20];
        strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localTime);
        file << "User: " << currentUser.username << "\n";
        file << "Score: " << score << "\n";
        file << "Difficulty: " << difficulty << "\n";
        file << "Mode: " << mode << "\n";
        file << "Time: " << timeStr << "\n";
        file << "----------------------------------------\n";
        file.close();
    }
}

void showPostGameMenu() {
    std :: cout << "1. Play again\n";
    std :: cout << "2. Change user\n";
    std :: cout << "3. Exit\n";
    std :: cout << "Choice: ";
    char op = _getch();
    std :: cout << op << std :: endl;
    if (op == '1') {} 
    else if (op == '2') loggedIn = false;
    else if (op == '3') exit(0);
    else {
        std :: cout << "Invalid choice.\n";
        showPostGameMenu();
    }
}

bool isFood(Coordinate head, const std :: vector < Coordinate>& foods) {
    for (const auto& f : foods)
        if (head == f) return true;
    return false;
}

void generateFoods(std :: vector < Coordinate>& foods, bool grid[WIDTH][HEIGHT], Mode mode) {
    int num = (mode == BASIC) ? 1 : (2 + rand() % 4);
    foods.clear();
    for (int i = 0; i < num; i++) {
        Coordinate food;
        do {
            food = {rand() % WIDTH, rand() % HEIGHT};
        } while (grid[food.x][food.y] || isFood(food, foods));
        foods.push_back(food);
    }
}
