/*
 * All rights reserved, copyright (c) 2006, Mitzyuki IMAIZUMI
 * $Id: popd.c,v 1.1 2006/05/16 05:25:10 mitz Exp $
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <termios.h>
#include <regex.h>

#define	CURDIR		"."

#define DOWN		(rtn == 1 && buf[0] == 'j')
#define UP			(rtn == 1 && buf[0] == 'k')
#define RETURN		(rtn == 1 && buf[0] == '\n')
#define ESCAPE		(rtn == 1 && buf[0] == 0x1b)
#define SEARCH		(rtn == 1 && buf[0] == '/')
#define FORWARD		(rtn == 1 && buf[0] == 'n')
#define BACKWARD	(rtn == 1 && buf[0] == 'N')

#define	back(i) 	if(i) fprintf(tty, "\033[%dD", i)

typedef	struct	dirent_t{
	char				*name;
	struct	dirent_t	*chain[2];			/* 0: backword / 1: forward */
}	dirent_t;

/*
 * 端末の初期化
 */
FILE		*initial(struct termios *save)
{

	FILE			*fp = NULL;
	struct	termios	term;

	if((fp = fopen("/dev/tty", "r+"))){
		tcgetattr(fileno(fp), &term);
		*save = term;
		term.c_lflag &= ~ICANON;
		term.c_lflag &= ~ECHO;
		term.c_cc[VMIN] = 1;
		term.c_cc[VTIME] = 1;
		tcsetattr(fileno(fp), TCSANOW, &term);

		setbuf(fp, NULL);
	}

	return(fp);

}

/*
 * 終了処理
 */
int		final(FILE *fp, char *dir, struct termios *save, char  *msg, ...)
{

		/* 端末へのメッセージ出力 */
		if(msg){
			va_list	ap;

			va_start(ap, msg);
			vfprintf(fp, msg, ap);
			va_end(ap);
		}

		/* 端末モードの復帰 */
		if(fp && save)
			tcsetattr(fileno(fp), TCSANOW, save);

		/* 移動先ディレクトリの表示 */
		printf("%s\n", dir);

		return(0);
	
}

/*
 * ディレクトリリストを双方向チェーンに追加
 *	**start を利用する事により開始ノードのチェックを不要とする
 *	先頭/末尾のチェーンは自己参照とし NULL チェックを省略可とする
 */
dirent_t	*insert_node(char *file, dirent_t **start)
{

	char		buf[1024];
	FILE		*fp;
	dirent_t	**p = start,
				*q = NULL,
				*r = NULL;

	if((fp = fopen(file, "r"))){
		while(fgets(buf, sizeof(buf), fp)){
			r = (dirent_t *)malloc(sizeof(dirent_t));
			r->name = strdup(strtok(buf, "\n"));
			r->chain[0] = q;
			r->chain[1] = r;

			q = *p = r;
			p = &(r->chain[1]);
		}
		fclose(fp);
		(*start)->chain[0] = *start;
	}

	return(r);

}

/*
 * 正規表現による検索処理
 */
dirent_t	*regexp(dirent_t *cur, int direction, int len, regex_t *preg)
{

	while(cur != cur->chain[direction])
		if((cur = cur->chain[direction]))
			if(!regexec(preg, cur->name, 0, NULL, 0))
				return(cur);

	return(NULL);

}

/*
 * リニアサーチによる検索処理
 */
dirent_t	*linear(dirent_t *cur, int direction, int len, regex_t *preg)
{

	return(len ? cur->chain[direction] : cur);

}

/*
 * 検索パターンの取得処理
 */
int			getpattern(FILE *tty, char *pattern, size_t size)
{

	int		len = -1;

	while(1){
		read(fileno(tty), &pattern[++len], size);
		
		/* Backspace */
		if(pattern[len] == '\b'){
			pattern[len--] = '\0';
			if(len >= 0){
				pattern[len--] = '\0';
				back(1);
				fprintf(tty, "\033[K");
			}
		}

		/* Escape */
		else if(pattern[len] == 0x1b){
			back(len);							/* 入力データを消去 */

			return(0);
		}

		/* Enter */
		else if(pattern[len] == '\n'){
			pattern[len] = '\0';				/* '\n' 自体は削除 */

			return(len);
		}
		else
			putc(pattern[len], tty);
	}

}
	
int	main(int argc, char *argv[])
{

	int				rtn, 
					len = 0,
					direction = 0;
	char			buf[1024],
					pattern[1024];
	FILE			*tty;
	regex_t			preg;
	struct	termios	save;
	dirent_t		*cur = NULL,
					*start = NULL,
					*(*search)();

	/* ディレクトリリストの作成 */
	if(!(cur = insert_node(argv[1], &start)))
		exit(final(NULL, CURDIR, NULL, NULL));
	
	/* 端末の初期化 */
	if(!(tty = initial(&save)))
		exit(final(NULL, CURDIR, NULL, NULL));

	/* 検索文字が指定されている場合は検索モードで開始 */
	if(argc == 3){
		regcomp(&preg, argv[2], REG_EXTENDED);
		search = regexp;
		strcpy(pattern, argv[2]);
	}
	else{
		regcomp(&preg, ".*", REG_EXTENDED);
		search = linear;
	}

	/* メインループ */
	while(1){
		back(len);
		if((cur = (*search)(cur, direction, len, &preg)))
			len = fprintf(tty, "\033[0K%s", cur->name) - 4;
		else
			exit(final(tty, CURDIR, &save, "\033[0K%s : No mach\n", pattern));

		rtn = read(fileno(tty), buf, sizeof(buf));

		if(UP){
			direction = 0;
			search = linear;
		}
		else if(DOWN){
			direction = 1;
			search = linear;
		}
		else if(FORWARD){
			direction = 0;
			search = regexp;
		}
		else if(BACKWARD){
			direction = 1;
			search = regexp;
		}

		else if(SEARCH){
			back(len);
			fprintf(tty, "\033[0K/");

			if((len = getpattern(tty, pattern, sizeof(pattern)))){
				len++;						/* '/' 分 */
				regcomp(&preg, pattern, REG_EXTENDED);
				search = regexp;
			}
			else{
				back(1);					/* '/' のみ削除 */
				regcomp(&preg, ".*", REG_EXTENDED);
				search = linear;
			}
			direction = 0;
		}

		else if(RETURN)
			exit(final(tty, cur->name, &save, "\n"));

		else if(ESCAPE)
			exit(final(tty, CURDIR, &save, "\n"));
	}
}