/*****************************************************************************/
/* The development of this program is partly supported by IPA                */
/* (Information-Technology Promotion Agency, Japan).                         */
/*****************************************************************************/

/*****************************************************************************/
/*  gdavl.c - GUI disk allocation viewer                                     */
/*  Copyright: Copyright (c) Hitachi, Ltd. 2004-2007                         */
/*             Authors: Yumiko Sugita (yumiko.sugita.yf@hitachi.com),        */
/*                      Satoshi Fujiwara (sa-fuji@sdl.hitachi.co.jp)         */
/*                                                                           */
/*  This program is free software; you can redistribute it and/or modify     */
/*  it under the terms of the GNU General Public License as published by     */
/*  the Free Software Foundation; either version 2 of the License, or        */
/*  (at your option) any later version.                                      */
/*                                                                           */
/*  This program is distributed in the hope that it will be useful,          */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/*  GNU General Public License for more details.                             */
/*                                                                           */
/*  You should have received a copy of the GNU General Public License        */
/*  along with this program; if not, write to the Free Software              */
/*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA      */
/*****************************************************************************/

#include <gtk/gtk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include "gdavl.h"

#include "xpm/ublk.xpm"
#include "xpm/sblk.xpm"
#include "xpm/xblk.xpm"
#include "xpm/cblk.xpm"
#include "xpm/fblk.xpm"
#include "xpm/fblk_lf.xpm"
#include "xpm/fblk_lsf.xpm"

#include "xpm/nc_ublk.xpm"
#include "xpm/nc_sblk.xpm"
#include "xpm/nc_xblk.xpm"
#include "xpm/nc_cblk.xpm"
#include "xpm/nc_fblk.xpm"
#include "xpm/nc_fblk_lf.xpm"
#include "xpm/nc_fblk_lsf.xpm"



static const gchar	*note_data[] = {
	"unused blocks",
	"system blocks",
	"xattr/acl blocks",
	"continuous blocks (continuous file)",
	"continuous blocks (fragmented file)",
	"fragmented blocks (fragmented file)",
	"system fragmented blocks (fragmented file)",
};

/* must be same order to note_data */
static const gchar	**xpm_pathes[] = {
	ublk,
	sblk,
	xblk,
	cblk,
	fblk,
	fblk_lf,
	fblk_lsf,
};
static const gchar	**nc_xpm_pathes[] = {
	nc_ublk,
	nc_sblk,
	nc_xblk,
	nc_cblk,
	nc_fblk,
	nc_fblk_lf,
	nc_fblk_lsf,
};

static const gchar	*result_chk_tbl[] = {
	TAG_FSTYPE,
	TAG_MOUNT,
	TAG_PERCENT,
	TAG_BLOCKS,
	TAG_SBLOCKS,
	TAG_XBLOCKS,
	TAG_FBLOCKS,
	TAG_FRAGS,
	TAG_SFRAGS,
	TAG_CFILES,
	TAG_FFILES,
	TAG_DEPTH,
};


void display_err_dialog(gchar* msg) {
	GtkWidget	*dlg, *label, *btn;

	dlg = gtk_dialog_new();
	label = gtk_label_new(msg);
	btn = gtk_button_new_with_label("OK");
	gtk_signal_connect_object(GTK_OBJECT(btn), "clicked",
				  GTK_SIGNAL_FUNC(gtk_widget_destroy),
				  (gpointer)dlg);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), btn,
			   TRUE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), label,
			   TRUE, TRUE, 5);
  	gtk_window_set_title(GTK_WINDOW(dlg), "Error");
#ifdef	DEBUG
	gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
#endif

	gtk_widget_show_all(dlg);
}



gint on_version_close(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	if (app->version_dlg) {
		gtk_widget_destroy(app->version_dlg);
		app->version_dlg = NULL;
	}
	return FALSE;
}

gint on_version_delete(GtkWidget *w, GdkEvent *event, gpointer tmp) {
	on_version_close(w, tmp);
	return FALSE;
}

void display_version_dialog(gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	GtkWidget	*dlg, *label, *btn;
	GString		*ver = g_string_new("\nGDAVL\n\n");

	if (app->version_dlg)
		return;

	g_string_sprintfa(ver, " version %s \n %s ", GDAVL_VER, COPYRIGHT);
	dlg = gtk_dialog_new();
	app->version_dlg = dlg;

	label = gtk_label_new(ver->str);
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	btn = gtk_button_new_with_label("OK");
	gtk_signal_connect(GTK_OBJECT(btn), "clicked",
			   GTK_SIGNAL_FUNC(on_version_close), tmp);
	gtk_signal_connect(GTK_OBJECT(dlg), "delete_event",
			   GTK_SIGNAL_FUNC(on_version_delete), tmp);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), btn,
			   TRUE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), label,
			   TRUE, TRUE, 0);
  	gtk_window_set_title(GTK_WINDOW(dlg), "version");
#ifdef	DEBUG
	gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
#endif
	g_string_free(ver, TRUE);

	gtk_widget_show_all(dlg);
}



/* draw note */
gint draw_note(t_draw *note) {
	GdkGC		*gc = note->widget->style->black_gc;
	gint		i, x, y;

	gdk_draw_rectangle(note->pixmap,
			   note->widget->style->bg_gc[GTK_STATE_NORMAL],
			   TRUE, 0, 0, note->width, note->height);

	x = MARGIN;
	y = MARGIN;
	for (i = 0; i < MAX_BLK_ID; i++) {
		gdk_draw_pixmap(note->pixmap, gc, note->xpm[i], 0, 0,
				x, y, -1, -1);
		gdk_draw_string(note->pixmap, note->font, gc,
				x + 14, y + 10, note_data[i]);
		y += 14;
	}
	return FALSE;
}

gint on_expose_note(GtkWidget *w, GdkEventExpose *event, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	gdk_draw_pixmap(w->window,
			w->style->fg_gc[GTK_WIDGET_STATE(w)],
			app->note.pixmap,
			event->area.x, event->area.y,
			event->area.x, event->area.y,
			event->area.width, event->area.height);
	return FALSE;
}





guint get_page_ln(t_page* page, guint ln_per_page, gint ev_type) {

	switch (ev_type) {
	case EV_TOTAL_PAGE:
	case EV_FFILE_PAGE:
	case EV_FRAG_PAGE:
		/* do nothing */
		break;
	default:
		page->page = 0;
		page->page_max = (page->ln_all + ln_per_page - 1) / ln_per_page;
		page->ln_per_page = ln_per_page;
		break;
	}

	if (page->ln_all > ln_per_page) {
		if (page->page == page->page_max - 1)
			return page->ln_all - (ln_per_page * page->page);
		else
			return ln_per_page;
	}
	return page->ln_all;
}

void parse_tcnts(t_tcnts* tcnts) {
	gchar		*p = tcnts->s, *p_tmp, *p_max;
	t_bseq_dt	dt;
	guint		bcnt = 0;

	if (!p)		/* empty parse string */
		goto ERR_RETURN;

	if (tcnts->dts)	/* already parsed */
		return;

	tcnts->dts = g_array_new(TRUE, FALSE, sizeof(t_bseq_dt));

	p_max = p + strlen(p);
	for (;;) {
		p_tmp = strchr(p, ',');
		if (p_tmp) {
			if (p_tmp >= p_max)
				goto ERR_RETURN;
			*p_tmp = '\0';
		}
		switch (*p++) {
		case '.': dt.type = UBLK; break;
		case 'S':
		case 's':
		case 'i':
		case 'I': dt.type = SBLK; break;
		case 'E': dt.type = XBLK; break;
		case 'o': dt.type = CBLK; break;
		case 'x': dt.type = FBLK; break;
		case 'X': dt.type = FBLK_LF; break;
		case 'Y': dt.type = FBLK_LSF; break;
		default:
			goto ERR_RETURN;
		}
		dt.n = strtoul(p, NULL, 10);
		if (dt.n == ULONG_MAX)
			goto ERR_RETURN;
		bcnt += dt.n;

		g_array_append_val(tcnts->dts, dt);
		if (p_tmp)
			p = p_tmp + 1;
		else
			break;
	}
	tcnts->bcnt = bcnt;
	return;

ERR_RETURN:
	if (tcnts->dts) {
		g_array_free(tcnts->dts, TRUE);
		tcnts->dts = NULL;
	}
	return;
}

gint build_tcnts_dts(t_result *r, gint ev_type) {
	t_frag_info	*dt;

	switch (ev_type) {
	case EV_START:
		parse_tcnts(&r->fsys_tcnts);
		if (!r->fsys_tcnts.dts) {
			display_err_dialog("invalid block-count sequence.");
			return FALSE;
		}
		if (r->fb_tcnts.s) {
			parse_tcnts(&r->fb_tcnts);
			if (!r->fb_tcnts.dts) {
				display_err_dialog("invalid fbseq format.");
				return FALSE;
			}
		}
		break;
	case EV_FRAG_VIEW:
		if (r->ffile_row < 0)
			return TRUE;
		dt = (t_frag_info*)g_slist_nth_data(r->frag_info, r->ffile_row);
		parse_tcnts(&dt->tcnts);
		if (dt->tcnts.s && !dt->tcnts.dts) {
			display_err_dialog("invalid fragmented file info.");
			return FALSE;
		}
		parse_tcnts(&dt->tcnts_B);
		if (dt->tcnts_B.s && !dt->tcnts_B.dts) {
			display_err_dialog("invalid fragmented file info.");
			return FALSE;
		}
		break;
	}
	return TRUE;
}

#define	VROUND(v, offset, range)	\
	(v) + (range - (((v) - (offset)) & ((range) - 1)))

