/**********************************************************************
tac.c  2000年01月06日（木）03時00分  バージョン 1.0 
tac.c  2000年01月07日（金）17時30分  バージョン 1.1 文字列反転をオプションに
tac.c  2001年12月24日（月）15時05分  バージョン 1.2 作成者オプションを追加．
                                                    クリスマスイブに寂しく更新．

ファイル内容を逆に表示するコマンド（catの逆）
使用法：tac [-ahv] [-[n|N]r] [files...]

作成者：玉田春昭 <tamada@oikaze.com>
開発所要時間：１時間・・・こんなものか。
最新バージョン：http://oikaze.com/~tamada/Products/junk/tac.c
コンパイル:   gcc tac.c
   デバッグ： gcc -DDEBUG tac.c
   最適化:    gcc -O3 -o tac tac.c
**********************************************************************/
#include<stdio.h>   /* fopen, fclose, fgets, fprintf, fputc, NULL, FILE */
#include<stdlib.h>  /* malloc, free                                     */
#include<string.h>  /* strlen                                           */

#define BUFFERSIZE   256
#define VERSION      "1.1"
#define AUTHOR       "Haruaki TAMADA"
#define MAILADDRESS  "tamada@oikaze.com"
#define URL          "http://oikaze.com/~tamada/Products/junk/index.html"

/* 一行ずつ確保するリスト。
 * 一つ前のエントリを知っているので、逆に表示させるのも簡単♪
 */
typedef struct line_list{
    struct line_list *prev;
    struct line_list *next;
    char *value;
} buffer;

/* 使用法を表示する。 */
void usage(char *prog){
    fprintf(stderr, "使用法: %s [-ah[n|N]rv] [files...]\n", prog);
    exit(1);
}

void author \(char *prog){
    fprintf(stdout, "%s: 作者: %s <%s>\n\
最新バージョン: %s", prog, AUTHOR, MAILADDRESS, URL);
    exit(0);
}
     
/* バージョン表示する。 */
void version(char *prog){
    fprintf(stdout, "%s: バージョン \"1.1\"\n", prog, VERSION);
    exit(0);
}

/* ヘルプを表示する。 */
void help(char *prog){
    fprintf(stdout, "%s [-h[n|N]v] [files...]\n\
    -a:    作者を表示\n\
    -h:    ヘルプ（このメッセージ）を表示\n\
    -n:    行番号を付ける。番号は表示される行\n\
    -N:    行番号を付ける。番号は変更前の行番号\n\
    -r:    表示する文字列も反転させる\n\
    -v:    バージョンを表示\n\
    files: 逆に表示させるファイル。指定されなければ標準入力から読込む\n", prog);
    exit(0);
}

/* 返り値: 文字列の長さ
 * 改行文字が文字列に含まれていれば改行文字を出力
 * なぜなら、BUFFERSIZEより大きな文字列を呼んだ場合、改行文字が
 * ない部分で改行してしまう。
 */
int len_ret(char *value){
    int len = 0;
    while(*value != '\0'){
        if(*value == '\n') fputc('\n', stdout);
        value++;
        len++;
    }
    return len;
}

/* 文字列を逆に表示する。
 * その際、改行文字は表示させない。
 * val: 表示させる文字列
 * len: 文字列の長さ
 */
void showrev(char *val, int len){
    int i;
    for(i = len - 1; i >= 0; i--){
        if(val[i] != '\n') fputc(val[i], stdout);
    }
}

/* 文字列を順に表示する。
 * その際、改行文字は表示させない。
 * val: 表示させる文字列
 * len: 文字列の長さ
 */
void shownor(char *val, int len){
    int i;
    for(i = 0; i < len; i++){
        if(val[i] != '\n') fputc(val[i], stdout);
    }
}

/* ファイルを逆に表示する。
 * fp:   表示させるファイルへのポインタ
 * flag: 1 なら行番号を付け、行番号は正常に表示。
 *       2 なら行番号を付け、行番号は逆に表示。
 *       4 なら行の反転を行う。
 */
void tac(FILE *fp, int flag){
    int linenum = 0;
    int linestep;
    buffer *buf, *current;
    current = (buffer *)malloc(sizeof(buffer));
    current->prev = NULL;
#ifdef DEBUG
    fprintf(stdout, "tac\n");
#endif
    while(1){
        linenum++;
#ifdef DEBUG
        fprintf(stdout, "line number: %d\n", linenum);
#endif
        if((current->value = (char *)malloc(sizeof(char) * BUFFERSIZE)) == NULL){
            perror("memory");
            free(current);
            return;
        }
        if(fgets(current->value, BUFFERSIZE, fp) == NULL) break; 
        buf = (buffer *)malloc(sizeof(buffer));
        if(buf == NULL){
            perror("memory");
            free(current);
            return;
        }
        buf->prev = current;
        current = buf;
    }
    switch((flag & 3)){
    case 1:
        linenum = 1;
        linestep = 1;
        break;
    case 2:
        linestep = -1;
        break;
    }
    do{
       int len;
#ifdef DEBUG
        fprintf(stdout, "DEBUG: %s",  buf->value);             /* デバッグ用 */
#endif
        len = len_ret(buf->value); /* １行の文字数取得 ＆ 改行文字出力（あれば） */

        if((flag & 3) != 0) fprintf(stdout, "%6d ", linenum); /* 行番号表示 */
        linenum += linestep;

        if((flag & 4) == 4) showrev(buf->value, len);
        else                shownor(buf->value, len);
        buf = buf->prev;
        if(buf != NULL) free(buf->next);
    }
    while(buf != NULL);
    fputc('\n', stdout);
}

/* メイン関数
 * オプションを解析して実行するぞ。
 */
int main(int argc, char *argv[]){
    int i, j;
    int flag = 0;
    int realargc = 0;
    FILE *fp;
  
    for(i = 1; i < argc; i++){
#ifdef DEBUG
        fprintf(stdout, "argv[%d]: %s\n", i, argv[i]);              /* デバッグ用 */
#endif
        if(*argv[i] == '-'){
            for(j = 1; j < strlen(argv[i]); j++){
                if(argv[i][j] == 'v')      version(argv[0]);
                else if(argv[i][j] == 'h') help(argv[0]);
                else if(argv[i][j] == 'a') author(argv[0]);
                else if(argv[i][j] == 'r') flag |= 4;
                else if(argv[i][j] == 'n'){
                    if((flag&3)!=0) usage(argv[0]);
                    flag |= 1;
                }
                else if(argv[i][j] == 'N'){
                    if((flag & 3) != 0) usage(argv[0]);
                    flag |= 2;
                }
                else usage(argv[0]);
            }
        }
        else realargc++;
    }
#ifdef DEBUG
    fprintf(stdout, "flag: %d\n", flag);
#endif
    if(realargc == 0) tac(stdin, flag);
    else for(i = 1; i < argc; i++){
        if(*argv[i] != '-'){
            /* 表示するファイルが一つじゃなく、   */
            /* リダイレクトされていないなら       */
            /* ファイル名を表示する。             */
            if(realargc != 1 && isatty(fileno(stdout)) == 1)
                fprintf(stdout, "::::::::::::::\n%s\n::::::::::::::\n", argv[i]);
            if((fp = fopen(argv[i], "r")) == NULL){
                perror(argv[i]);
                continue;
            }
            tac(fp, flag);
            fclose(fp); 
        }
    }
    exit(0);
}
