#include<stdio.h>
#include<string.h>
#include<signal.h>

#ifdef DEBUG
#define DEBUGPRINT(s)          printf("%s", (s))
#define DEBUGPRINT2(s, s1)     printf("%s", (s), (s1))
#define DEBUGPRINT3(s, s1, s2) printf("%s", (s), (s1), (s2))
#else
#define DEBUGPRINT(s)
#define DEBUGPRINT2(s, s1)
#define DEBUGPRINT3(s, s1, s2)
#endif

#define APPEND_FILE_OPTION        'a'
#define OUTPUT_STDERR_OPTION      'e'
#define IGNORE_INTERRUPT_OPTION   'i'
#define HELP_OPTION               'h'
#define VERSION_OPTION            'v'
#define AUTHOR_OPTION             'A'

#define APPEND_FILE       0x01
#define IGNORE_INTERRUPT  0x02
#define OUTPUT_STDERR     0x04
#define USAGE_FLAG        0x08
#define HELP_FLAG         0x10
#define AUTHOR_FLAG       0x20
#define VERSION_FLAG      0x40

#define VERSION             "1.0.0"
#define AUTHOR              "Haruaki TAMADA"
#define AUTHOR_MAILADDRESS  "tamada@oikaze.com"


typedef struct filelist{
    char *value;
    char *name;
    FILE *f;
    struct filelist *next;
} list;

char *nopath(char *path){
    char *p;
    p = strrchr(path, '/');
    if(p != NULL) return p + 1;
    return path;
}

void version(char *prog){
    fprintf(stdout, "%s vesion %s\n", nopath(prog), VERSION);
}

void usage(char *prog){
    fprintf(stdout, "Usage: %s [-aeihvA] [file...]\n", nopath(prog));
}

void author(char *prog){
    fprintf(stdout, "%s author %s <%s>\n", nopath(prog), AUTHOR, AUTHOR_MAILADDRESS);
}

void help(char *prog){
    usage(prog);
    fprintf(stdout, "Copy stdin to each file and stdout/stderr\n\
    -a     append file\n\
    -e     output to stderr\n\
    -i     interrupted ignore\n\
    -h     print this message\n\
    -v     print version\n\
    -A     print author\n");
}

list *addlist(list *first, char *file){
    list *l;

    l = (list *)malloc(sizeof(list));
    l->name = (char *)malloc(sizeof(char *) * (strlen(file) + 1));
    strcpy(l->name, file);

    l->next = first;

    DEBUGPRINT2("%s add list\n", file);
    return l;
}

void openall(list *first, int option){
    list *current = first;
    char mode[2];
    mode[1] = '\0';
    if((option & APPEND_FILE) == APPEND_FILE) mode[0] = 'a';
    else                                      mode[0] = 'w';

    while(current != NULL){
        DEBUGPRINT3("%s open[%s]\n", current->name, mode);
        if((current->f = fopen(current->name, mode)) == NULL){
            perror(current->name);
        }
        DEBUGPRINT3("%s open done[%s]\n", current->name, mode);
        current = current->next;
    }
    DEBUGPRINT("open all\n");
}

void writeall(list *first, int data){
    list *current = first;

    while(current != NULL){
        fputc(data, current->f);
        current = current->next;
    }
}

void closeall(list *first){
    list *current = first;
    list *old;

    while(current->next != NULL){
        fclose(current->f);
        free(current->name);
        old = current;
        current = current->next;
        free(old);
    }
    free(current);
    DEBUGPRINT("close all\n");
}

void tee(list *files, int option){
    int data;
    DEBUGPRINT2("tee [%x]\n", option);

    while((data = fgetc(stdin)) != -1){
        writeall(files, data);
        if((option & OUTPUT_STDERR) == OUTPUT_STDERR) fputc(data, stderr);
        else                                          fputc(data, stdout);
    }
}

void prepare(int argc, char **argv, int option){
    list *first;

    int i, j;
    int done = 0;

    first = NULL;
    for(i = 1; i < argc; i++){
        if(argv[i][0] != '-'){
            first = addlist(first, argv[i]);
            done = 1;
        }
    }

    if(done == 0){
        usage(argv[0]);
        exit(1);
    }

    DEBUGPRINT("add done\n");

    openall(first, option);
    DEBUGPRINT("open all\n");

    if((option & IGNORE_INTERRUPT) == IGNORE_INTERRUPT){
        DEBUGPRINT("ignore signals\n");
        signal(SIGINT,  SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
    }

    tee(first, option);

    if((option & IGNORE_INTERRUPT) == IGNORE_INTERRUPT){
        DEBUGPRINT("\nto default signals\n");
        signal(SIGINT,  SIG_DFL);
        signal(SIGQUIT, SIG_DFL);
        signal(SIGPIPE, SIG_DFL);
    }

}

int option(int argc, char **argv){
    int i, j;
    int option = 0;
    int exit_option = 0;

    for(i = 1; i < argc; i++){
        if(argv[i][0] == '-'){
            for(j = 1; j < strlen(argv[i]); j++){
                if(argv[i][j] == APPEND_FILE_OPTION)           option |= APPEND_FILE;
                else if(argv[i][j] == IGNORE_INTERRUPT_OPTION) option |= IGNORE_INTERRUPT;
                else if(argv[i][j] == OUTPUT_STDERR_OPTION)    option |= OUTPUT_STDERR;
                else if(argv[i][j] == HELP_OPTION){
                    if((exit_option & HELP_FLAG) != HELP_FLAG) help(argv[0]);
                    exit_option |= HELP_FLAG;
                    exit_option |= USAGE_FLAG;
                }
                else if(argv[i][j] == VERSION_OPTION){
                    if((exit_option & VERSION_FLAG) != VERSION_FLAG) version(argv[0]);
                    exit_option |= VERSION_FLAG;
                }
                else if(argv[i][j] == AUTHOR_OPTION){
                    if((exit_option & AUTHOR_FLAG) != AUTHOR_FLAG) author(argv[0]);
                    exit_option |= AUTHOR_FLAG;
                }
                else{
                    fprintf(stderr, "Unknown option: %c\n", argv[i][j]);
                    if((exit_option & USAGE_FLAG) != USAGE_FLAG) usage(argv[0]);
                    exit_option |= USAGE_FLAG;
                }
            }
        }
    }

    if(exit_option != 0) exit(0);

    return option;
}

int main(int argc, char *argv[]){
    int flag = option(argc, argv);

    prepare(argc, argv, flag);
}