void check_diff_draw_area(guint start_1st, guint end_1st,
			  guint start_2nd, guint end_2nd,
			  gint bpl, t_page* page) {
	guint		end_max;

	page->start_bn = start_1st;
	end_1st = VROUND(end_1st, start_1st, bpl);
	start_2nd = VROUND(start_2nd, start_1st, bpl) - bpl;
	end_2nd = VROUND(end_2nd, start_1st, bpl);
	if (end_1st < start_2nd &&
	    (start_2nd - end_1st) / bpl > DIFF_MARGIN_LN) {
		page->ln_1st = (end_1st - start_1st) / bpl;
		page->start_bn_2nd = start_2nd;
		page->ln_all = page->ln_1st + DIFF_MARGIN_LN +
				(end_2nd - start_2nd) / bpl;
	} else {
		end_max = end_1st > end_2nd ? end_1st : end_2nd;
		page->ln_all = (end_max - start_1st) / bpl;
	}
}

gint check_draw_area(guint start_A, t_tcnts* tcnts_A,
		     guint start_B, t_tcnts* tcnts_B,
		     gint summary_cnt, t_draw* blks, gint ev_type) {
	guint		end_A, end_B;
	gint		bpl, width, height, blks_per_line = BLKS_PER_LINE;
	guint		ln, max_ln_per_page;
	t_page		*page = &blks->page;

	page->ln_1st = 0;
	end_A = start_A + tcnts_A->bcnt;
	if (tcnts_B) {
		bpl = blks_per_line / 2 * summary_cnt;
		end_B = start_B + tcnts_B->bcnt;
		if (start_A < start_B)
			check_diff_draw_area(start_A, end_A, start_B, end_B,
					     bpl, page);
		else
			check_diff_draw_area(start_B, end_B, start_A, end_A,
					     bpl, page);
	} else {
		bpl = blks_per_line * summary_cnt;
		page->start_bn = start_A;
		end_A = VROUND(end_A, start_A, bpl);
		page->ln_all = (end_A - start_A) / bpl;
	}

	if (ev_type != EV_TOTAL_PAGE && ev_type != EV_FRAG_PAGE)
		blks->bnum_width = gdk_string_width(blks->font, "9999999999");
	width = (BLK_WIDTH - 1)
			* (BLKS_PER_LINE + (tcnts_B? DIFF_MARGIN_BN : 0))
			+ 1 + blks->bnum_width + MARGIN * 3;
	max_ln_per_page =
		(MAX_DRAW_PIXEL - 1 - (MARGIN * 2)) / (BLK_HEIGHT - 1);
	ln = get_page_ln(page, max_ln_per_page, ev_type);
	height = (BLK_HEIGHT - 1) * ln + 1 + MARGIN * 2;

	if (blks->pixmap)
		gdk_pixmap_unref(blks->pixmap);
	blks->pixmap = gdk_pixmap_new(blks->widget->window, width, height, -1);
	blks->width = width;
	blks->height = height;

	return ln;
}

gint get_summarized_type(gint cur_type, gint new_type) {
	return (cur_type > new_type ? cur_type : new_type);
}

void get_summary_cnt(t_spin* summary) {
	gint		i, n, cnt;

	n = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(summary->widget));
	cnt = 1;
	for (i = 0; i < n; i++)
		cnt *= 2;
	summary->cnt = cnt;
}

void update_page_btn(t_page* p) {
	if (p->page_max > 1) {
		GString *s = g_string_sized_new(0);

		g_string_sprintfa(s, "(%d/%d)", p->page + 1, p->page_max);
		gtk_label_set_text(GTK_LABEL(p->info), s->str);
		g_string_free(s, TRUE);

		gtk_widget_set_sensitive(p->prev, (p->page > 0));
		gtk_widget_set_sensitive(p->next, (p->page < p->page_max - 1));
		gtk_widget_show_all(p->box);
	} else
		gtk_widget_hide_all(p->box);
}

void draw_update(t_draw* blks) {
	GdkRectangle		rect;

	rect.x = 0;
	rect.y = 0;
	rect.width = blks->width;
	rect.height = blks->height;
	gtk_widget_draw(blks->widget, &rect);
}

/* t_cnts array access status
 *   if matche found, need to be sequencial access.
 */
typedef struct {
	guint		i;
	t_bseq_dt	dt;
	gboolean	matched;
}	t_ary_access_st;

#define	BN_NOT_STARTED	-1
#define	BN_FINISHED	-2

gint get_abs_bn_type(guint start, t_tcnts* tcnts, guint bn,
		     t_ary_access_st* ast) {
	GArray		*ary;
	t_bseq_dt	*p;
	gint		rc;

	if (!tcnts) {
		rc = BN_FINISHED;
		goto UNMATCHED;
	}
	if (bn >= start && bn < start + tcnts->bcnt) {
		ary = tcnts->dts;
		if (ast->matched) {
			if (!ast->dt.n) {
				p = &g_array_index(ary, t_bseq_dt, ast->i);
				ast->i++;
				ast->dt.type = p->type;
				ast->dt.n = p->n;
			}
			ast->dt.n--;
			return ast->dt.type;
		} else {
			ary = tcnts->dts;
			bn -= start;
			for (ast->i = 0;;) {
				p = &g_array_index(ary, t_bseq_dt, ast->i);
				ast->i++;
				if (!p) {
					rc = BN_FINISHED;
					goto UNMATCHED;
				}
				if (p->n > bn) {
					ast->matched = TRUE;
					ast->dt.type = p->type;
					ast->dt.n = p->n - bn - 1;
					return ast->dt.type;
				}
				bn -= p->n;
			}
		}
	}
	rc = bn < start ? BN_NOT_STARTED : BN_FINISHED;
UNMATCHED:
	ast->matched = FALSE;
	return rc;
}

void __DP_popdown(t_combo* combo) {
	GList		*l;

	g_print("... dump popup-history ...\n");
	if (combo->list) {
		for (l = combo->list; l; l = l->next)
			g_print("\t%s\n", (gchar*)l->data);
	} else
		g_print("\t(NULL)\n");
}

void __DP_baddr(gchar *p, gchar *bstr, gboolean next_line) {
	gint		i;
	gboolean	in_str = FALSE;

	g_print("%s ", p);
	if (next_line) {
		for (i = BLKS_PER_LINE + DIFF_MARGIN_BN; i >= 0; i--) {
			if (bstr[i] != '\0')
				in_str = TRUE;
			if (in_str && bstr[i] == '\0')
				bstr[i] = ' ';
		}
		g_print("%s\n", bstr);
		memset(bstr, '\0', BLKS_PER_LINE + DIFF_MARGIN_BN + 1);
	}
}

void __DP_block(gchar *p, gint x, gint type, t_draw* blks) {
	gint		i;
	char		c = 0;

	i = (x - MARGIN - blks->bnum_width) / (BLK_WIDTH - 1);
	if (type >= MAX_BLK_ID) {
		type -= MAX_BLK_ID;
		c = 0x20;
	}
	switch (type) {
	case UBLK:	c += 'U'; break;
	case SBLK:	c += 'S'; break;
	case XBLK:	c += 'E'; break;
	case CBLK:	c += 'C'; break;
	case FBLK:	c += 'O'; break;
	case FBLK_LF:	c += 'X'; break;
	case FBLK_LSF:	c += 'F'; break;
	default:	c = ' '; break;
	}
	p[i] = c;
}

void __DP_ffinfo(gchar **p) {
	gint		i;

	if (p == NULL) {
		g_print("ffinfo ----------------\n");
		return;
	}
	g_print("ffinfo: \"%s\", ", p[0]);
	for (i = 0; i < FFILE_INFO_CNT; i++)
		g_print("%s, ", p[i + 1]);
	g_print("\n");
}

#ifdef DEBUG
#  define	DP_popdown(combo)		__DP_popdown(combo)
#  define	DP_baddr(p, bstr, next_line)	__DP_baddr(p, bstr, next_line)
#  define	DP_block(p, x, type, blks)	__DP_block(p, x, type, blks)
#  define	DP_ffinfo(p)			__DP_ffinfo(p)
#else
#  define	DP_popdown(combo)
#  define	DP_baddr(p, bstr, next_line)
#  define	DP_block(p, x, type, blks)
#  define	DP_ffinfo(p)
#endif

