#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// CMD 구조체 정의: 명령어 이름, 설명, 함수 포인터 포함
typedef struct CMD 
{
    char *name; // 명령어 이름
    char *desc; // 명령어 설명
    int (*cmd)(int argc, char *argv[]); // 명령어 실행 함수 포인터
} CMD;

// 함수 선언
int cmd_cd(int argc, char *argv[]);
int cmd_pwd(int argc, char *argv[]);
int cmd_exit(int argc, char *argv[]);
int cmd_help(int argc, char *argv[]);
int cmdProcessing(void);
int cmd_history(int argc, char *argv[]);
void save_cmd(const char *command);
void load_history();

// 내장 명령어 목록 정의
CMD builtin[] = {
    {"cd", "작업 디렉터리 바꾸기", cmd_cd},
    {"pwd", "현재 작업 디렉터리", cmd_pwd},
    {"exit", "셀 실행을 종료합니다", cmd_exit},
    {"help", "도움말 보여주기", cmd_help},
    {"history", "명령어 기록 보기", cmd_history},
};

// 내장 명령어 개수
const int builtins = sizeof(builtin) / sizeof(CMD);

// 상수 정의
#define STR_LEN 1024
#define MAX_TOKENS 128
#define HISTORY_FILE "history.txt"
#define MAX_HISTORY 100

// 명령 기록 저장 배열과 개수
char history[MAX_HISTORY][STR_LEN];
int history_count = 0;

// 프로그램의 메인 함수
int main(void) {
    int isExit = 0; // 종료 플래그
    load_history(); // 이전 기록 불러오기

    while (!isExit) {
        isExit = cmdProcessing(); // 명령 처리 반복
    }

    printf("My shell을 종료합니다\n");
    return 0;
}

// 명령어 처리 함수
int cmdProcessing(void) 
{
    char cmdLine[STR_LEN]; // 입력 명령어
    char *cmdTokens[MAX_TOKENS]; // 토큰화된 명령어
    char delim[] = " \t\n\r"; // 구분자
    char *token;
    int tokenNum, exitCode = 0;

    pid_t pid;
    int status;

    printf("[mysh v0.1] $ ");
    fgets(cmdLine, STR_LEN, stdin); // 명령어 입력

    save_cmd(cmdLine); // 명령 기록 저장

    tokenNum = 0;
    token = strtok(cmdLine, delim);
    while (token) {
        cmdTokens[tokenNum++] = token; // 토큰 저장
        token = strtok(NULL, delim);
    }
    cmdTokens[tokenNum] = NULL;

    if (tokenNum == 0) return exitCode; // 명령어가 없으면 반환

    for (int i = 0; i < builtins; ++i) {
        if (strcmp(cmdTokens[0], builtin[i].name) == 0) {
            return builtin[i].cmd(tokenNum, cmdTokens); // 내장 명령어 실행
        }
    }

    pid = fork(); // 외부 명령어 처리
    if (pid == 0) { // 자식 프로세스
        if (execvp(cmdTokens[0], cmdTokens) == -1) {
            perror("외부 명령 실행 실패");
        }
        exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        waitpid(pid, &status, 0); // 부모 프로세스: 자식 종료 대기
    }
    return exitCode;
}

// cd 명령어: 디렉터리 변경
int cmd_cd(int argc, char *argv[]) {
    if (argc < 2) {
        const char *home = getenv("HOME"); // 홈 디렉터리 경로 가져오기
        if (!home) {
            fprintf(stderr, "cd: 홈 디렉터리를 찾을 수 없습니다.\n");
            return 0;
        }
        if (chdir(home) != 0) {
            perror("cd");
        }
    } else {
        if (chdir(argv[1]) != 0) {
            perror("cd");
        }
    }
    return 0;
}

// pwd 명령어: 현재 디렉터리 출력
int cmd_pwd(int argc, char *argv[]) {
    char cwd[STR_LEN];
    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        printf("%s\n", cwd);
    } else {
        perror("pwd");
    }
    return 0;
}

// exit 명령어: 셸 종료
int cmd_exit(int argc, char *argv[]) {
    return 1; // 종료 플래그 반환
}

// help 명령어: 명령어 설명 출력
int cmd_help(int argc, char *argv[]) {
    if (argc == 2) {
        for (int i = 0; i < builtins; ++i) {
            if (strcmp(argv[1], builtin[i].name) == 0) {
                printf("%s: %s\n", builtin[i].name, builtin[i].desc);
                return 0;
            }
        }
        printf("'%s' 명령어를 찾을 수 없습니다.\n", argv[1]);
    } else {
        printf("지원하는 명령어 목록:\n");
        for (int i = 0; i < builtins; ++i) {
            printf("  %s: %s\n", builtin[i].name, builtin[i].desc);
        }
    }
    return 0;
}

// history 명령어: 명령어 기록 출력
int cmd_history(int argc, char *argv[]) {
    if (argc == 1) {
        for (int i = 0; i < history_count; ++i) {
            printf("%d %s", i + 1, history[i]);
        }
    } else {
        printf("인자가 너무 많습니다 (관련 옵션 구현 안함)\n");
    }
    return 0;
}

// 명령어 기록 저장 함수
void save_cmd(const char *command) {
    if (history_count == MAX_HISTORY) {
        for (int i = 1; i < MAX_HISTORY; ++i) {
            strcpy(history[i - 1], history[i]);
        }
        --history_count;
    }
    strncpy(history[history_count++], command, STR_LEN);

    FILE *file = fopen(HISTORY_FILE, "w");
    if (!file) {
        perror("history 파일 열기 실패");
        return;
    }
    for (int i = 0; i < history_count; ++i) {
        fputs(history[i], file);
    }
    fclose(file);
}

// 명령어 기록 불러오기 함수
void load_history() {
    FILE *file = fopen(HISTORY_FILE, "r");
    if (!file) return;

    while (fgets(history[history_count], STR_LEN, file)) {
        history_count++;
        if (history_count >= MAX_HISTORY) break;
    }
    fclose(file);
}