/* Find file command for the Midnight Commander
   Copyright (C) The Free Software Foundation
   Written 1995 by Miguel de Icaza

   Complete rewrote.
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include "tty.h"
#include <string.h>
#include <stdio.h>
#include <malloc.h>	/* For free() */
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif

/* unistd.h defines _POSIX_VERSION on POSIX.1 systems. */
#if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION)
#   include <dirent.h>
#   define NLENGTH(dirent) (strlen ((dirent)->d_name))
#else
#   define dirent direct
#   define NLENGTH(dirent) ((dirent)->d_namlen)

#   ifdef HAVE_SYS_NDIR_H
#       include <sys/ndir.h>
#   endif /* HAVE_SYS_NDIR_H */

#   ifdef HAVE_SYS_DIR_H
#       include <sys/dir.h>
#   endif /* HAVE_SYS_DIR_H */

#   ifdef HAVE_NDIR_H
#       include <ndir.h>
#   endif /* HAVE_NDIR_H */
#endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */

#include <sys/stat.h>
#include <sys/param.h>
#include "global.h"
#include "mad.h"
#include "util.h"
#include "win.h"
#include "color.h"
#include "global.h"

extern int verbose;		/* Should be in a more sensible header file */

/* Dialog manager and widgets */
#include "dlg.h"
#include "widget.h"

#include "dialog.h"     /* For do_refresh() */
#define  DIR_H_INCLUDE_HANDLE_DIRENT
#include "dir.h"
#include "panel.h"		/* current_panel */
#include "main.h"		/* do_cd, try_to_select */
#include "wtools.h"
#include "tree.h"
#include "../vfs/vfs.h"

#ifdef HAVE_XVIEW
#include "xvmain.h"
#endif


/* Size of the find parameters window */
#define FIND_Y 10
#define FIND_X 50

/* Size of the find window */
#define FIND2_Y 20
#define FIND2_X 50

/* A couple of extra messages we need */
#define B_STOP   B_USER+1
#define B_AGAIN  B_USER+2
#define B_PANELIZE	B_USER+3

#define B_TREE	 B_USER+1

/* A list of directories to be ignores, separated with ':' */
char *find_ignore_dirs = 0;

static Dlg_head *find_dlg;	/* The dialog */
static WInput *in_start;	/* Start path */
static WInput *in_name;		/* Pattern to search */
static WListbox *find_list;	/* Listbox with the file list */
static int running = 0;		/* nice flag */
static WButton *stop_button;	/* pointer to the stop button */
static WLabel *status_label;	/* Finished, Searching etc. */
static char *find_pattern;	/* Pattern to search */
static int count;		/* Number of files displayed */
static int matches;		/* Number of matches */
static int is_start;		/* Status of the start/stop toggle button */
int max_loops_in_idle = 10;
static char *old_dir;

static int find_par_callback (struct Dlg_head *h, int id, int Msg)
{
#ifndef HAVE_X
    switch (Msg){
    case DLG_DRAW:
	attrset (REVERSE_COLOR);
	dlg_erase (h);
	draw_box (h, 1, 1, FIND_Y-2, FIND_X-2);
	break;
    }
#endif
    return 0;
}

static char *in_start_dir = NULL;
static char *in_start_name = NULL;