void draw_blocks(guint start_A, t_tcnts* tcnts_A,
		 guint start_B, t_tcnts* tcnts_B, gint page_ln,
		 gint summary_cnt, gboolean is_fbseq, t_draw* blks) {
	GtkStyle	*style = blks->widget->style;
	GdkGC		*gc = style->black_gc;
	t_page		*page = &blks->page;
	gint		i, y, x, bpl, width;
	gint		type_A = 0, type_B = 0, tmp_A, tmp_B;
	gint		diff_margin_width, offset;
	gboolean	is_diff;
	guint		ln, target_ln;
	guint		bn, max_bn, target_bn, abs_bn;
	t_ary_access_st	ast_A, ast_B;
	guint		start;
	gchar		buf[10 + 1];
#ifdef DEBUG
	gchar		bstr[BLKS_PER_LINE + DIFF_MARGIN_BN + 1];

	memset(bstr, '\0', BLKS_PER_LINE + DIFF_MARGIN_BN + 1);
	g_print("block ----------------\n");
#endif

	/* first, resize draw area, and fill with background color */
	gtk_drawing_area_size(GTK_DRAWING_AREA(blks->widget),
			      blks->width, blks->height);
	gdk_draw_rectangle(blks->pixmap, style->bg_gc[GTK_STATE_NORMAL],
			   TRUE, 0, 0, blks->width, blks->height);

	y = MARGIN;
	x = MARGIN * 2 + blks->bnum_width;
	if (tcnts_B) {
		bpl = summary_cnt * (BLKS_PER_LINE / 2);
		diff_margin_width = ((BLKS_PER_LINE / 2) + DIFF_MARGIN_BN)
						* (BLK_WIDTH - 1);
	} else {
		bpl = summary_cnt * BLKS_PER_LINE;
		diff_margin_width = 0;
	}

	/* bn, ln is relative value to page-top bn and page-top ln */
	target_ln = page->page * page->ln_per_page;
	target_bn = target_ln * bpl;

	start = page->start_bn;
	if (page->ln_1st > 0 && target_ln >= page->ln_1st + DIFF_MARGIN_LN) {
		start = page->start_bn_2nd;
		target_bn -= (page->ln_1st + DIFF_MARGIN_LN) * bpl;
	}

	ast_A.matched = ast_B.matched = FALSE;

	max_bn = bpl * page->ln_per_page;
	for (bn = 0, ln = 0; bn < max_bn && ln < page_ln; bn += summary_cnt) {
		if (page->ln_1st > 0) {
			if (ln + target_ln >= page->ln_1st &&
			    ln + target_ln < page->ln_1st + DIFF_MARGIN_LN) {
				sprintf(buf, "  ....... ");
				width = gdk_string_width(blks->font, buf);
				x = MARGIN + blks->bnum_width - width;
				DP_baddr(buf, bstr, TRUE);
				gdk_draw_string(blks->pixmap, blks->font, gc,
						x, y + 10, buf);
				y += BLK_HEIGHT - 1;
				ln++;
				continue;
			} else if (ln + target_ln
				   == page->ln_1st + DIFF_MARGIN_LN &&
				   start != page->start_bn_2nd) {
				start = page->start_bn_2nd;
				target_bn = 0;
				ast_A.matched = ast_B.matched = FALSE;
				bn = 0;
			}
		}
		abs_bn = start + target_bn + bn;
		if (bn % bpl == 0) {
			if (!is_fbseq) {
				sprintf(buf, "%10u", abs_bn);
				width = gdk_string_width(blks->font, buf);
				x = MARGIN + blks->bnum_width - width;
				DP_baddr(buf, bstr, FALSE);
				gdk_draw_string(blks->pixmap, blks->font, gc,
						x, y + 10, buf);
				x += width + MARGIN;
			}
		
		}
		is_diff = FALSE;
		type_A = type_B = -1;
		for (i = 0; i < summary_cnt; i++) {
			tmp_A = get_abs_bn_type(start_A, tcnts_A,
						abs_bn + i, &ast_A);
			type_A = get_summarized_type(type_A, tmp_A);
			tmp_B = BN_FINISHED;
			if (tcnts_B) {
				tmp_B = get_abs_bn_type(start_B, tcnts_B,
							abs_bn + i, &ast_B);
				type_B = get_summarized_type(type_B, tmp_B);
				if (tmp_A != tmp_B)
					is_diff = TRUE;
			}
			if (tmp_A == BN_FINISHED && tmp_B == BN_FINISHED)
				break;
		}
		if (type_A >= 0) {
			offset = 0;
			if (!is_diff && tcnts_B) {
				type_A += MAX_BLK_ID;
				offset = 1;
			}
			DP_block(bstr, x, type_A, blks);
			gdk_draw_pixmap(blks->pixmap, gc, blks->xpm[type_A],
					0 + offset, 0 + offset,
					x + offset, y + offset, -1, -1);
		}
		if (type_B >= 0) {
			offset = 0;
			if (!is_diff && tcnts_B) {
				type_B += MAX_BLK_ID;
				offset = 1;
			}
			DP_block(bstr, x + diff_margin_width, type_B, blks);
			gdk_draw_pixmap(blks->pixmap, gc, blks->xpm[type_B],
					0 + offset, 0 + offset,
					x + offset + diff_margin_width,
					y + offset, -1, -1);
		}
		x += BLK_WIDTH - 1;
		if (bn % bpl + summary_cnt == bpl) {
			x = MARGIN * 2 + blks->bnum_width;
			y += BLK_HEIGHT - 1;
			DP_baddr("", bstr, TRUE);
			ln++;
		}
	}
}

/* draw view */
void draw_view(gpointer tmp, gint ev_type) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_result	*r_B;
	t_draw		*blks;
	t_frag_info	*dt;
	guint		start = 0, start_A = 0, start_B = 0;
	gint		ln;
	t_tcnts		*tcnts_A, *tcnts_B = NULL;
	gboolean	is_total = TRUE, is_fbseq = TRUE;
	gboolean	is_diff;
	GtkWidget	*fbview;

	r_B = app->out_B.s->len ? ((t_all_data*)tmp)->result_B : NULL;

	if (!r->fsys_tcnts.s)	/* cdavl output correct ? */
		return;

	if (!build_tcnts_dts(r, ev_type))
		return;
	if (r_B && !build_tcnts_dts(r_B, ev_type))
		return;

	switch (ev_type) {
	case EV_FRAG_VIEW:
	case EV_FRAG_PAGE:
		is_total = FALSE;
		break;
	case EV_SWITCH_TAB:
	case EV_FBVIEW:
	case EV_SUMMARY:
		is_total = (app->tab_page == NOTEPAGE_TOTAL);
		break;
	}
	if (is_total) {
		fbview = app->fbview[NOTEPAGE_TOTAL];
		is_fbseq = r->fb_tcnts.s && GTK_TOGGLE_BUTTON(fbview)->active;
		if (is_fbseq) {
			tcnts_A = &r->fb_tcnts;
		} else {
			tcnts_A = &r->fsys_tcnts;
			start_A = start = r->start;
		}
		if (r_B) {
			if (is_fbseq) {
				tcnts_B = &r_B->fb_tcnts;
			} else {
				tcnts_B = &r_B->fsys_tcnts;
				start_B = r_B->start;
			}
		}
		blks = &app->blks[NOTEPAGE_TOTAL];
	} else {
		if (r->ffile_row < 0)
			return;
		dt = (t_frag_info*)g_slist_nth_data(r->frag_info, r->ffile_row);
		tcnts_A = &dt->tcnts;
		if (r_B)
			tcnts_B = &dt->tcnts_B;
		blks = &app->blks[NOTEPAGE_FRAG];
	}

	/* already drawn ? */
	switch (ev_type) {
	case EV_SWITCH_TAB:
	case EV_SUMMARY:
		if (blks->summary_cnt == app->summary.cnt)
			return;
	}

	is_diff = (app->out_B.s->len != 0);
	ln = check_draw_area(start_A, tcnts_A, start_B, tcnts_B,
			     app->summary.cnt, blks, ev_type);
	update_page_btn(&blks->page);
	draw_blocks(start_A, tcnts_A, start_B, tcnts_B, ln,
		    app->summary.cnt, is_fbseq, blks);
	blks->summary_cnt = app->summary.cnt;

	return;
}

gint on_expose_total_blocks(GtkWidget *w, GdkEventExpose *event, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_draw		*blks = &app->blks[NOTEPAGE_TOTAL];

	if (!blks->pixmap)
		return FALSE;

	gdk_draw_pixmap(w->window,
			w->style->fg_gc[GTK_WIDGET_STATE(w)],
			blks->pixmap,
			event->area.x, event->area.y,
			event->area.x, event->area.y,
			event->area.width, event->area.height);
	return FALSE;
}

gint on_expose_frag_blocks(GtkWidget *w, GdkEventExpose *event, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_draw		*blks = &app->blks[NOTEPAGE_FRAG];

	if (!blks->pixmap)
		return FALSE;

	gdk_draw_pixmap(w->window,
			w->style->fg_gc[GTK_WIDGET_STATE(w)],
			blks->pixmap,
			event->area.x, event->area.y,
			event->area.x, event->area.y,
			event->area.width, event->area.height);
	return FALSE;
}





/* cdavl io function */
gint g_pipe(gint fd[2]) {
	gint		rc;

	fd[0] = fd[1] = -1;
	rc = pipe(fd);
	if (rc < 0) {
		display_err_dialog("pipe create failed.");
		return rc;
	}
	return 0;
}

void g_fd_close(gint fd[2], gint i) {
	if (fd[i] < 0)
		return;
	close(fd[i]);
	fd[i] = -1;
}

void g_pipe_close(gint fd[2]) {
	gint	i;

	for (i = 0; i < 2; i++)
		g_fd_close(fd, i);
}

gint g_dup(gint fd) {
	gint		rc;

	rc = dup(fd);
	if (rc < 0) {
		display_err_dialog("dup failed.");
		return -1;
	}
	return 0;
}

gint exec_cdavl(gint out, gint err, t_app_status* app) {
	gint		rc;
	gchar*		argv[5];

	close(STDOUT_FILENO);
	rc = g_dup(out);
	if (rc < 0)
		return -1;
	close(out);

	close(STDERR_FILENO);
	rc = g_dup(err);
	if (rc < 0)
		return -1;
	close(err);

	argv[0] = "cdavl";
	argv[1] = app->not_use_devdrv ? "-Tvs" : "-Tv";
	argv[2] = app->dev.s;
	argv[3] = app->path.s;
	argv[4] = NULL;

	rc = execvp("cdavl", argv);
	if (rc < 0) {
		fprintf(stderr,
			"cdavl not found in PATH environment variable.\n");
		return -1;
	}
	return 0;
}

void clean_up(pid_t* pid, t_io_info *out, t_io_info *out_B, t_io_info *err) {
	gint		status;

	if (out->ch) {
		g_io_channel_unref(out->ch);
		out->ch = NULL;
	}
	if (err->ch) {
		g_io_channel_unref(err->ch);
		err->ch = NULL;
	}
	if (*pid >= 0) {
		kill(*pid, SIGTERM);
		waitpid(*pid, &status, 0);
		*pid = -1;
	}
	if (out->s) {
		g_string_free(out->s, TRUE);
		out->s = NULL;
	}
	if (out_B->s) {
		g_string_free(out_B->s, TRUE);
		out_B->s = NULL;
	}
	if (err->s) {
		g_string_free(err->s, TRUE);
		err->s = NULL;
	}
}





