// 优化昨天的学生信息管理系统，将STU的数据持久化到stu.data文件中
// 【提示】运行程序时，从文件中加载已有数据并生成链表，在退出程序时，将链表的数据更新到文件中。
#include <stdio.h>
#include <stdlib.h> // malloc
#include <string.h> // strcpy

typedef enum front_color_e
{
    BLACK = 30, // 黑色
    RED,        // 红色
    GREEN,      // 绿色
    YELLOW,     // 黄色
    BLUE,       // 蓝色
    PURPLE,     // 紫色
    CYAN,       // 青色
    WHITE,      // 白色
} Front_Color;  // 前景色

typedef enum back_color_e
{
    BLACK_B = 40, // 黑色
    RED_B,        // 红色
    GREEN_B,      // 绿色
    YELLOW_B,     // 黄色
    BLUE_B,       // 蓝色
    PURPLE_B,     // 紫色
    CYAN_B,       // 青色
    WHITE_B,      // 白色
} Back_Color;     // 背景色

typedef enum attr_e
{
    BOLD = 1,      // 加粗
    UNDERLINE = 4, // 下划线
    BLINK,         // 闪烁
    REVERSE = 7,   // 反显
    HIDE,          // 隐藏
} Attr;            // 文本属性

// 定义学生信息结构体,链表的节点
typedef struct stu_s
{
    int sid;
    char name[32];
    int age;

    struct stu_s *next;
} STU;

// 节点交换
void swap(STU *a, STU *b)
{
    // 方法一: 直接通过临时指针交换数据
    // STU *tp = (STU *)malloc(sizeof(STU));
    // tp->sid = a->sid;
    // strcpy(tp->name, a->name);
    // tp->age = a->age;

    // a->sid = b->sid;
    // strcpy(a->name, b->name);
    // a->age = b->age;

    // b->sid = tp->sid;
    // strcpy(b->name, tp->name);
    // b->age = tp->age;
    // free(tp); // 释放临时指针

    // 方法二: 通过结构体指针交换数据
    // 先换数据
    STU *tp = malloc(sizeof(STU));
    *tp = *a;
    *a = *b;
    *b = *tp;

    // 再换指针的下一个节点指向
    tp->next = a->next;
    a->next = b->next;
    b->next = tp->next;

    free(tp); // 释放临时指针
}

// 添加学生
STU *insert_stu(STU *head, STU *item)
{
    // 如果头指针为空,说明链表为空,直接将新建的节点作为头指针
    if (head == NULL)
    {
        head = item;
    }
    else
    {
        // 如果头指针不为空,说明链表不为空,需要找到链表的尾部,将新建的节点插入到尾部
        STU *p = head;          // 创建一个临时指针,用来保存头指针,防止头指针丢失
        while (p->next != NULL) // 当 p 的下一个节点不为空时,说明 p 不是尾部
        {
            p = p->next; // p 一直向后移动,直到找到尾部
        }
        p->next = item; // 将 item 插入到尾部
    }
    return head; // 返回头指针
}

// 删除学生
STU *delete_stu(STU *head, char sid)
{
    if (NULL == head)
    {
        printf("\033[%dm无数据,无法删除\033[0m\n", RED);
        return NULL;
    }
    if (head->sid == sid)
    {
        // *tp 为临时指针,用来保存头指针
        STU *tp = head;    // 保存头指针
        head = head->next; // 头指针指向下一个节点
        free(tp);          // 释放头指针
    }
    else
    {
        STU *tp = head;                                  // 保存头指针
        while (NULL != tp->next && sid != tp->next->sid) // 当 tp 的下一个节点不为空时,且下一个节点的学号不等于要删除的学号时,继续向后移动指针 tp
        {
            tp = tp->next; // tp 一直向后移动,直到找到尾部
        }
        if (NULL == tp->next) // 到达了链表尾部还是没有找到要删除的内容
        {
            printf("\033[%dm未找到 sid = %d 的学生,删除失败\033[0m\n", RED, sid);
        }
        else
        {
            STU *tpx = tp->next;  // 保存要删除的节点
            tp->next = tpx->next; // 将链表位置指向要删除的节点的下一个节点
            free(tpx);            // 释放要删除的节点
            printf("\033[%dm删除 sid = %d 的学生成功\033[0m\n", BLUE, sid);
        }
    }
    return head; // 返回头指针
}