static int find_parameters (char **start_dir, char **pattern)
{
    int return_value;
    char *temp_dir;

 find_par_start:
    if (!in_start_dir)
	in_start_dir = strdup (".");
    if (!in_start_name)
	in_start_name = strdup (easy_patterns ? "*" : ".");
    find_dlg = create_dlg (0, 0, FIND_Y, FIND_X, dialog_colors,
			   find_par_callback, "[Find File]", "findfile",
			   DLG_CENTER);
    x_set_dialog_title (find_dlg, "Find File");

    in_start = input_new (3, 14, INPUT_COLOR, 30, in_start_dir);
    in_name  = input_new (5, 14, INPUT_COLOR, 30, in_start_name);

#ifndef HAVE_XVIEW
    add_widget (find_dlg, in_start);
#endif
    tk_new_frame (find_dlg, "b.");
    add_widgetl (find_dlg, button_new (7, 36, B_CANCEL,"[ Cancel ]",'c',2,0,0),
        XV_WLAY_RIGHTOF);
    
#if HAVE_AVOID_DIRS
    add_widgetl (find_dlg, button_new (7, 22, B_USER, "[ Avoid... ]",
				      'a', 2,
				      avoid_box, 0),
	         XV_WLAY_RIGHTOF);
#endif

    add_widgetl (find_dlg, button_new (7, 12, B_TREE, "[ Tree ]", 't', 2,0,0),
        XV_WLAY_RIGHTOF);
    add_widgetl (find_dlg, button_new (7, 4, B_ENTER,"[ Ok ]",'o', 2, 0, 0),
        XV_WLAY_CENTERROW);
    tk_end_frame ();
    add_widgetl (find_dlg, in_name, XV_WLAY_RIGHTOF);

    
    add_widgetl (find_dlg, label_new (5, 3, "Filename:"), XV_WLAY_NEXTROW);

#ifdef HAVE_XVIEW
    add_widgetl (find_dlg, in_start, XV_WLAY_RIGHTOF);
#endif    
    add_widget (find_dlg, label_new (3, 3, "Start at:"));

    run_dlg (find_dlg);
    if (find_dlg->ret_value == B_CANCEL)
	return_value = 0;
    else if (find_dlg->ret_value == B_TREE){
	temp_dir = strdup (in_start->buffer);
	destroy_dlg (find_dlg);
	free (in_start_dir);
	if (strcmp (temp_dir, ".") == 0){
	    free (temp_dir);
	    temp_dir = strdup (cpanel->cwd);
	}
	in_start_dir = tree (temp_dir);
	if (in_start_dir)
	    free (temp_dir);
	else
	    in_start_dir = temp_dir;
	/* Warning: Dreadful goto */
	goto find_par_start;
    } else {
	return_value = 1;
	*start_dir = strdup (in_start->buffer);
	*pattern   = strdup (in_name->buffer);
	free (in_start_dir);
	in_start_dir = strdup (*start_dir);
	free (in_start_name);
	in_start_name = strdup (*pattern);
    }

    destroy_dlg (find_dlg);
			 
    return return_value;
}

typedef struct dir_stack {
    char *name;
    struct dir_stack *prev;
} dir_stack ;

dir_stack *dir_stack_base = 0;

static void push_directory (char *dir)
{
    dir_stack *new;

    new = xmalloc (sizeof (dir_stack), "find: push_directory");
    new->name = strdup (dir);
    new->prev = dir_stack_base;
    dir_stack_base = new;
}

static char *pop_directory (void)
{
    char *name; 
    dir_stack *next;

    if (dir_stack_base){
	name = dir_stack_base->name;
	next = dir_stack_base->prev;
	free (dir_stack_base);
	dir_stack_base = next;
	return name;
    } else
	return 0;
}

static void insert_file (char *dir, char *file)
{
    char *tmp_name;
    static char *dirname;
    int i;

    if (dir [0] == '/' && dir [1] == '/')
	dir++;
    i = strlen (dir);
    if (i){
	if (dir [i - 1] != '/'){
	    dir [i] = '/';
	    dir [i + 1] = 0;
	}
    }

    if (old_dir){
	if (strcmp (old_dir, dir)){
	    free (old_dir);
	    old_dir = strdup (dir);
	    dirname = listbox_add_item (find_list, 0, 0, dir, 0);
	}
    } else {
	old_dir = strdup (dir);
	dirname = listbox_add_item (find_list, 0, 0, dir, 0);
    }
    
    tmp_name = copy_strings ("    ", file, 0);
    listbox_add_item (find_list, 0, 0, tmp_name, dirname);
    free (tmp_name);
}