void output_text_buf(t_text* text, gchar* s) {
#ifdef DEBUG
	g_print("%s", s);
#endif
#ifdef GTK2
	GtkTextBuffer	*buf = gtk_text_view_get_buffer
					(GTK_TEXT_VIEW(text->widget));
	gtk_text_buffer_insert_at_cursor(buf, s, -1);
#else
	GdkFont		*font = text->font;
	GtkWidget	*w = text->widget;
	gtk_text_insert(GTK_TEXT(w), font, NULL, NULL, s, -1);
#endif
}

#define	GET_RESULT_STR(rA, rB, sA, sB, member)			\
	do {							\
		(sA) = (rA)->member;				\
		if ((rB) && strcmp((sA), (rB)->member) != 0)	\
			(sB) = (rB)->member;			\
	} while (0)

/* output result (text data) */
void output_tag_str(t_text* text, gchar* tag, gint id,
		    t_result* r, t_result* r_B) {
	gchar		*s = "", *s_B = NULL;

	switch (id) {
	case 0: GET_RESULT_STR(r, r_B, s, s_B, fstype); break;
	case 1: GET_RESULT_STR(r, r_B, s, s_B, mount_st); break;
	case 2: GET_RESULT_STR(r, r_B, s, s_B, f_per); break;
	}
	output_text_buf(text, " ");
	output_text_buf(text, tag);
	output_text_buf(text, s);
	if (s_B) {
		output_text_buf(text, " -> ");
		output_text_buf(text, s_B);
	}
	output_text_buf(text, "\n");
}

void output_tag_value(t_text* text, gchar* tag, gint id,
		      t_result* r, t_result* r_B) {
	GString		*s = g_string_new(tag);

	if (r->values[id] == ULONG_MAX)
		return;
	g_string_sprintfa(s, "%u", r->values[id]);
	if (r_B && r->values[id] != r_B->values[id])
		g_string_sprintfa(s, " -> %u", r_B->values[id]);
	g_string_sprintfa(s, "\n");

	output_text_buf(text, " ");
	output_text_buf(text, s->str);

	g_string_free(s, TRUE);
}

void output_result(t_text* text, t_result* r, t_result *r_B) {
#ifdef DEBUG
	g_print("text ----------------\n");
#endif
	output_tag_str(text, "filesystem: ", 0, r, r_B);
	output_tag_str(text, "mount status: ", 1, r, r_B);
	output_tag_str(text, "fragmented percentage: ", 2, r, r_B);
	output_tag_value(text, "total blocks: ", ID_BLOCKS, r, r_B);
	output_tag_value(text, "system blocks: ", ID_SBLOCKS, r, r_B);
	output_tag_value(text, "xattr/acl blocks: ", ID_XBLOCKS, r, r_B);
	output_tag_value(text, "file blocks: ", ID_FBLOCKS, r, r_B);
	output_tag_value(text, "fragmented blocks: ", ID_FRAGS, r, r_B);
	output_tag_value(text, "sys-fragmented blocks: ", ID_SFRAGS, r, r_B);
	output_tag_value(text, "continuous files: ", ID_CFILES, r, r_B);
	output_tag_value(text, "fragmented files: ", ID_FFILES, r, r_B);
	output_tag_value(text, "max dir depth: ", ID_DEPTH, r, r_B);

	/* delete last LF */
#ifdef GTK2
	GtkTextBuffer	*buf;
	GtkTextIter	start, end;
	buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text->widget));
	gtk_text_buffer_get_end_iter(buf, &end);
	start = end;
	gtk_text_iter_backward_char(&start);
	gtk_text_buffer_delete(buf, &start, &end);
#else
	gtk_text_backward_delete(GTK_TEXT(text->widget), 1);
	gtk_text_set_point(GTK_TEXT(text->widget), 0);
#endif
	gtk_adjustment_set_value(text->adjust, 0.0);
}

void fragment_info_free(t_result* r) {
	gint			i;
	t_frag_info		*dt;

	if (r->frag_info) {
		for (i = 0; i < g_slist_length(r->frag_info); i++) {
			dt = g_slist_nth_data(r->frag_info, i);
			if (dt->tcnts.dts)
				g_array_free(dt->tcnts.dts, TRUE);
			if (dt->tcnts_B.dts)
				g_array_free(dt->tcnts_B.dts, TRUE);
			g_chunk_free(dt, r->finfo_mem_chunk);
		}
		g_slist_free(r->frag_info);
		r->frag_info = NULL;
	}
	if (r->finfo_mem_chunk) {
		g_mem_chunk_destroy(r->finfo_mem_chunk);
		r->finfo_mem_chunk = NULL;
	}
}

gboolean init_draw_area(gint ev_type, gint i, gpointer p) {
	t_draw			*blks = &((t_app_status*)p)->blks[i];
	GtkScrolledWindow	*win;
	GtkAdjustment		*adj;

	win = GTK_SCROLLED_WINDOW(blks->scrl_win);
	adj = gtk_scrolled_window_get_vadjustment(win);
	gtk_adjustment_set_value(adj, 0.0);

	if (blks->pixmap) {
		gdk_draw_rectangle(blks->pixmap,
				   blks->widget->style->
					bg_gc[GTK_STATE_NORMAL],
				   TRUE, 0, 0, blks->width, blks->height);
		draw_update(blks);
	}
	switch (ev_type) {
	case EV_TOTAL_PAGE:
	case EV_FFILE_PAGE:
	case EV_FRAG_PAGE:
		break;
	default:
		blks->page.page_max = 0;
		update_page_btn(&blks->page);
		break;
	}
	return TRUE;
}

void init_result(t_result* r) {
	if (r->fsys_tcnts.dts)
		g_array_free(r->fsys_tcnts.dts, TRUE);
	if (r->fb_tcnts.dts)
		g_array_free(r->fb_tcnts.dts, TRUE);
	fragment_info_free(r);
	bzero(r, sizeof(t_result));
	r->ffile_row = -1;
}

void init_gui(t_app_status* app, t_result* r, t_result* r_B, gint ev_type) {
	t_text		*text = &app->text;
	gint		i, j, max;

	if (ev_type == EV_START) {
		gtk_widget_set_sensitive(app->fbview[NOTEPAGE_TOTAL], FALSE);
#ifdef GTK2
		GtkTextBuffer	*buf = gtk_text_view_get_buffer
						(GTK_TEXT_VIEW(text->widget));
		gtk_text_buffer_set_text(buf, "", -1);
#else
		gtk_text_forward_delete(GTK_TEXT(text->widget),
					gtk_text_get_length
						(GTK_TEXT(text->widget)));
#endif
		g_string_truncate(app->out.s, 0);
		g_string_truncate(app->out_B.s, 0);
		g_string_truncate(app->err.s, 0);
		init_result(r);
		init_result(r_B);
		app->is_abort = FALSE;

		app->ffrag.page.page_max = 0;
		update_page_btn(&app->ffrag.page);
		gtk_clist_clear(GTK_CLIST(app->ffrag.widget));
	}

	i = max = 0;
	switch (ev_type) {
	case EV_START:
		i = NOTEPAGE_TOTAL;
		max = 2;
		break;
	case EV_TOTAL_PAGE:
		i = NOTEPAGE_TOTAL;
		max = 1;
		break;
	case EV_FRAG_VIEW:
	case EV_FRAG_PAGE:
		i = NOTEPAGE_FRAG;
		max = 1;
		break;
	case EV_SWITCH_TAB:
	case EV_SUMMARY:
		/* already drawn ? */
		if (app->blks[app->tab_page].summary_cnt == app->summary.cnt)
			return;
		/* else, fallthrough */
	case EV_FBVIEW:
		i = app->tab_page;
		max = 1;
		break;
	}
	for (j = 0; j < max; j++)
		if (!init_draw_area(ev_type, i++, app))
			break;
}

gdouble get_diff_value_fper(t_tcnts *a, t_tcnts *b) {
	gdouble		va = 0.0, vb = 0.0;

	if (a->s)
		va = a->fper;
	if (b->s)
		vb = b->fper;
	return vb - va;
}

long long int get_diff_value_other(t_tcnts *a, t_tcnts *b, int index) {
	guint		va = 0, vb = 0;

	if (a->s) {
		switch (index) {
		case FFI_TOTAL:  va = a->total; break;
		case FFI_FRAGS:  va = a->frags; break;
		case FFI_SFRAGS: va = a->sfrags; break;
		}
	}
	if (b->s) {
		switch (index) {
		case FFI_TOTAL:  vb = b->total; break;
		case FFI_FRAGS:  vb = b->frags; break;
		case FFI_SFRAGS: vb = b->sfrags; break;
		}
	}
	if (index == FFI_TOTAL && (va == 0 || vb == 0))
		va = vb;
	return (long long int)vb - va;
}

void get_diff_value_str(t_tcnts *a, t_tcnts *b, GString *s, int index) {
	if (index == FFI_PERCENT) {
		gdouble		v_diff = get_diff_value_fper(a, b);
		g_string_sprintf(s, (v_diff > 0.0 ? "+%3.2f" : "%3.2f"),
				 v_diff);
	} else {
		long long int	v_diff = get_diff_value_other(a, b, index);
		g_string_sprintf(s, (v_diff > 0 ? "+%lld" : "%lld"), v_diff);
	}
}

void get_value_str(t_tcnts *t, GString *s, int index) {
	switch (index) {
	case FFI_PERCENT: g_string_sprintf(s, "%3.2f", t->fper); break;
	case FFI_TOTAL:   g_string_sprintf(s, "%u", t->total); break;
	case FFI_FRAGS:   g_string_sprintf(s, "%u", t->frags); break;
	case FFI_SFRAGS:  g_string_sprintf(s, "%u", t->sfrags); break;
	}
}