// 打印学生
void shows(STU *head)
{
    // 如果头指针为空,说明链表为空,直接返回
    if (head == NULL)
    {
        printf("\033[%dm无数据,无法打印\033[0m\n", RED);
        return;
    }
    else
    {
        // 如果头指针不为空,说明链表不为空,需要遍历链表,打印每一个节点的信息
        STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
        // 清屏
        // printf("\033[2J");
        printf("--------------------\n");
        printf("\033[%dm学号\t姓名\t年龄\033[0m\n", BLUE_B);
        while (p != NULL) // 当 p 不为空时,说明 p 不是尾部,则打印 p 的信息
        {
            printf("\033[%dm%d\t%s\t%d\033[0m\n", BLUE, p->sid, p->name, p->age); // 打印 p 的信息
            p = p->next;                                                          // p 一直向后移动,直到找到尾部
        }
    }
}

// 查询学生
STU *find_stu(STU *head, char sid)
{
    if (NULL == head)
    {
        printf("\033[%dm无数据,无法查询\033[0m\n", RED);
        return NULL; // 返回空指针,说明没有找到要查询的节点
    }

    STU *tp = head;                            // 保存头指针
    while (NULL != tp->next && sid != tp->sid) // 当 tp 的下一个节点不为空时,且下一个节点的学号不等于要查询的学号时,继续向后移动指针 tp
    {
        tp = tp->next; // tp 一直向后移动,直到找到尾部
    }
    if (sid == tp->sid)
        return tp; // 返回当前节点的指针,即为要查询的节点
    return NULL;   // 返回空指针,说明没有找到要查询的节点
}

// 修改学生
STU *update_stu(STU *head, char sid)
{
    if (NULL == head)
    {
        printf("\033[%dm无数据,无法修改\033[0m\n", RED);
        return NULL;
    }

    STU *tp = find_stu(head, sid); // 查找要修改的节点
    if (tp == NULL)
    {
        printf("\033[%dm未找到 sid = %d 的学生\033[0m\n", RED, sid);
    }
    else
    {
        // 临时变量,用来保存要修改的学生信息
        char msid;
        char mname[32];
        int mage;

        // 输入要修改的内容
        printf("输入要修改的学生的 学号 姓名 年龄: ");
        scanf("%hhd %s %d", &msid, mname, &mage);
        tp->sid = msid;
        strcpy(tp->name, mname);
        tp->age = mage;
        // 修改成功
        printf("\033[%dm修改 sid = %d 的学生成功\033[0m\n", BLUE, sid);
    }
    return head; // 返回头指针
}

// 排序学生（选择排序，也可以使用归并排序）
STU *sort_stu(STU *head)
{
    if (NULL == head)
    {
        printf("\033[%dm无数据,无法排序\033[0m\n", RED);
        return NULL;
    }
    // 选择排序方式: 1) 按照sid 2)按照age
    int choose; // 选择排序方式
    printf("选择排序方式: 1) 按照sid 2)按照age: ");
    scanf("%d", &choose);
    if (1 == choose) // 按照sid排序
    {
        // 选择排序方式: 1) 升序 2)降序
        int choose2; // 选择排序方式
        printf("选择排序方式: 1) 升序 2)降序: ");
        scanf("%d", &choose2);
        if (1 == choose2) // 升序
        {
            // 按照sid升序排序
            STU *p = head;    // 创建一个临时指针,用来保存头指针,防止头指针丢失
            while (p != NULL) // 相当于 for (int i = 0; i < len; i++)
            {
                STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
                while (q != NULL) // 相当于 for (int j = i + 1; j < len; j++)
                {
                    if (p->sid > q->sid) // 相当于 if (arr[i] > arr[j])
                    {
                        swap(p, q); // 相当于 swap(arr[i], arr[j]
                    }
                    q = q->next;
                }
                p = p->next;
            }
        }
        else if (2 == choose2) // 降序
        {
            STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
            while (p != NULL)
            {
                STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
                while (q != NULL)
                {
                    if (p->sid < q->sid)
                    {
                        swap(p, q);
                    }
                    q = q->next;
                }
                p = p->next;
            }
        }
        else
        {
            // 选择错误
            printf("\033[%dm选择错误\033[0m\n", RED);
        }
    }
    else if (2 == choose) // 按照age排序
    {
        // 选择排序方式: 1) 升序 2)降序
        int choose2; // 选择排序方式
        printf("选择排序方式: 1) 升序 2)降序: ");
        scanf("%d", &choose2);
        if (1 == choose2) // 升序
        {
            STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
            while (NULL != p)
            {
                STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
                while (NULL != q)
                {
                    if (p->age > q->age)
                    {
                        swap(p, q);
                    }
                    q = q->next;
                }
                p = p->next;
            }
        }
        else if (2 == choose2) // 降序
        {
            STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
            while (NULL != p)
            {
                STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
                while (NULL != q)
                {
                    if (p->age < q->age)
                    {
                        swap(p, q);
                    }
                    q = q->next;
                }
                p = p->next;
            }
        }
        else
        {
            // 选择错误
            printf("\033[%dm选择错误\033[0m\n", RED);
        }
    }
    else
    {
        // 选择错误
        printf("\033[%dm选择错误\033[0m\n", RED);
    }
    return head; // 返回头指针
}