static char *rotating_dash = "|/-\\";

static void do_search (struct Dlg_head *h)
{
    static struct dirent *dp   = 0;
    static DIR  *dirp = 0;
    static char directory [MC_MAXPATHLEN+2];
    struct stat tmp_stat;
    static int pos;
    static int subdirs_left = 0;
    char *tmp_name;		/* For bulding file names */
    int p;

 do_search_begin:
    while (!dp){
	
	if (dirp){
	    mc_closedir (dirp);
	    dirp = 0;
	}
	
	while (!dirp){
	    char *tmp;

#ifndef HAVE_X	    
	    attrset (REVERSE_COLOR);
#endif
	    while (1) {
		tmp = pop_directory ();
		if (!tmp){
		    running = 0;
		    label_set_text (status_label, "Finished");
		    set_idle_proc (h, 0);
		    return;
		}
		if (find_ignore_dirs){
		    char *temp_dir = copy_strings (":", tmp, ":", 0);
		    if (strstr (find_ignore_dirs, temp_dir))
			free (tmp);
		    else
			break;
		} else
		    break;
	    } 
	    
	    strcpy (directory, tmp);
	    free (tmp);

	    if (verbose){
		    char buffer [50];
		    
		    sprintf (buffer, "Searching %s", name_trunc (directory, 
#ifndef HAVE_X
		    FIND2_X-20
#else		    
		    35
#endif
		    ));
		    label_set_text (status_label, buffer);
	    }
	    dirp = mc_opendir (directory);
	    mc_stat (directory, &tmp_stat);
	    subdirs_left = tmp_stat.st_nlink - 2;
	    /* Commented out as unnecessary
	    if (subdirs_left < 0)
	        subdirs_left = MAXINT;
	    */
	}
	dp = mc_readdir (dirp);
    }

    if (strcmp (dp->d_name, ".") == 0 ||
	strcmp (dp->d_name, "..") == 0){
	dp = mc_readdir (dirp);
#ifdef HAVE_XVIEW
	xv_post_proc (h, (void (*)(void *))do_search, (void *)h);
#endif		
	return;
    }
    
    tmp_name = get_full_name (directory, dp->d_name);

    if (subdirs_left){
	mc_lstat (tmp_name, &tmp_stat);
	if (S_ISDIR (tmp_stat.st_mode)){
	    push_directory (tmp_name);
	    subdirs_left--;
	}
    }
    
    if (regexp_match (find_pattern, dp->d_name, match_file)){
	insert_file (directory, dp->d_name);
	matches++;

	p = matches & 7;

	if (!p)
	    listbox_select_last (find_list, 1);
	else
	    listbox_select_last (find_list, 0);

#ifndef HAVE_X
	/* Updates the current listing */
	send_message (h, &find_list->widget, WIDGET_DRAW, 0);
	if (p == 7)
	    refresh ();
#endif	    
    }
    
    free (tmp_name);
    dp = mc_readdir (dirp);

    /* Displays the nice dot */
    count++;
    if (!(count & 31)){
	if (verbose){
#ifndef HAVE_X
	    pos = (++pos) % 4;
	    attrset (NORMALC);
	    dlg_move (h, FIND2_Y-6, FIND2_X - 4);
	    addch (rotating_dash [pos]);
	    refresh ();
	}
    } else
	goto do_search_begin;
#else
        }
    }
#ifdef HAVE_XVIEW
    xv_post_proc (h, (void (*)(void *))do_search, (void *)h);
#endif		
#endif

}