void update_frag_files(gpointer tmp, gint ev_type) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_result	*r_B = ((t_all_data*)tmp)->result_B;
	GtkCList	*w = (GtkCList*)app->ffrag.widget;
	t_page		*page = &app->ffrag.page;
	GSList		*l = r->frag_info;
	guint		i, j, ln, top_ln;
	t_frag_info	*dt;
	gchar		*p[FFILE_INFO_CNT +1 +1];
					/* +1 for path, termination */
	t_tcnts		*tcnts_A, *tcnts_B;
	GString		*s[FFILE_INFO_CNT];

	page->ln_all = g_slist_length(l);
	ln = get_page_ln(page, MAX_FFILES_PER_PAGE, ev_type);

	update_page_btn(page);

	top_ln = page->page * page->ln_per_page;
	p[FFILE_INFO_CNT + 1] = "";

	gtk_clist_freeze(w);
	gtk_clist_clear(w);
	DP_ffinfo(NULL);
	for (i = 0; i < ln; i++) {
		dt = (t_frag_info*)g_slist_nth_data(l, i + top_ln);
		tcnts_A = &dt->tcnts;
		tcnts_B = &dt->tcnts_B;
		p[0] = dt->path;
		for (j = 0; j < FFILE_INFO_CNT; j++) {
			s[j] = g_string_new("");
			if (app->out_B.s->len)
				get_diff_value_str(tcnts_A, tcnts_B, s[j], j);
			else
				get_value_str(tcnts_A, s[j], j);
			p[j + 1] = s[j]->str;
		}
		gtk_clist_append(w, p);
		DP_ffinfo(p);
		for (j = 0; j < FFILE_INFO_CNT; j++)
			if (s[j])
				g_string_free(s[j], TRUE);
	}
	gtk_clist_thaw(w);

	/* clear the draw area of fragmented file blocks */
	r->ffile_row = -1;
	init_gui(app, r, r_B, EV_FRAG_VIEW);
}



void update_popdown(t_combo* combo) {
	GList		*l;
	gchar		*s;

	if (!combo->s)
		return;

	for (l = combo->list; l; l = l->next)
		if (strcmp(l->data, combo->s) == 0)
			break;
	if (!l) {
		if (g_list_length(combo->list) >= MAX_POPDOWN_CNT) {
			/* del */
			l = g_list_last(combo->list);
			if (l) {
				l->prev->next = NULL;
				l->prev = NULL;
				if (l->data)
					g_free(l->data);
				g_list_free(l);
			}
		}
		/* add */
		s = g_strdup(combo->s);
		combo->list = g_list_prepend(combo->list, s);
	} else if (l->prev){
		/* mov */
		l->prev->next = l->next;
		if (l->next)
			l->next->prev = l->prev;
		l->prev = NULL;
		l->next = combo->list;
		if (l->next)
			l->next->prev = l;
		combo->list = l;
	}
	DP_popdown(combo);
	gtk_combo_set_popdown_strings(GTK_COMBO(combo->widget), combo->list);
}

gint cmp_finfo(gconstpointer tmp1, gconstpointer tmp2, gint index) {
	t_frag_info	*x = (t_frag_info*)tmp1, *y = (t_frag_info*)tmp2;
	gdouble		vxf, vyf;
	gboolean	is_diff = x->is_diff | y->is_diff;

	switch (index) {
	case FFI_PERCENT:
		if (is_diff) {
			vxf = get_diff_value_fper(&x->tcnts, &x->tcnts_B);
			vyf = get_diff_value_fper(&y->tcnts, &y->tcnts_B);
		} else {
			vxf = x->tcnts.fper;
			vyf = y->tcnts.fper;
		}
		if (vxf > vyf)
			return 1;
		else if (vxf == vyf)
			return cmp_finfo(tmp1, tmp2, -1);
		else
			return -1;
	case FFI_TOTAL:
	case FFI_FRAGS:
	case FFI_SFRAGS:
		if (is_diff) {
			long long int	vx, vy;
			vx = get_diff_value_other(&x->tcnts, &x->tcnts_B,
						  index);
			vy = get_diff_value_other(&y->tcnts, &y->tcnts_B,
						  index);
			if (vx == vy)
				return cmp_finfo(tmp1, tmp2, -1);
			return vx - vy;
		} else {
			guint		vx = 0, vy = 0;
			switch (index) {
			case FFI_TOTAL:
				vx = x->tcnts.total;
				vy = y->tcnts.total;
				break;
			case FFI_FRAGS:
				vx = x->tcnts.frags;
				vy = y->tcnts.frags;
				break;
			case FFI_SFRAGS:
				vx = x->tcnts.sfrags;
				vy = y->tcnts.sfrags;
				break;
			}
			if (vx == vy)
				return cmp_finfo(tmp1, tmp2, -1);
			return vx - vy;
		}
	default:
		return strcmp(x->path, y->path);
	}
	return 0;
}

gint cmp_finfo_path(gconstpointer a, gconstpointer b) {
	return cmp_finfo(a, b, -1);
}

gint cmp_finfo_fper(gconstpointer a, gconstpointer b) {
	return cmp_finfo(a, b, FFI_PERCENT);
}

gint cmp_finfo_total(gconstpointer a, gconstpointer b) {
	return cmp_finfo(a, b, FFI_TOTAL);
}

gint cmp_finfo_frags(gconstpointer a, gconstpointer b) {
	return cmp_finfo(a, b, FFI_FRAGS);
}

gint cmp_finfo_sfrags(gconstpointer a, gconstpointer b) {
	return cmp_finfo(a, b, FFI_SFRAGS);
}

gchar* parse_ffile_info(gchar* p, gchar* p_max, gchar* tag_start,
			gchar* tag_end, t_tcnts* tcnts, int index) {
	gchar		*p_val;

	p = strstr(p + 1, tag_start);
	CHK_POINTER(p, p_max);

	p_val = p + strlen(tag_start);
	switch (index) {
	case FFI_PERCENT: tcnts->fper = strtod(p_val, NULL); break;
	case FFI_TOTAL:   tcnts->total = strtoul(p_val, NULL, 10); break;
	case FFI_FRAGS:   tcnts->frags = strtoul(p_val, NULL, 10); break;
	case FFI_SFRAGS:  tcnts->sfrags = strtoul(p_val, NULL, 10); break;
	}

	p = strstr(p_val, tag_end);
	CHK_POINTER(p, p_max);
	*p++ = '\0';
	return p;

ERR_RETURN:
	return NULL;
}

void filter_diff_ffiles(gpointer elem, gpointer tmp) {
	t_frag_info	*dt = (t_frag_info*)elem;
	t_result	*r = (t_result*)tmp;
	GMemChunk	*mem_chunk = r->finfo_mem_chunk;

	if ((dt->tcnts.s && !dt->tcnts_B.s) || (!dt->tcnts.s && dt->tcnts_B.s)||
	    dt->tcnts.fper != dt->tcnts_B.fper ||
	    dt->tcnts.total != dt->tcnts_B.total ||
	    dt->tcnts.frags != dt->tcnts_B.frags ||
	    dt->tcnts.sfrags != dt->tcnts_B.sfrags) {
		dt->is_diff = TRUE;
		r->frag_info = g_slist_insert_sorted(r->frag_info, dt,
						     cmp_finfo_path);
		return;
	}
	g_chunk_free(dt, mem_chunk);
}

t_frag_info* find_same_path_finfo(GSList* l, gchar* path) {
	t_frag_info	*finfo;
	guint		min, mid, max;
	gboolean	loop = TRUE;

	if (l == NULL)
		return NULL;
	min = 0;
	max = g_slist_length(l) - 1;
	while (loop && min <= max) {
		mid = (min + max) / 2;
		finfo = (t_frag_info*)g_slist_nth_data(l, mid);
		switch (strcmp(finfo->path, path)) {
		case 0:
			return finfo;
		case -1:
			min = mid + 1;
			break;
		case 1:
			if (mid == 0)
				loop = FALSE;
			else
				max = mid - 1;
			break;
		}
	}
	return NULL;
}