// 反序学生（头变尾，尾变头）
STU *reverse_stu(STU *head)
{
    if (NULL == head)
    {
        printf("\033[%dm无数据,无法反序\033[0m\n", RED);
        return NULL;
    }

    STU *pi = head->next; // 当前节点的下一个节点地址
    head->next = NULL;    // 把头节点的下一个节点置空，让头节点成为尾节点
    STU *tp = NULL;       // 当前节点的下一个节点地址 pi 的下一个节点地址
    while (pi != NULL)    // 当 pi 不为空时,说明 pi 不是尾部,则反序 pi 的信息
    {
        tp = pi->next;   // 首先 tp 保存 pi 的下一个节点地址
        pi->next = head; // 然后把 pi 的下一个节点地址指向 head
        head = pi;       // 然后让 hand 指向 pi
        pi = tp;         // 最后找到新的 pi，即 tp
    }

    printf("\033[%dm反序成功\033[0m\n", BLUE);
    return head; // 返回头指针
}

// 保存数据到文件
void saveToFile(STU *head, const char *filename)
{
    FILE *file = fopen(filename, "w");
    if (file == NULL)
    {
        printf("\033[%dm无法打开文件, 保存数据失败\033[0m", RED);
        return;
    }

    STU *current = head;    // 定义临时指针,用来保存头指针,防止头指针丢失
    while (current != NULL) // 当 p 不为空时,说明 p 不是尾部,则打印 p 的信息
    {
        // 格式化存储数据，每个字段之间用逗号分隔
        fprintf(file, "%d,%s,%d\n", current->sid, current->name, current->age);
        current = current->next; // p 一直向后移动,直到找到尾部
    }
    printf("保存数据成功\n");
    fclose(file);
}

// 从文件中读取数据
STU *readFromFile(const char *filename)
{
    FILE *file = fopen(filename, "r");
    if (file == NULL)
    {
        printf("\033[%dm无法打开文件, 加载数据失败\033[0m", RED);
        return NULL;
    }

    STU *head = NULL; // 定义头指针
    STU *prev = NULL; // 定义尾指针
    char line[100];   // 100 个字符的数组,用来保存每一行的数据

    while (fgets(line, sizeof(line), file) != NULL) // 逐行读取文件内容
    {
        STU *node = (STU *)malloc(sizeof(STU)); // 申请一个节点的内存, 用来保存学生信息
        /*
        %d :表示读取一个整数，并将其存储在 &(node->sid)的地址中，即学生的学号。 
        , :表示读取一个逗号，用于分隔学号和姓名之间的字段。 
        %[^,] :表示读取一个字符串，直到遇到逗号为止，并将其存储在 node -> name 中，即学生的姓名。[^,] 表示匹配除逗号以外的任意字符。 
        , :表示读取一个逗号，用于分隔姓名和年龄之间的字段。 
        %d :表示读取一个整数，并将其存储在 &(node->age)的地址中，即学生的年龄。
        */
        sscanf(line, "%d,%[^,],%d", &(node->sid), node->name, &(node->age)); // 将每一行的数据保存到节点中
        // fscanf() 每次执行后会丢失数据，所以需要使用 sscanf() 函数，原因是 fscanf() 函数会在每次执行后将文件指针向后移动，而 sscanf() 函数不会移动文件指针。
        // fscanf(file, "%d,%[^,],%d\n", &(node->sid), node->name, &(node->age)); // 将每一行的数据保存到节点中
        node->next = NULL; // 新建的节点的下一个节点指向空,因为是尾部插入

        if (head == NULL) // 当头指针为空时,说明链表为空,直接将新建的节点作为头指针
        {
            head = node;
        }
        else // 否则,说明链表不为空,需要找到链表的尾部,将新建的节点插入到尾部
        {
            prev->next = node; // 将新建的节点插入到尾部
        }
        prev = node; // 将尾指针指向新建的节点
    }

    fclose(file);
    printf("加载数据成功\n");
    return head;
}

