// mycp.c
// 파일 복사 프로그램: 파일 또는 여러 파일을 지정된 디렉토리로 복사
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>

// 단일 파일 복사 함수
void copy_file(const char *src, const char *dest) 
{
    // 소스 파일 열기 (읽기 전용)
    int src_fd = open(src, O_RDONLY);
    if (src_fd < 0) {
        perror("Error opening source file"); // 소스 파일 열기 실패 시 에러 메시지 출력
        return;
    }

    // 소스 파일 정보 가져오기
    struct stat src_stat;
    if (fstat(src_fd, &src_stat) < 0) {
        perror("Error getting file information"); // 파일 정보 가져오기 실패 시 에러 출력
        close(src_fd);
        return;
    }

    // 대상 파일 열기 (쓰기 전용, 없으면 생성, 내용 삭제)
    int dest_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, src_stat.st_mode);
    if (dest_fd < 0) {
        perror("Error opening destination file"); // 대상 파일 열기 실패 시 에러 출력
        close(src_fd);
        return;
    }

    // 버퍼를 사용해 파일 내용을 읽고 쓰기
    char buffer[1024];
    ssize_t bytes_read;
    while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
        if (write(dest_fd, buffer, bytes_read) != bytes_read) {
            perror("Error writing to destination file"); // 쓰기 실패 시 에러 출력
            break;
        }
    }

    // 읽기 실패 시 에러 출력
    if (bytes_read < 0) {
        perror("Error reading source file");
    }

    // 파일 디스크립터 닫기
    close(src_fd);
    close(dest_fd);
}

// 소스 파일을 지정된 디렉토리로 복사
void copy_to_directory(const char *src, const char *dest_dir) {
    struct stat statbuf;

    // 대상이 디렉토리인지 확인
    if (stat(dest_dir, &statbuf) < 0 || !S_ISDIR(statbuf.st_mode)) {
        fprintf(stderr, "%s is not a directory\n", dest_dir); // 디렉토리가 아니면 에러 출력
        return;
    }

    // 대상 경로 생성: 디렉토리 경로 + 소스 파일 이름
    char dest_path[1024];
    snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, strrchr(src, '/') ? strrchr(src, '/') + 1 : src);

    // 복사 실행
    copy_file(src, dest_path);
}