gchar* parse_ffiles(gchar* p, gint len, t_result* r, t_result* r_marge) {
	GSList		*l = NULL, *l_marge = NULL;
	GMemChunk	*mem_chunk;
	t_frag_info	*dt = NULL, *dt_marge;
	gchar		*p_max, *p_frag_max, *p_last = NULL;
	gchar		*tag_path_start = "\n\"";
	gchar		*tag_path_end = "\"(ino: ";
	gchar		*tag_fbseq_start = "\nblock sequence: ";
	gint		i;

	if (r_marge) {
		l_marge = r_marge->frag_info;
		mem_chunk = r_marge->finfo_mem_chunk;
	} else {
		mem_chunk = g_mem_chunk_create(t_frag_info, 1024,
					       G_ALLOC_AND_FREE);
		r->finfo_mem_chunk = mem_chunk;
	}

	p_max = p + len;
	p_frag_max = strstr(p, TAG_RESULT_START);
	CHK_POINTER(p_frag_max, p_max);

	while (p < p_frag_max) {
		/* check path */
		p = strstr(p, tag_path_start);
		if (!p || p >= p_max)
			break;
		*p = '\0';
		dt = g_chunk_new(t_frag_info, mem_chunk);
		memset(dt, 0, sizeof(t_frag_info));

		dt->path = p + strlen(tag_path_start);
		p = strstr(dt->path, tag_path_end);
		CHK_POINTER(p, p_max);
		*p++ = '\0';

		/* check additional-info */
		i = 0;
		if ((p = parse_ffile_info(p, p_max, " (", "%, ",
					  &dt->tcnts, i++)) == NULL)
			goto ERR_RETURN;
		if ((p = parse_ffile_info(p, p_max, "total: ", "/",
					  &dt->tcnts, i++)) == NULL)
			goto ERR_RETURN;
		if ((p = parse_ffile_info(p, p_max, "frag: ", ",",
					  &dt->tcnts, i++)) == NULL)
			goto ERR_RETURN;
		if ((p = parse_ffile_info(p, p_max, "sfrag: ", ")",
					  &dt->tcnts, i++)) == NULL)
			goto ERR_RETURN;

		/* check fbseq */
		p = strstr(p, tag_fbseq_start);
		CHK_POINTER(p, p_max);
		dt->tcnts.s = p_last = p + strlen(tag_fbseq_start);

		if (r_marge) {
			dt_marge = find_same_path_finfo(l_marge, dt->path);
			if (dt_marge) {
				dt_marge->tcnts_B = dt->tcnts;
				dt_marge->is_diff = TRUE;
				g_chunk_free(dt, mem_chunk);
			} else {
				dt->tcnts_B = dt->tcnts;
				memset(&dt->tcnts, 0, sizeof(t_tcnts));
				dt->is_diff = TRUE;
				l_marge = g_slist_insert_sorted
						(l_marge, dt, cmp_finfo_path);
			}
		} else {
			l = g_slist_insert_sorted(l, dt, cmp_finfo_path);
		}
	}
	if (p_last) {		/* fragmented file exists */
		p = strchr(p_last, '\n');
		CHK_POINTER(p, p_max);
		*p++ = '\0';
	}
	if (r_marge) {
		GSList	*tmp = l_marge;
		r_marge->frag_info = NULL;
		g_slist_foreach(tmp, filter_diff_ffiles, r_marge);
	} else
		r->frag_info = l;
	return p_frag_max;

ERR_RETURN:
	if (r_marge) {
		r_marge->frag_info = l_marge;
		fragment_info_free(r_marge);
	} else {
		r->frag_info = l;
		fragment_info_free(r);
	}
	return NULL;
}

gboolean chk_result_line(guint ln, gchar* tag, gchar* p, t_result *r) {
	gchar		**chk = (gchar**)result_chk_tbl;
	gint		max = ARRAY_SIZE(result_chk_tbl);
	guint		val;

	/* check tag */
	if (ln < max) {
		if (strcmp(chk[ln], tag) != 0)
			return FALSE;
	} else if (strcmp(TAG_FBSEQ, tag) == 0) {
		if (r->fb_tcnts.s)
			return FALSE;
		r->fb_tcnts.s = p;
	} else {
		if (r->fb_tcnts.s)
			return FALSE;
		if (!r->fsys_tcnts.s) {
			val = strtoul(tag, NULL, 10);
			if (val == ULONG_MAX)
				return FALSE;
			r->start = val;
			r->fsys_tcnts.s = p;
		}
	}

	/* check value */
	if (ln >= max)
		return TRUE;
	switch (ln) {
	case 0:
		if (strcmp(p, "ext2") != 0 && strcmp(p, "ext3") != 0)
			return FALSE;
		r->fstype = p;
		break;
	case 1:
		r->mount_st = p;
		break;
	case 2:
		r->f_per = p;
		break;
	default:
		if (strcmp(p, "-") == 0)
			val = ULONG_MAX;
		else {
			val = strtoul(p, NULL, 10);
			if (val == ULONG_MAX)
				return FALSE;
		}
		r->values[ln-3] = val;
		break;
	}
	return TRUE;
}

gchar* parse_result(gchar* p, gint len, t_result* r) {
	gchar		*p_max, *p_tmp, *p_new;
	guint		ln;

	p_max = p + len;
	p = strstr(p, TAG_RESULT_START);
	if (p == NULL || p >= p_max) {
		display_err_dialog(EMSG_CDAVL_RESULT_INVALID);
		return NULL;
	}
	*p = '\0';
	p += strlen(TAG_RESULT_START);

	ln = 0;
	while (p < p_max) {
		p_tmp = strchr(p, '\t');
		CHK_POINTER(p_tmp, p_max);
		*p_tmp = '\0';
		p_new = strchr(p_tmp + 1, '\n');
		CHK_POINTER(p_new, p_max);
		*p_new = '\0';
		if (!chk_result_line(ln, p, p_tmp + 1, r)) {
			display_err_dialog(EMSG_CDAVL_RESULT_INVALID);
			return NULL;
		}
		p = p_new + 1;
		ln++;
	}
	return p_max;

ERR_RETURN:
	return NULL;
}

void get_diff_time(struct timeval *start, struct timeval *end,
		   struct timeval *result) {

	result->tv_sec = end->tv_sec - start->tv_sec;
	if (end->tv_usec >= start->tv_usec)
		result->tv_usec = end->tv_usec - start->tv_usec;
	else {
		result->tv_sec--;
		result->tv_usec = 1000000 - start->tv_usec + end->tv_usec;
	}
}

void set_mode_enable(t_app_status* app, gboolean enable) {
	if (app->anim.interval)
		return;
	gtk_widget_set_sensitive(GTK_WIDGET(app->menu.normal), enable);
	gtk_widget_set_sensitive(GTK_WIDGET(app->menu.diff), enable);
}

void set_statusbar_abort(t_sbar* sbar) {
	gtk_statusbar_push(GTK_STATUSBAR(sbar->widget), sbar->cid,
			   "check aborted.");
}

void set_statusbar_done(t_sbar* sbar, t_time_info* time) {
	GString		*s = g_string_sized_new(0);
	struct timeval	full_time;
	struct timeval	chk_time;

	get_diff_time(&time->start, &time->end, &full_time);
	get_diff_time(&time->start, &time->cdavl_end, &chk_time);
	g_string_sprintfa(s,
			  "check done.      time: %u.%usec (check: %u.%usec)",
			  (int)full_time.tv_sec, (int)full_time.tv_usec / 1000,
			  (int)chk_time.tv_sec, (int)chk_time.tv_usec / 1000);
	gtk_statusbar_push(GTK_STATUSBAR(sbar->widget), sbar->cid, s->str);
	g_string_free(s, TRUE);
}

gint parse_all(GString *s, t_result *r, t_result *r_marge) {
	gchar		*p;

	p = parse_ffiles(s->str, s->len, r, r_marge);
	if (!p)
		return FALSE;
	p = parse_result(p, s->len - (p - s->str), r);
	if (!p)
		return FALSE;
	return TRUE;
}

#define	S_CHK_TYPE(r)	\
	(r)->fb_tcnts.s ? "file" : \
	((r)->values[ID_SBLOCKS] == ULONG_MAX ? "directory" : "partition")

gint prev_diff_chk(t_result *r_A, t_result *r_B) {
	gchar		buf[128];

	if (strcmp(r_A->fstype, r_B->fstype) != 0)
		printf("WARN: filesystem differ (%s -> %s)\n",
		       r_A->fstype, r_B->fstype);
	if ((r_A->values[ID_SBLOCKS] == ULONG_MAX
	     && r_B->values[ID_SBLOCKS] != ULONG_MAX)
	    || (r_A->values[ID_SBLOCKS] != ULONG_MAX
		&& r_B->values[ID_SBLOCKS] == ULONG_MAX)
	    || (r_A->fb_tcnts.s && !r_B->fb_tcnts.s)
	    || (!r_A->fb_tcnts.s && r_B->fb_tcnts.s)) {
		sprintf(buf, "check type differ (%s -> %s)",
			S_CHK_TYPE(r_A), S_CHK_TYPE(r_B));
		display_err_dialog(buf);
		return FALSE;
	}
	return TRUE;
}

gint output_data_proc(gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_result	*r_B = ((t_all_data*)tmp)->result_B;
	t_io_info	*out = &app->out;
	t_io_info	*out_B = &app->out_B;
	GtkWidget	*fbview;

	if (!parse_all(out->s, r, NULL))
		return FALSE;

	if (out_B->s->len) {
		if (!parse_all(out_B->s, r_B, r))
			return FALSE;

		if (!prev_diff_chk(r, r_B))
			return FALSE;
	}

	if (r->fb_tcnts.s) {
		fbview =  app->fbview[NOTEPAGE_TOTAL];
		gtk_widget_set_sensitive(fbview, TRUE);
	}
	update_popdown(&app->dev);
	if (app->is_normal)
		update_popdown(&app->path);
	else
		update_popdown(&app->after);

	output_result(&app->text, r, out_B->s->len ? r_B : NULL);
	update_frag_files(tmp, EV_START);
	draw_view(tmp, EV_START);

	return TRUE;
}

guint on_input(GIOChannel *c, GIOCondition condition, gpointer tmp,
	       gboolean is_out) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	GIOError	rc;
	gint		max = 64, cnt, status;
	gchar		buf[max];
	GIOChannel	**c_target;
	GString		*s;
	t_io_info	*out, *err;

	out = &app->out;
	err = &app->err;
	c_target = is_out ? &out->ch : &err->ch;
	if (!*c_target) {
		/* already closed */
		/*g_warning("already closed.\n");*/
		return FALSE;
	}
	/*
	if (g_io_channel_unix_get_fd(c) != g_io_channel_unix_get_fd(*c_target)){
		g_print("fd unmatch.\n");
		return FALSE;
	}
	*/

	rc = g_io_channel_read(c, buf, max, (unsigned int*)&cnt);
	if (rc == G_IO_ERROR_AGAIN)
		return TRUE;
	if (rc != G_IO_ERROR_NONE || cnt == 0) {
		g_io_channel_unref(c);
		*c_target = NULL;
		if (!out->ch && !err->ch && app->cdavl_pid >= 0) {
			waitpid(app->cdavl_pid, &status, 0);
			app->cdavl_pid = -1;
			gettimeofday(&app->time.cdavl_end, NULL);

			if (err->s->len) {
				g_string_truncate(err->s,
						  strlen(err->s->str) - 1);
				display_err_dialog(err->s->str);
			} else if (!app->is_abort)
				output_data_proc(tmp);

			gettimeofday(&app->time.end, NULL);
			if (app->is_abort)
				set_statusbar_abort(&app->sbar);
			else
				set_statusbar_done(&app->sbar, &app->time);
			set_mode_enable(app, TRUE);
		}
		return FALSE;
	}
	s = is_out ? out->s : err->s;
	g_string_sprintfa(s, "%.*s", cnt, buf);

	return TRUE;
}