int main(int argc, char const *argv[])
{
    STU *head = NULL; // 定义全局的头指针

    // 声明临时变量,用来保存学生信息
    char tsid;
    char tname[32];
    int tage;

    // // 创建几个学生信息，用于测试
    // STU *stu1 = (STU *)malloc(sizeof(STU));
    // stu1->sid = 1;
    // strcpy(stu1->name, "张三");
    // stu1->age = 18;
    // STU *stu2 = (STU *)malloc(sizeof(STU));
    // stu2->sid = 2;
    // strcpy(stu2->name, "李四");
    // stu2->age = 21;
    // STU *stu3 = (STU *)malloc(sizeof(STU));
    // stu3->sid = 3;
    // strcpy(stu3->name, "王五");
    // stu3->age = 15;
    // STU *stu4 = (STU *)malloc(sizeof(STU));
    // stu4->sid = 4;
    // strcpy(stu4->name, "赵六");
    // stu4->age = 13;
    // head = insert_stu(head, stu1);
    // head = insert_stu(head, stu2);
    // head = insert_stu(head, stu3);
    // head = insert_stu(head, stu4);

    while (1)
    {
        // printf("\033[2J");
        // printf("--------------------\n");
        printf("\t\t    \033[%d;%dm学生管理系统\033[0m\n\n", BOLD, CYAN_B);
        printf("\033[%d;%dm1) 添加学生 \t2) 删除学生 \t3) 打印  \n4) 查询  \t5) 修改 \t6) 排序  \n7) 反序  \t8) 加载数据  \t0) 退出(保存数据)\n请输入选项# \033[0m", BOLD, GREEN);
        int cmd = 0;
        scanf("%d", &cmd);
        switch (cmd)
        {
        case 0:
            // store_data(head); // 存储数据
            saveToFile(head, "stu.date");
            return 1;
        case 1:
            printf("输入学生的 学号 姓名 年龄: ");
            // 键盘收集数据并动态插入链表
            scanf("%hhd %s %d", &tsid, tname, &tage); // 键盘收集数据
            STU *item = (STU *)malloc(sizeof(STU));   // 申请一个节点的内存, 用来保存学生信息
            item->sid = tsid;                         // 保存学生学号到新建的节点
            strcpy(item->name, tname);                // 保存学生姓名到新建的节点
            item->age = tage;                         // 保存学生年龄到新建的节点
            item->next = NULL;                        // 新建的节点的下一个节点指向空,因为是尾部插入
            head = insert_stu(head, item);            // 将新建的节点插入到链表中
            break;
        case 2:
            printf("输入要删除的学生的学号: ");
            scanf("%hhd", &tsid);
            head = delete_stu(head, tsid);
            break;
        case 3:
            shows(head);
            break;
        case 4:
            printf("输入要查询的学生的学号: ");
            scanf("%hhd", &tsid);
            head = find_stu(head, tsid);
            if (head == NULL)
            {
                printf("\033[%dm未找到 sid = %d 的学生\033[0m\n", RED, tsid);
            }
            else
            {
                printf("查询到的学生信息如下: \n");
                printf("\033[%dm学号\t姓名\t年龄\033[0m\n", BLUE_B);
                printf("\033[%dm%d\t%s\t%d\033[0m\n", BLUE, head->sid, head->name, head->age);
            }
            break;
        case 5:
            printf("输入要修改的学生的学号:");
            scanf("%hhd", &tsid);
            head = update_stu(head, tsid);
            break;
        case 6:
            printf("排序学生信息:\n");
            head = sort_stu(head);
            break;
        case 7:
            printf("反序学生信息:\n");
            head = reverse_stu(head);
            break;
        case 8:
            printf("加载数据......\n");
            head = readFromFile("stu.date");
            break;
        default:
            printf("\033[%dm选择错误\033[0m\n", RED);
            break;
        }
    }
    return 0;
}