static int find_callback (struct Dlg_head *h, int id, int Msg)
{
    switch (Msg){
#ifndef HAVE_X    
    case DLG_DRAW:
	attrset (REVERSE_COLOR);
	dlg_erase (h);
	draw_box (h, 1, 1, FIND2_Y-2, FIND2_X-2);
	attrset (COLOR_HOT_NORMAL);
	dlg_move (h, 1, 2);
	addstr (" Find file ");
	break;
#endif

    case DLG_KEY:
	if (id == KEY_F(3) || id == KEY_F(13)){
	    int unparsed_view = (id == KEY_F(13));
	    WLEntry *entry = find_list->current;
	    char *dir, *name;

	    dir = entry->data;
	    
	    if (!entry->text || !dir)
		return MSG_NOT_HANDLED;

	    if (dir [0] == '.' && dir [1] == 0)
		name = strdup (entry->text + 4);
	    else if (dir [0] == '.' && dir [1] == '/')
		name = get_full_name (dir+2, entry->text + 4);
	    else
		name = get_full_name (dir, entry->text+4);

	    view_file (name, unparsed_view, use_internal_view);
	    free (name);
	    return MSG_HANDLED;
	}
	return MSG_NOT_HANDLED;
	
    case DLG_IDLE:
	do_search (h);
	break;
    }
    return 0;
}

#define BUTTON_NEW(i,msg,str) \
   button_new (FIND2_Y-4, 3+i*12, msg, str, str [2], 2, 0, 0)

#define BUTTON_NEW_CBACK(i,msg,str,a,b) \
   button_new (FIND2_Y-4, 3+i*12, msg, str, str [2], 2, a, b)

/* Handles the Stop/Start button in the find window */
static int start_stop (int button, void *extra)
{
    char *button_labels [2] = { "[ Stop ] ", "[ Start ]" };
    
    running = is_start;
    set_idle_proc (find_dlg, running);
    is_start = !is_start;

    label_set_text (status_label, is_start ? "Stopped" : "Searching");
    button_set_text (stop_button, button_labels [is_start]);
    
    return 0;
}

static void init_find_vars (void)
{
    char *dir;
    
    if (old_dir){
	free (old_dir);
	old_dir = 0;
    }
    count = 0;
    matches = 0;

    /* Remove all the items in the stack */
    while ((dir = pop_directory ()) != NULL)
	free (dir);
}