guint on_input_from_out(GIOChannel *c, GIOCondition condition, gpointer tmp) {
	return on_input(c, condition, tmp, TRUE);
}

guint on_input_from_err(GIOChannel *c, GIOCondition condition, gpointer tmp) {
	return on_input(c, condition, tmp, FALSE);
}





gboolean is_dac_out_file(gchar* fpath) {
	gint		fd;
 	gchar		*p = "data for gdavl (cdavl ver: ";
	ssize_t		size, max = 128, len = strlen(p);
	gchar		buf[max];
	gboolean	result = FALSE;

	fd = open(fpath, O_RDONLY);
	if (fd < 0)
		goto RETURN;

	while ((size = read(fd, buf, len)) < 0 && errno == EINTR);
	if (size != len)
		goto RETURN;

	if (strncmp(p, buf, len) != 0)
		goto RETURN;

	result = TRUE;
RETURN:
	if (fd >= 0)
		close(fd);
	return result;
}

void fdReadToGstr(gint fd, GString *s) {
	ssize_t		size, max = 128;
	gchar		buf[max];

	while ((size = read(fd, buf, max))) {
		if (size < 0) {
			if (errno == EINTR)
				continue;
			else {
				display_err_dialog("cdavl out-file read failed.");
				break;
			}
		} else
			g_string_sprintfa(s, "%.*s", size, buf);
	}
}

gint proc_cdavl_out_file(gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	gint		fd, fd_B = -1;
	t_io_info	*out = &app->out;

	/* check device file */
	fd = open(app->dev.s, O_RDONLY);
	if (fd < 0)
		return FALSE;
	if (!app->is_normal)
		fd_B = open(app->after.s, O_RDONLY);

	gettimeofday(&app->time.start, NULL);
	gettimeofday(&app->time.cdavl_end, NULL);
	gtk_statusbar_push(GTK_STATUSBAR(app->sbar.widget),
			   app->sbar.cid, "checking fragmentation...");

	fdReadToGstr(fd, out->s);
	if (fd_B >= 0)
		fdReadToGstr(fd_B, app->out_B.s);

	if (out->s->len)
		output_data_proc(tmp);

	gettimeofday(&app->time.end, NULL);
	set_statusbar_done(&app->sbar, &app->time);
	set_mode_enable(app, TRUE);

	if (fd >= 0)
		close(fd);
	if (fd_B >= 0)
		close(fd_B);

	return TRUE;
}

gboolean prev_chk(gpointer tmp, gboolean *dev_is_dacout) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	if (app->is_normal) {
		if (!app->dev.s) {
			display_err_dialog("device not defined.");
			return FALSE;
		}
		if ((*dev_is_dacout = is_dac_out_file(app->dev.s)) &&
		    app->path.s) {
			display_err_dialog("cannot set path to cdavl-out file.");
			return FALSE;
		}
	} else {
		if (!app->dev.s || !app->after.s) {
			display_err_dialog("before and after not defined.");
			return FALSE;
		}
		if (!(*dev_is_dacout = is_dac_out_file(app->dev.s)) ||
		    !is_dac_out_file(app->after.s)) {
			display_err_dialog("not cdavl-out files diff.");
			return FALSE;
		}

	}
	return TRUE;
}

gint proc_exec(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_result	*r_B = ((t_all_data*)tmp)->result_B;
	gint		rc;
	gint		pipe_out[2], pipe_err[2];
	t_io_info	*out, *err;
	gboolean	dev_is_dacout;

	if (app->cdavl_pid >= 0)
		return TRUE;
	if (!prev_chk(tmp, &dev_is_dacout))
		return TRUE;

	set_mode_enable(app, FALSE);
	init_gui(app, r, r_B, EV_START);

	if (dev_is_dacout) {
		proc_cdavl_out_file(tmp);
		return TRUE;
	}

	rc = g_pipe(pipe_out);
	if (rc < 0)
		return TRUE;
	rc = g_pipe(pipe_err);
	if (rc < 0) {
		g_pipe_close(pipe_out);
		return TRUE;
	}

	gettimeofday(&app->time.start, NULL);
	app->cdavl_pid = fork();
	switch (app->cdavl_pid) {
	case -1:
		display_err_dialog("fork failed.");
		g_pipe_close(pipe_out);
		g_pipe_close(pipe_err);
		return TRUE;
	case 0:
		g_fd_close(pipe_out, 0);
		g_fd_close(pipe_err, 0);
		rc = exec_cdavl(pipe_out[1], pipe_err[1], app);
		if (rc < 0)
			_exit(0);
		_exit(1);
		break;
	default:
		gtk_statusbar_push(GTK_STATUSBAR(app->sbar.widget),
				   app->sbar.cid,
				   "checking fragmentation...");
		g_fd_close(pipe_out, 1);
		g_fd_close(pipe_err, 1);
		out = &app->out;
		err = &app->err;
		out->ch = g_io_channel_unix_new(pipe_out[0]);
		err->ch = g_io_channel_unix_new(pipe_err[0]);
		g_io_add_watch(out->ch,
			       G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
			       (GIOFunc)on_input_from_out, tmp);
		g_io_add_watch(err->ch,
			       G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
			       (GIOFunc)on_input_from_err, tmp);
		break;
	}
	return TRUE;
}

gint proc_abort(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	if (app->cdavl_pid < 0)
		return TRUE;

	app->is_abort = TRUE;
	kill(app->cdavl_pid, SIGTERM);
	return TRUE;
}

gint on_exec_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_anim		*anim = &app->anim;

	if (app->anim.interval) {
		anim->is_exec = TRUE;
		gtk_widget_set_sensitive(app->exec, FALSE);
		gtk_widget_set_sensitive(app->abort, TRUE);
	} else
		proc_exec(w, tmp);
	return TRUE;
}

gint on_abort_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_anim		*anim = &app->anim;

	if (app->anim.interval) {
		anim->exec_time.tv_sec = 0;
		anim->is_exec = FALSE;
		gtk_widget_set_sensitive(app->exec, TRUE);
		gtk_widget_set_sensitive(app->abort, FALSE);
	} else
		proc_abort(w, tmp);
	return TRUE;
}

void exit_dav(t_app_status* app) {
	clean_up(&app->cdavl_pid, &app->out, &app->out_B, &app->err);
	gtk_main_quit();
}

void on_delete(GtkWidget *w, GdkEvent *event, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	exit_dav(app);
}

/*
void destroy(GtkWidget *widget, gpointer data) {
	g_print("destroy event occurred\n");
	gtk_main_quit();
}
*/

gint on_device_changed(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	app->dev.s = (gchar*)gtk_entry_get_text(GTK_ENTRY(w));
	if (app->dev.s[0] == '\0')
		app->dev.s = NULL;
	return TRUE;
}

gint on_after_changed(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	app->after.s = (gchar*)gtk_entry_get_text(GTK_ENTRY(w));
	if (app->after.s[0] == '\0')
		app->after.s = NULL;
	return TRUE;
}

gint on_path_changed(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	app->path.s = (gchar*)gtk_entry_get_text(GTK_ENTRY(w));
	if (app->path.s[0] == '\0')
		app->path.s = NULL;
	return TRUE;
}

void update_view(gpointer tmp, gint ev_type) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_result	*r_B = ((t_all_data*)tmp)->result_B;

	init_gui(app, r, r_B, ev_type);
	draw_view(tmp, ev_type);
}

void page_button_proc(t_page* page, gboolean is_next,
		      t_page_func func, gpointer tmp, gint ev_type) {
	if (is_next) {
		if (page->page < page->page_max - 1) {
			page->page++;
			func(tmp, ev_type);
		}
	} else {
		if (page->page > 0) {
			page->page--;
			func(tmp, ev_type);
		}
	}
}

gint on_total_prev_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->blks[NOTEPAGE_TOTAL].page;

	page_button_proc(page, FALSE, update_view, tmp, EV_TOTAL_PAGE);
	return FALSE;
}
gint on_total_next_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->blks[NOTEPAGE_TOTAL].page;

	page_button_proc(page, TRUE, update_view, tmp, EV_TOTAL_PAGE);
	return FALSE;
}

gint on_frag_prev_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->blks[NOTEPAGE_FRAG].page;

	page_button_proc(page, FALSE, update_view, tmp, EV_FRAG_PAGE);
	return FALSE;
}
gint on_frag_next_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->blks[NOTEPAGE_FRAG].page;

	page_button_proc(page, TRUE, update_view, tmp, EV_FRAG_PAGE);
	return FALSE;
}

gint on_ffile_prev_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->ffrag.page;

	page_button_proc(page, FALSE, update_frag_files, tmp, EV_FFILE_PAGE);
	return FALSE;
}
gint on_ffile_next_clicked(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_page		*page = &app->ffrag.page;

	page_button_proc(page, TRUE, update_frag_files, tmp, EV_FFILE_PAGE);
	return FALSE;
}

void on_switch_tab(GtkNotebook *w, GtkNotebookPage *page,
		   gint pnum, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	gint		pnum_other;

	pnum_other = pnum == NOTEPAGE_TOTAL ? NOTEPAGE_FRAG : NOTEPAGE_TOTAL;
	gtk_widget_hide(app->fbview[pnum_other]);
	gtk_widget_show(app->fbview[pnum]);

	app->tab_page = pnum;
	update_view(tmp, EV_SWITCH_TAB);
}