int main(int argc, char *argv[]) 
{
    // 인자가 부족하면 사용법 안내 메시지 출력
    if (argc < 3) {
        fprintf(stderr, "Usage: %s source_file(s) destination\n", argv[0]);
        return EXIT_FAILURE;
    }

    struct stat dest_stat; // 대상 경로의 상태를 저장하는 변수

    // 대상이 디렉토리일 경우
    if (stat(argv[argc - 1], &dest_stat) == 0 && S_ISDIR(dest_stat.st_mode)) 
    {
        // 소스 파일들을 대상으로 지정된 디렉토리로 복사
        for (int i = 1; i < argc - 1; i++) {
            copy_to_directory(argv[i], argv[argc - 1]);
        }
    } 
    // 대상이 디렉토리가 아니고, 파일 복사 하나만 수행하는 경우
    else if (argc == 3) 
    {
        copy_file(argv[1], argv[2]);
    } 
    // 다수의 파일을 복사하는 경우 대상이 디렉토리여야 함
    else 
    {
        fprintf(stderr, "Destination must be a directory when copying multiple files\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS; // 프로그램 정상 종료
}

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>      // 디렉터리 작업 관련 함수 (opendir, readdir, closedir)
#include <string.h>
#include <sys/stat.h>    // 파일 상태 정보 확인 (stat, lstat)
#include <unistd.h>      // 시스템 호출 및 파일 작업 관련 함수
#include <pwd.h>         // 사용자 정보 관련 함수 (getpwuid)
#include <grp.h>         // 그룹 정보 관련 함수 (getgrgid)
#include <time.h>        // 시간 정보 처리 (strftime)
#include <sys/ioctl.h>   // 터미널 크기 정보 확인 (ioctl)

#define MAX_ENTRIES 1024 // 한 디렉터리에서 처리 가능한 최대 파일 수

// 파일 정보를 저장하는 구조체
typedef struct 
{
    char filename[NAME_MAX]; // 파일 이름 (NAME_MAX: 시스템 정의 최대 파일 이름 길이)
    struct stat filestat;    // 파일 상태 정보 (lstat 함수로 가져옴)
} FileEntry;

// 파일 이름 오름차순 비교 함수
int name_compare(const void *a, const void *b) 
{
    FileEntry *entry1 = (FileEntry *)a;
    FileEntry *entry2 = (FileEntry *)b;
    return strcmp(entry1->filename, entry2->filename); // 사전순 비교
}

// 파일 이름 내림차순 비교 함수 (-r 옵션)
int name_reverse_compare(const void *a, const void *b) {
    return name_compare(b, a); // 오름차순 비교 결과를 반전
}

// 파일 이름을 한 줄로 출력하는 함수
void print_inline(FileEntry entries[], int count, int terminal_width) {
    int current_width = 0; // 현재 출력된 줄의 폭
    for (int i = 0; i < count; i++) {
        int name_length = strlen(entries[i].filename) + 1; // 파일 이름 길이 + 공백
        if (current_width + name_length > terminal_width) {
            printf("\n");  // 터미널 폭을 초과하면 줄 바꿈
            current_width = 0;
        }
        printf("%s ", entries[i].filename); // 파일 이름 출력
        current_width += name_length;
    }
    printf("\n"); // 마지막 줄 끝에 줄 바꿈
}

// 상세 정보 출력 (-l 옵션)
void print_detailed(FileEntry entry, int show_inode, int show_blocks, int show_type) {
    if (show_inode) {
        printf("%lu ", entry.filestat.st_ino); // inode 번호 출력
    }
    if (show_blocks) {
        printf("%ld ", entry.filestat.st_blocks); // 블록 크기 출력
    }

    // 파일 유형 출력
    printf("%c", S_ISDIR(entry.filestat.st_mode) ? 'd' :
                 S_ISLNK(entry.filestat.st_mode) ? 'l' : '-');

    // 파일 권한 출력
    for (int i = 8; i >= 0; i--) {
        printf("%c", (entry.filestat.st_mode & (1 << i)) ? 
                      ((i % 3 == 0) ? 'r' : (i % 3 == 1) ? 'w' : 'x') : '-');
    }

    // 하드 링크 수, 소유자, 그룹 출력
    printf(" %ld", entry.filestat.st_nlink);
    struct passwd *user = getpwuid(entry.filestat.st_uid);
    struct group *group = getgrgid(entry.filestat.st_gid);
    printf(" %s %s", user->pw_name, group->gr_name);

    // 파일 크기와 수정 시간 출력
    printf(" %ld", entry.filestat.st_size);
    char time_buf[20];
    strftime(time_buf, sizeof(time_buf), "%b %d %H:%M", localtime(&entry.filestat.st_mtime));
    printf(" %s", time_buf);

    // 파일 이름 출력
    printf(" %s", entry.filename);

    // 파일 유형 기호 추가
    if (show_type) {
        if (S_ISDIR(entry.filestat.st_mode)) printf("/");
        else if (entry.filestat.st_mode & S_IXUSR) printf("*");
        else if (S_ISLNK(entry.filestat.st_mode)) printf("@");
    }
    printf("\n");
}

// 디렉터리 내용을 출력하는 함수
void list_contents(const char *directory, int show_all, int reverse, int detailed, int recursive, 
                   int show_inode, int show_blocks, int show_type, int terminal_width) {
    struct dirent *dir_entry;       // 디렉터리 엔트리 구조체
    DIR *dir = opendir(directory); // 디렉터리 열기
    if (!dir) { // 디렉터리 열기 실패 시
        perror("opendir");
        return;
    }

    FileEntry entries[MAX_ENTRIES]; // 파일 정보 배열
    int count = 0;                  // 파일 수

    while ((dir_entry = readdir(dir)) != NULL) {
        if (!show_all && dir_entry->d_name[0] == '.') {
            continue; // 숨겨진 파일 제외 (-a 옵션 비활성화 시)
        }

        FileEntry entry;
        snprintf(entry.filename, sizeof(entry.filename), "%s", dir_entry->d_name);

        char full_path[PATH_MAX];
        snprintf(full_path, sizeof(full_path), "%s/%s", directory, dir_entry->d_name);

        if (lstat(full_path, &entry.filestat) == -1) { // 파일 상태 정보 가져오기
            perror("lstat");
            continue;
        }

        entries[count++] = entry; // 파일 정보 배열에 저장
    }
    closedir(dir); // 디렉터리 닫기

    // 파일 정렬 (오름차순 또는 내림차순)
    qsort(entries, count, sizeof(FileEntry), reverse ? name_reverse_compare : name_compare);

    // 상세 출력 또는 간단 출력
    if (detailed) {
        for (int i = 0; i < count; i++) {
            print_detailed(entries[i], show_inode, show_blocks, show_type);
        }
    } else {
        print_inline(entries, count, terminal_width);
    }

    // 재귀적으로 하위 디렉터리 탐색 (-R 옵션)
    if (recursive) {
        for (int i = 0; i < count; i++) {
            if (S_ISDIR(entries[i].filestat.st_mode) &&
                strcmp(entries[i].filename, ".") != 0 &&
                strcmp(entries[i].filename, "..") != 0) {
                printf("\n%s:\n", entries[i].filename); // 하위 디렉터리 이름 출력
                char next_directory[PATH_MAX];
                snprintf(next_directory, sizeof(next_directory), "%s/%s", directory, entries[i].filename);
                list_contents(next_directory, show_all, reverse, detailed, recursive, 
                              show_inode, show_blocks, show_type, terminal_width);
            }
        }
    }
}

// main 함수
int main(int argc, char *argv[]) {
    int show_all = 0, reverse = 0, detailed = 0, recursive = 0;
    int show_inode = 0, show_blocks = 0, show_type = 0;

    // 터미널 폭 계산
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    int terminal_width = w.ws_col;

    const char *directory = "."; // 기본 디렉터리는 현재 디렉터리

    // 명령줄 옵션 처리
    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') { // 옵션 처리
            for (int j = 1; argv[i][j] != '\0'; j++) {
                switch (argv[i][j]) {
                    case 'a': show_all = 1; break;
                    case 'r': reverse = 1; break;
                    case 'l': detailed = 1; break;
                    case 'R': recursive = 1; break;
                    case 'i': show_inode = 1; break;
                    case 's': show_blocks = 1; break;
                    case 'F': show_type = 1; break;
                    default:
                        fprintf(stderr, "Unknown option: -%c\n", argv[i][j]);
                        exit(EXIT_FAILURE);
                }
            }
        } else {
            directory = argv[i]; // 디렉터리 이름 설정
        }
    }

    // 디렉터리 내용 출력
    list_contents(directory, show_all, reverse, detailed, recursive, 
                  show_inode, show_blocks, show_type, terminal_width);

    return 0;
}