static int find_file (char *start_dir, char *pattern,
		      char **dirname,  char **filename)
{
    int return_value = 0;
    char *dir;
    char *dir_tmp, *file_tmp;
    
    find_dlg = create_dlg (0, 0, FIND2_Y, FIND2_X, dialog_colors,
			   find_callback, "[Find File]", "mfind", DLG_CENTER);
    
    x_set_dialog_title (find_dlg, "Find file");

    find_list = listbox_new (2, 2, FIND2_X-4, FIND2_Y-9, listbox_finish, 0);

#ifndef HAVE_X
    add_widget (find_dlg, find_list);
#endif

    tk_new_frame (find_dlg, "b.");
    add_widgetl (find_dlg,
		button_new (FIND2_Y-3, 3, B_PANELIZE, "[ Panelize ]", 'p', 2, 0, 0),
		XV_WLAY_CENTERROW);
    add_widgetl (find_dlg, BUTTON_NEW (3, B_CANCEL, "[ Quit ]"),
        XV_WLAY_RIGHTOF);
    stop_button = BUTTON_NEW_CBACK (2, B_STOP,   "[ Stop  ]", start_stop,
				    find_dlg);
    add_widgetl (find_dlg, stop_button,
        XV_WLAY_RIGHTOF);
    add_widgetl (find_dlg, BUTTON_NEW (1, B_AGAIN,  "[ Again ]"),
        XV_WLAY_RIGHTOF);
    add_widgetl (find_dlg, BUTTON_NEW (0, B_ENTER,  "[ Chdir ]"), 
        XV_WLAY_CENTERROW);
    tk_end_frame ();

    status_label = label_new (FIND2_Y-6, 4, "Searching");
    add_widgetl (find_dlg, status_label, XV_WLAY_BELOWOF);
#ifdef HAVE_X
    add_widgetl (find_dlg, find_list, XV_WLAY_EXTENDWIDTH);
#endif

    /* Need to cleanup this */
    find_pattern = pattern;

    set_idle_proc (find_dlg, 1);
    init_find_vars ();
    push_directory (start_dir);

#ifdef HAVE_XVIEW
    xv_post_proc (find_dlg, (void (*)(void *))do_search, (void *)find_dlg);
#endif    
    run_dlg (find_dlg);

    return_value = find_dlg->ret_value;

    /* Remove all the items in the stack */
    while ((dir = pop_directory ()) != NULL)
	free (dir);
    
    listbox_get_current (find_list, &file_tmp, &dir_tmp);

    if (dir_tmp)
	*dirname  = strdup (dir_tmp);
    if (file_tmp)
	*filename = strdup (file_tmp);
    if (return_value == B_PANELIZE && *dirname && *filename){
	struct dirent dp;
	int status, link_to_dir, stalled_link;
	int next_free = 0;
	int i;
	struct stat buf;
	WLEntry *entry = find_list->list;
	dir_list *list = &cpanel->dir;
	char *dir, *name;

	clean_dir (list, cpanel->count);
	for (i = 0; entry && i < find_list->count; entry = entry->next, i++){
	    if (!entry->text || !entry->data)
		continue;
	    dir = entry->data;
	    if (dir [0] == '.' && dir [1] == 0)
		name = strdup (entry->text + 4);
	    else if (dir [0] == '.' && dir [1] == '/')
		name = get_full_name (dir + 2, entry->text + 4);
	    else
		name = get_full_name (dir, entry->text + 4);
	    strcpy (dp.d_name, name);
	    free (name);
#if !defined(HAVE_DIRENT_H) && !defined(_POSIX_VERSION)
	    NLENGTH (&dp) = strlen (dp.d_name);
#endif
	    status = handle_dirent (list, NULL, &dp, &buf, next_free, &link_to_dir,
	        &stalled_link);
	    if (status == 0)
		continue;
	    if (status == -1)
		break;
	    list->list [next_free].fnamelen = strlen (dp.d_name);
	    list->list [next_free].fname = strdup (dp.d_name);
	    list->list [next_free].cache = NULL;
	    file_mark (cpanel, next_free, 0);
	    list->list [next_free].f.link_to_dir = link_to_dir;
	    list->list [next_free].f.stalled_link = stalled_link;
	    list->list [next_free].buf = buf;
	    next_free++;
           if (!(next_free & 15))
	       rotate_dash ();
	}
	if (next_free){
	    cpanel->count = next_free;
	    cpanel->is_panelized = 1;
	    if (start_dir [0] == '/'){
		strcpy (cpanel->cwd, "/");
		chdir ("/");
	    }
	}
    }

    destroy_dlg (find_dlg);
    if (old_dir){
	free (old_dir);
	old_dir = 0;
    }
    return return_value;
}

void do_find (void)
{
    char *start_dir, *pattern;
    char *filename, *dirname;
    int  v, dir_and_file_set;
    int done = 0;
    
    while (!done){
	if (!find_parameters (&start_dir, &pattern))
	    break;

	dirname = filename = NULL;
	v = find_file (start_dir, pattern, &dirname, &filename);
	free (start_dir);
	free (pattern);

	if (v == B_ENTER){
	    if (dirname || filename){
		if (dirname){
		    do_cd (dirname);
		    if (filename)
			try_to_select (current_panel, filename + 4);
		} else if (filename)
		    do_cd (filename);
		paint_panel (current_panel);
		select_item (cpanel);
	    }
	    break;
	}
	dir_and_file_set = dirname && filename;
	if (dirname)  free (dirname);
	if (filename) free (filename);
	if (v == B_CANCEL)
	    break;
	if (v == B_PANELIZE){
	    if (dir_and_file_set){
	        try_to_select (cpanel, NULL);
	        paint_panel (cpanel);
	    }
	    break;
	}
    }
}