gint on_fbview_toggled(GtkWidget *w, gpointer tmp) {
	update_view(tmp, EV_FBVIEW);
	return FALSE;
}

gint on_summary_changed(GtkWidget *w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	get_summary_cnt(&app->summary);
	update_view(tmp, EV_SUMMARY);
	return FALSE;
}

gint on_select_ffile(GtkWidget *w, gint row, gint column,
		     GdkEventButton *event, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_result	*r = ((t_all_data*)tmp)->result;
	t_page		*page = &app->ffrag.page;

	r->ffile_row = row += page->page * page->ln_per_page;
	update_view(tmp, EV_FRAG_VIEW);
	return FALSE;
}

gint on_unselect_ffile(GtkWidget *w, gint row, gint column,
		     GdkEventButton *event, gpointer tmp) {
	t_result	*r = ((t_all_data*)tmp)->result;

	r->ffile_row = -1;
	update_view(tmp, EV_FRAG_VIEW);
	return FALSE;
}

gint on_ffiles_click_column(GtkWidget *w, gint column, gpointer tmp) {
	t_result	*r = ((t_all_data*)tmp)->result;

	column--;
	if (column >= FFILE_INFO_CNT)
		return FALSE;

	switch (column) {
	case FFI_PERCENT:
		r->frag_info = g_slist_sort(r->frag_info, cmp_finfo_fper);
		break;
	case FFI_TOTAL:
		r->frag_info = g_slist_sort(r->frag_info, cmp_finfo_total);
		break;
	case FFI_FRAGS:
		r->frag_info = g_slist_sort(r->frag_info, cmp_finfo_frags);
		break;
	case FFI_SFRAGS:
		r->frag_info = g_slist_sort(r->frag_info, cmp_finfo_sfrags);
		break;
	default:
		r->frag_info = g_slist_sort(r->frag_info, cmp_finfo_path);
		break;
	}
	update_frag_files(tmp, EV_START);

	return FALSE;
}

void on_menu_normal(GtkWidget* w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	app->is_normal = TRUE;
	gtk_label_set_text(GTK_LABEL(app->dev.label), DEV_LABEL_STR);
	gtk_widget_hide(app->after.label);
	gtk_widget_hide(app->after.widget);
	gtk_widget_set_sensitive(app->path.label, TRUE);
	gtk_widget_set_sensitive(app->path.widget, TRUE);
}

void on_menu_diff(GtkWidget* w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	app->is_normal = FALSE;
	gtk_label_set_text(GTK_LABEL(app->dev.label), BEFORE_LABEL_STR);
	gtk_widget_show(app->after.label);
	gtk_widget_show(app->after.widget);
	gtk_widget_set_sensitive(app->path.label, FALSE);
	gtk_widget_set_sensitive(app->path.widget, FALSE);
}

void on_menu_quit(GtkWidget* w, gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;

	exit_dav(app);
}

void on_menu_version(GtkWidget *w, gpointer tmp) {
	display_version_dialog(tmp);
}



//gboolean one_shot_proc(gint phase)
gint idle(gpointer tmp) {
	t_app_status	*app = ((t_all_data*)tmp)->app;
	t_anim		*anim = &app->anim;
	struct timeval	cur, diff;

	gettimeofday(&cur, NULL);
	get_diff_time(&anim->exec_time, &cur, &diff);
	if (anim->is_exec
	    && (!anim->exec_time.tv_sec || diff.tv_sec >= anim->interval)) {
		anim->exec_time = cur;
		app->dev.s = anim->fpaths[anim->id];
		anim->id++;
		if (anim->id == anim->cnt)
			anim->id = 0;
		//update_popdown(combo);
		proc_exec(app->exec, tmp);
	} else
		usleep(50 * 1000);
	return 1;
}

void usage(void) {
	fprintf(stderr, "gdavl %s\n", GDAVL_VER);
	fprintf(stderr, "    %s\n\n", COPYRIGHT);
	fprintf(stderr, "gdavl [-bhs] [-S n] [devname [path]]" \
			"\t\t\t(normal mode)\n" \
		        "gdavl [-b] [-S n] [-d file-before file-after]" \
			"\t\t(diff mode)\n" \
		        "gdavl [-S n] [-a interval file-1 file-2 [file-3 ...]]"\
			"\t(animation mode)\n"
			);
	fprintf(stderr, "  -b: batch (no need to execution)\n");
	fprintf(stderr, "  -h: show this help message\n");
	fprintf(stderr, "  -s: do not use device-driver interface\n");
	fprintf(stderr, "      (this option takes effect" \
			      " only if the partition is mounted)\n");
	fprintf(stderr, "  -S: set to summarize 2^n\n");
	fprintf(stderr, "  -d: diff mode\n");
	fprintf(stderr, "  -a: animation mode" \
		              " (interval is the update seconds)\n");
}

int main(int argc, char *argv[]) {
	t_all_data	all;
	t_app_status	app;
	t_result	result;
	t_result	result_B;
	int		i, j, summarize = SUMMARIZE_DEFAULT;
	gboolean	is_anim = FALSE, is_batch = FALSE;
	char		*p;

	bzero(&app, sizeof(app));
	app.cdavl_pid = -1;
	app.is_normal = TRUE;
	bzero(&result, sizeof(result));
	bzero(&result_B, sizeof(result_B));
	all.app = &app;
	all.result = &result;
	all.result_B = &result_B;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (strcmp(argv[i], "-s") == 0)
				app.not_use_devdrv = 1;
			else if (strcmp(argv[i], "-S") == 0) {
				if (argc < i + 2) {
					fprintf(stderr, "invalid argument\n");
					return(1);
				}
				i++;
				summarize = strtoul(argv[i], &p, 10);
				if (p != argv[i] + strlen(argv[i])
				    || summarize < SUMMARIZE_MIN
				    || summarize > SUMMARIZE_MAX) {
					fprintf(stderr,
						"invalid summarize count:%s\n",
						argv[i]);
					return(1);
				}
			} else if (strcmp(argv[i], "-d") == 0) {
				app.is_normal = FALSE;
				if (argc != i + 2 + 1) {
					fprintf(stderr, "invalid file-count\n");
					return(1);
				}
				app.dev.s = argv[i+1];
				app.after.s = argv[i+2];
				break;
			} else if (strcmp(argv[i], "-a") == 0) {
				is_anim = TRUE;
				if (argc < i + 4) {
					fprintf(stderr, "invalid argument\n");
					return(1);
				}
				if (argc > i + 2 + MAX_POPDOWN_CNT) {
					fprintf(stderr, "too many files\n");
					return(1);
				}
				app.anim.cnt = argc - (i + 2);
				app.anim.fpaths = &argv[i + 2];
			} else {
				for (j = 1; j < strlen(argv[i]); j++)
					switch (argv[i][j]) {
					case 'b':
						is_batch = TRUE;
						break;
					case 's':
						app.not_use_devdrv = 1;
						break;
					default:
						usage();
						return(1);
					}
			}
		} else if (is_anim) {
			if (!app.anim.interval) {
				app.anim.interval = strtoul(argv[i], &p, 10);
				if (p != argv[i] + strlen(argv[i])
				    || app.anim.interval == 0) {
					fprintf(stderr, "invalid interval:%s\n",
						argv[i]);
					return(1);
				}
			}
		} else if (!app.dev.s)
			app.dev.s = argv[i];
		else if (!app.path.s)
			app.path.s = argv[i];
		else {
			usage();
			return(1);
		}
	}

	gtk_init(&argc, &argv);

	app.out.s = g_string_sized_new(OUT_DEFAULT_LEN);
	app.out_B.s = g_string_sized_new(OUT_DEFAULT_LEN);
	app.err.s = g_string_sized_new(ERR_DEFAULT_LEN);

	app.blks[NOTEPAGE_TOTAL].font
	= app.blks[NOTEPAGE_FRAG].font
	= app.note.font
	= app.text.font = gdk_font_load(GDAVL_FONT);

	create_window(&all);

	for (i = 0; i < MAX_BLK_ID; i++) {
		app.blks[NOTEPAGE_TOTAL].xpm[i]
		= app.blks[NOTEPAGE_FRAG].xpm[i]
		= app.note.xpm[i]
			= gdk_pixmap_create_from_xpm_d
				(app.note.widget->window, NULL, NULL,
				 (gchar**)xpm_pathes[i]);
		app.blks[NOTEPAGE_TOTAL].xpm[i + MAX_BLK_ID]
		= app.blks[NOTEPAGE_FRAG].xpm[i + MAX_BLK_ID]
			= gdk_pixmap_create_from_xpm_d
				(app.note.widget->window, NULL, NULL,
				 (gchar**)nc_xpm_pathes[i]);
	}
  	draw_note(&app.note);

	if (is_anim) {
		GtkCombo	*c1, *c2;
		c1 = GTK_COMBO(app.dev.widget);
		c2 = GTK_COMBO(app.path.widget);
		gtk_entry_set_editable(GTK_ENTRY(c1->entry), FALSE);
		gtk_entry_set_editable(GTK_ENTRY(c2->entry), FALSE);
	} else {
		if (app.dev.s)
			update_popdown(&app.dev);
		if (app.is_normal) {
			if (app.path.s)
				update_popdown(&app.path);
		} else {
			if (app.after.s)
				update_popdown(&app.after);
		}
	}

	gtk_spin_button_set_value(GTK_SPIN_BUTTON(app.summary.widget),
				  summarize);
	get_summary_cnt(&app.summary);
	init_gui(&app, &result, &result_B, EV_START);
	if (is_anim) {
		gtk_idle_add(idle, &all);
		on_exec_clicked(app.exec, &all);
	} else {
		if (is_batch)
			on_exec_clicked(app.exec, &all);
	}
	gtk_main();

	return(0);
}
