/* Routines invoked by a function key
   They normally operate on the current panel.
   
   Copyright (C) 1994, 1995 Miguel de Icaza
   Copyright (C) 1994, 1995 Janne Kukonlehto
   
   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>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "tty.h"
#include <stdio.h>
#include <stdlib.h>		/* getenv (), rand */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <malloc.h>
#include <string.h>
#include <fcntl.h>		/* open, O_RDWR */
#include <errno.h>

#ifdef USE_NETCODE
#include <netdb.h>
#endif

#ifdef HAVE_MMAP
#   include <sys/mman.h>
#endif
#include <sys/time.h>
#include "mad.h"
#include "dir.h"
#include "util.h"
#include "panel.h"
#include "cmd.h"		/* Our definitions */
#include "view.h"		/* view() */
#include "dialog.h"		/* query_dialog, message */
#include "file.h"		/* the file operations */
#include "find.h"		/* do_find */
#include "hotlist.h"
#include "tree.h"
#include "subshell.h"		/* use_subshell */
#include "cons.saver.h"
#include "global.h"
#include "dlg.h"		/* required by wtools.h */
#include "widget.h"		/* required by wtools.h */
#include "wtools.h"		/* listbox */
#include "command.h"		/* for input_w */
#include "win.h"		/* do_exit_ca_mode */
#include "layout.h"		/* get_current/other_type */
#include "ext.h"		/* regex_command */
#include "view.h"		/* view */
#include "key.h"		/* get_key_code */
#include "help.h"		/* interactive_display */
#include "fs.h"
#include "boxes.h"		/* cd_dialog */
#include "color.h"
#include "../vfs/vfs.h"
#define WANT_WIDGETS
#include "main.h"		/* global variables, global functions */
#ifndef MAP_FILE
#   define MAP_FILE 0
#endif

/* If set and you don't have subshell support,then C-o will give you a shell */
int output_starts_shell = 0;

/* Source routing destination */
int source_route = 0;

/* This is used since the parameter panel on some of the commands */
/* defined in this file may receive a 0 parameter if they are invoked */
/* The drop down menu */

WPanel *get_a_panel (WPanel *panel)
{
    if (panel)
	return panel;
    if (get_current_type () == view_listing){
	return (WPanel *) get_panel_widget (get_current_index ());
    } else
	return (WPanel *) get_panel_widget (get_other_index ());
}

int view_file (char *filename, int normal, int internal)
{
    static char *viewer = 0;
    int move_dir = 0;


    if (normal) {
        int changed_hex_mode = 0;
        int changed_nroff_flag = 0;
    
        altered_hex_mode = 0;
        altered_nroff_flag = 0;
        if (default_hex_mode)
            changed_hex_mode = 1;
        if (default_nroff_flag)
            changed_nroff_flag = 1;
        default_hex_mode = 0;
        default_nroff_flag = 0;
        view (0, filename, &move_dir);
        if (changed_hex_mode && !altered_hex_mode)
            default_hex_mode = 1;
        if (changed_nroff_flag && !altered_nroff_flag)
            default_nroff_flag = 1;
        repaint_screen ();
        return move_dir;
    }
    if (internal){
	if (!regex_command (filename, "View", NULL, &move_dir)){
	    view (0, filename, &move_dir);
	    repaint_screen ();
	}
    } else {
	if (!viewer){
	    viewer = getenv ("PAGER");
	    if (!viewer)
		viewer = "view";
	}
	execute_internal (viewer, filename);
    }
    return move_dir;
}

static int scan_for_file (WPanel *panel, int idx, int direction)
{
    int i = idx + direction;

    while (i != idx){
	if (i < 0)
	    i = panel->count - 1;
	if (i == panel->count)
	    i = 0;
	if (!S_ISDIR (panel->dir.list [i].buf.st_mode))
	    return i;
	i += direction;
    }
    return i;
}

static void view_cmd_internal (WPanel *panel, int normal)
{
    int dir, file_idx;
    panel = get_a_panel (panel);
    
    /* Directories are viewed by changing to them */
    if (S_ISDIR (selection (panel)->buf.st_mode) ||
	link_isdir (selection (panel))){
	if (confirm_view_dir && (panel->marked || panel->dirs_marked)){
	    if (query_dialog (" CD ", "Files tagged, want to cd?",
			      0, 2, " Yes ", " No ") == 1){
		return;
	    }
	}
	do_cd (selection (panel)->fname);
	return;
	
    }

    file_idx = panel->selected;
    while (1) {
	char *filename;

	filename = panel->dir.list [file_idx].fname;

	dir = view_file (filename, normal, use_internal_view);
	if (dir == 0)
	    break;
	file_idx = scan_for_file (panel, file_idx, dir);
    }
}

void view_cmd (WPanel *panel)
{
    view_cmd_internal (panel, 0);
}

void view_simple_cmd (void)
{
    view_cmd_internal (cpanel, 1);
}

void filtered_view_cmd (WPanel *panel)
{
    char *command;

    panel = get_a_panel (panel);
    command = input_dialog (" Filtered view ", "Filter command and arguments:",
			    selection (panel)->fname);
    if (!command)
	return;

    view (command, "", 0);

    free (command);
}

void filtered_view_cmd_cpanel (void)
{
    filtered_view_cmd (cpanel);
}

void do_edit (const char *what)
{
    static char *editor = 0;

    if (!editor){
	editor = getenv ("EDITOR");
	if (!editor)
	    editor = "vi";
    }
    execute_internal (editor, what);
}

void edit_cmd (WPanel *panel)
{
    panel = get_a_panel(panel);
    if (!regex_command (selection (panel)->fname, "Edit", NULL, 0))
        do_edit (selection (panel)->fname);
}

void copy_cmd (void)
{
    save_cwds_stat ();
    if (panel_operate (OP_COPY, NULL)){
	update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
	repaint_screen ();
    }
}

void ren_cmd (void)
{
    save_cwds_stat ();
    if (panel_operate (OP_MOVE, NULL)){
	update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
	repaint_screen ();
    }
}

void copymove_cmd_with_default (int copy, char *thedefault)
{
    save_cwds_stat ();
    if (panel_operate (copy ? OP_COPY : OP_MOVE, thedefault)){
	update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
	repaint_screen ();
    }
}

void mkdir_cmd (WPanel *panel)
{
    char *dir;
    
    panel = get_a_panel (panel);
    dir = input_expand_dialog (" Mkdir ", "Enter directory name:" , "");
    
    if (!dir)
	return;

    save_cwds_stat ();
    if (my_mkdir (dir, 0777) == 0){
	update_panels (UP_OPTIMIZE, dir, UP_KEEPSEL);
	repaint_screen ();
	select_item (cpanel);
	free (dir);
	return;
    }
    free (dir);
    message (1, " Error ", "  %s  ", unix_error_string (errno));
}

void delete_cmd (void)
{
    save_cwds_stat ();

    if (panel_operate (OP_DELETE, NULL)){
	update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
	repaint_screen ();
    }
}

void find_cmd (void)
{
    do_find ();
}

void filter_cmd (void)
{
    char *reg_exp;
    char *x;
    WPanel *p;

    if (!SELECTED_IS_PANEL)
	return;

    p = MENU_PANEL;

    x = p->filter ? p->filter : easy_patterns ? "*" : ".";
	
    reg_exp = input_dialog (" Filter ", "", x);
    mad_check (__FILE__, __LINE__);
    if (!reg_exp)
	return;
    mad_check (__FILE__, __LINE__);
    if (p->filter){
	mad_check (__FILE__, __LINE__);
	free (p->filter);
	mad_check (__FILE__, __LINE__);
	p->filter = 0;
    }
    mad_check (__FILE__, __LINE__);
    if (!(reg_exp [0] == '*' && reg_exp [1] == 0))
	p->filter = reg_exp;
    mad_check (__FILE__, __LINE__);
    reread_cmd ();
}

void reread_cmd (void)
{
    int flag;

    mad_check (__FILE__, __LINE__);
    if (get_current_type () == view_listing &&
	get_other_type () == view_listing)
	flag = strcmp (cpanel->cwd, opanel->cwd) ? UP_ONLY_CURRENT : 0;
    else
	flag = UP_ONLY_CURRENT;
	
    update_panels (UP_RELOAD|flag, UP_KEEPSEL, UP_KEEPSEL);
    repaint_screen ();
}

/* Panel sorting related routines */
void do_re_sort (WPanel *panel)
{
    char *filename;
    int  i;

    panel = get_a_panel (panel);
    filename = strdup (selection (cpanel)->fname);
    unselect_item (panel);
    do_sort (&panel->dir, panel->sort_type, panel->count-1, panel->reverse, panel->case_sensitive);
    panel->selected = -1;
    for (i = panel->count; i; i--){
	if (!strcmp (panel->dir.list [i-1].fname, filename)){
	    panel->selected = i-1;
	    break;
	}
    }
    free (filename);
    cpanel->top_file = cpanel->selected - llines (cpanel)/2;
    if (cpanel->top_file < 0)
	cpanel->top_file = 0;
    select_item (panel);
    panel_update_contents (panel);
}

void reverse_selection_cmd (void)
{
    file_entry *file;
    int i;

    for (i = 0; i < cpanel->count; i++){
	file = &cpanel->dir.list [i];
	if (S_ISDIR (file->buf.st_mode))
	    continue;
	if (file->f.marked){
	    cpanel->marked--;
	    cpanel->total-=file->buf.st_size;
	} else {
	    cpanel->marked++;
	    cpanel->total+=file->buf.st_size;
	}
	file_mark (cpanel, i, !file->f.marked);
    }
    paint_panel (cpanel);
}

void select_cmd (void)
{
    char *reg_exp, *reg_exp_t;
    int i;
    int c;
    int dirflag = 0;

    reg_exp = input_dialog (" Select ", "", easy_patterns ? "*" : ".");
    if (!reg_exp)
	return;
    
    reg_exp_t = reg_exp;

    /* Check if they specified a directory */
    if (*reg_exp_t == PATH_SEP){
        dirflag = 1;
        reg_exp_t++;
    }
    if (reg_exp_t [strlen(reg_exp_t) - 1] == PATH_SEP){
        dirflag = 1;
        reg_exp_t [strlen(reg_exp_t) - 1] = 0;
    }

    for (i = 0; i < cpanel->count; i++){
        if (!strcmp( cpanel->dir.list [i].fname, ".."))
            continue;
	if (S_ISDIR (cpanel->dir.list [i].buf.st_mode)){
	    if (!dirflag)
                continue;
        } else {
            if (dirflag)
                continue;
	}
	c = regexp_match (reg_exp_t, cpanel->dir.list [i].fname, match_file);
	if (c == -1){
	    message (1, " Error ", "  Malformed regular expression  ");
	    free (reg_exp);
	    return;
	}
	if (c){
	    if (!cpanel->dir.list [i].f.marked){
		cpanel->marked++;
		if (S_ISDIR (cpanel->dir.list[i].buf.st_mode)) {
		    if (cpanel->has_dir_sizes)
		        cpanel->total += cpanel->dir.list [i].buf.st_size;
		    cpanel->dirs_marked++;
		} else
		    cpanel->total += cpanel->dir.list [i].buf.st_size;
	    }
	    file_mark (cpanel, i, 1);
	}
    }
    paint_panel (cpanel);
    free (reg_exp);
}

void unselect_cmd (void)
{
    char *reg_exp, *reg_exp_t;
    int i;
    int c;
    int dirflag = 0;

    reg_exp = input_dialog (" Unselect ","", easy_patterns ? "*" : ".");
    if (!reg_exp)
	return;
    
    reg_exp_t = reg_exp;
    
    /* Check if they specified directory matching */
    if (*reg_exp_t == PATH_SEP){
        dirflag = 1;
	reg_exp_t ++;
    }
    if (reg_exp_t [strlen(reg_exp_t) - 1] == PATH_SEP){
        dirflag = 1;
        reg_exp_t [strlen(reg_exp_t) - 1] = 0;
    }
    for (i = 0; i < cpanel->count; i++){
        if (!strcmp( cpanel->dir.list [i].fname, "..")) 
            continue;
	if (S_ISDIR (cpanel->dir.list [i].buf.st_mode)){
	    if (!dirflag)
	        continue;
        } else {
            if (dirflag)
                continue;
        }
	c = regexp_match (reg_exp_t, cpanel->dir.list [i].fname, match_file);
	if (c == -1){
	    message (1, " Error ", "  Malformed regular expression  ");
	    free (reg_exp);
	    return;
	}
	if (c){
	    if (cpanel->dir.list [i].f.marked){
		if (S_ISDIR (cpanel->dir.list[i].buf.st_mode)) {
		    if (cpanel->has_dir_sizes)
		        cpanel->total -= cpanel->dir.list [i].buf.st_size;
		    cpanel->dirs_marked--;
		} else
		    cpanel->total -= cpanel->dir.list [i].buf.st_size;
		cpanel->marked--;
	    }
	    file_mark (cpanel, i, 0);
	    cpanel->dir.list [i].f.marked = 0;
	}
    }
    paint_panel (cpanel);
    free (reg_exp);
}

/* Check if the file exists */
/* If not copy the default */
static int check_for_default(char *default_file, char *file)
{
    struct stat s;
    if (mc_stat (file, &s)){
	if (mc_stat (default_file, &s)){
	    return -1;
	}
	create_op_win (OP_COPY);
	copy_file_file (default_file, file, 0, 1);
	destroy_op_win ();
    }
    return 0;
}

void ext_cmd (void)
{
    char *buffer;
    int  dir;

    dir = 0;
    if (geteuid () == 0){
	dir = query_dialog ("Extension file edit",
			    " Which extension file you want to edit? ", 0, 2,
			    " User ", " System Wide ");
    }
    if (dir == 0){
	buffer = copy_strings (home_dir, "/.mc.ext", 0);
	check_for_default (LIBDIR "mc.ext", buffer);
	do_edit (buffer);
	free (buffer);
    } else if (dir == 1)
	do_edit (LIBDIR "mc.ext");
}

void menu_edit_cmd (void)
{
#define MC_MENU "mc.menu"

    char *buffer, *home_file;
    int dir = 0;
    
    if (geteuid () == 0)
	dir = query_dialog ("Menu file edit",
			    " Which menu file will you edit? ", 0, 2,
			      " User ", " System Wide ");

    home_file = copy_strings (home_dir, "/.mc.menu", 0);

    switch (dir){
    case 0:
	buffer = strdup (home_file);
	check_for_default (LIBDIR MC_MENU, buffer);
	break;

    case 1:
	buffer = copy_strings (LIBDIR, MC_MENU, 0);
	break;

    default:
	free (home_file);
	return;
    }
    do_edit (buffer);
    free (buffer);
    free (home_file);
}

void quick_chdir_cmd (void)
{
    char *target;

    target = hotlist_cmd (0);
    if (!target)
	return;

    if (get_current_type () == view_tree)
	tree_chdir (the_tree, target);
    else
	do_cd (target);
}

#ifdef USE_VFS
void reselect_vfs (void)
{
    char *target;

    target = hotlist_cmd (1);
    if (!target)
	return;

    do_cd (target);
    free (target);
}
#endif

static int compare_files (char *name1, char *name2, long size)
{
    int file1, file2;
    char *data1, *data2;
    int result = -1;		/* Different by default */

#ifdef HAVE_MMAP
    /* Ugly if jungle */
    file1 = open (name1, O_RDONLY);
    if (file1 >= 0){
	file2 = open (name2, O_RDONLY);
	if (file2 >= 0){
	    data1 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file1, 0);
	    if (data1 != (char*) -1){
		data2 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file2, 0);
		if (data2 != (char*) -1){
		    rotate_dash ();
		    result = memcmp (data1, data2, size);
		    munmap (data2, size);
		}
		munmap (data1, size);
	    }
	    close (file2);
	}
	close (file1);
    }
#endif
    return result;
}

static void compare_dir (WPanel *panel, WPanel *other, int thorough_flag)
{
    int i, j;
    char *src_name, *dst_name;

    panel = get_a_panel (panel);
    
    /* No marks by default */
    panel->marked = 0;
    panel->total = 0;
    panel->dirs_marked = 0;
    
    /* Handle all files in the panel */
    for (i = 0; i < panel->count; i++){
	file_entry *source = &panel->dir.list[i];

	/* Default: unmarked */
	file_mark (panel, i, 0);

	/* Skip directories */
	if (S_ISDIR (source->buf.st_mode))
	    continue;

	/* Search the corresponding entry from the other panel */
	for (j = 0; j < other->count; j++){
	    if (strcmp (source->fname,
			other->dir.list[j].fname) == 0)
		break;
	}
	if (j >= other->count)
	    /* Not found -> mark */
	    file_mark (panel, i, 1);
	else {
	    /* Found */
	    file_entry *target = &other->dir.list[j];

	    /* Older version is not marked */
	    if (source->buf.st_mtime < target->buf.st_mtime)
		continue;
	    /* Newer version with different size is marked */
	    if (source->buf.st_size != target->buf.st_size){
		file_mark (panel, i, 1);
	    } else if (!thorough_flag){
		/* Thorough compare off, compare only time stamps */
		/* Mark newer version, don't mark version with the same date */
		if (source->buf.st_mtime > target->buf.st_mtime)
		    file_mark (panel, i, 1);
		else
		    continue;
	    } else {
		/* Thorough compare on, do byte-by-byte comparison */
		src_name = get_full_name (panel->cwd, source->fname);
		dst_name = get_full_name (other->cwd, target->fname);
		if (compare_files (src_name, dst_name, source->buf.st_size))
		    file_mark (panel, i, 1);
		free (src_name);
		free (dst_name);
	    }
	}

	/* If marked increase counts */
	if (source->f.marked){
	    panel->marked++;
	    panel->total += source->buf.st_size;
	}
    } /* for (i ...) */
}

void compare_dirs_cmd (void)
{
    int thorough_flag = 0;

#ifdef HAVE_MMAP
    thorough_flag = query_dialog (" Compare directories ", " Select compare method: ",
				  0, 3, " Quick ", " Thorough ", " Cancel ");
    if (thorough_flag < 0 || thorough_flag > 1)
	return;
#endif
    if (get_current_type () == view_listing &&
	get_other_type () == view_listing){
	compare_dir (cpanel, opanel, thorough_flag);
	compare_dir (opanel, cpanel, thorough_flag);
	paint_panel (cpanel);
	paint_panel (opanel);
    } else {
	message (1, " Error ", " Both panels should be on the listing view mode to use this command ");
    }
}

void history_cmd (void)
{
    Listbox *listbox;
    Hist *current;

    if (input_w (cmdline)->need_push){
	if (push_history (input_w (cmdline), input_w (cmdline)->buffer) == 2)
	    input_w (cmdline)->need_push = 0;
    }
    if (!input_w (cmdline)->history){
	message (1, " Error ", " The command history is empty ");
	return;
    }
    current = input_w (cmdline)->history;
    while (current->prev)
	current = current->prev;
    listbox = create_listbox_window (60, 10, " Command history ",
				     "[Command Menu]");
    while (current){
	LISTBOX_APPEND_TEXT (listbox, 0, current->text,
			     current);
	current = current->next;
    }
    run_dlg (listbox->dlg);
    if (listbox->dlg->ret_value == B_CANCEL)
	current = NULL;
    else
	current = listbox->list->current->data;
    destroy_dlg (listbox->dlg);
    free (listbox);

    if (!current)
	return;
    input_w (cmdline)->history = current;
    assign_text (input_w (cmdline), input_w (cmdline)->history->text);
    update_input (input_w (cmdline));
}

#ifndef HAVE_XVIEW
void swap_cmd (void)
{
    swap_panels ();
    touchwin (stdscr);
    repaint_screen ();
}
#endif

void view_other_cmd (void)
{
    static int message_flag = TRUE;
#ifdef HAVE_SUBSHELL_SUPPORT
    char *new_dir = NULL;
    char **new_dir_p;
#endif

    if (!xterm_flag && !console_flag && !use_subshell){
	if (message_flag)
	    message (1, " Error ", " Not an xterm or Linux console; \n"
				   " the panels cannot be toggled. ");
	message_flag = FALSE;
    } else {
#ifndef HAVE_X
	if (use_mouse_p)
	    shut_mouse ();
#endif	    

	if (clear_before_exec) {
	    clr_scr ();
	}

	if (alternate_plus_minus && (console_flag || xterm_flag)) {
	    fprintf (stderr, "\033>"); fflush (stderr);
	}
#ifndef HAVE_SLANG
	/* With slang we don't want any of this, since there
	 * is no mc_raw_mode supported
	 */
	reset_shell_mode ();
	noecho ();
#endif
	endwin ();
	if (!status_using_ncurses)
	    do_exit_ca_mode ();
	mc_raw_mode ();
	if (console_flag)
	    restore_console ();

#ifdef HAVE_SUBSHELL_SUPPORT
	if (use_subshell){
	    new_dir_p = vfs_current_is_local () ? &new_dir : NULL;
	    if (invoke_subshell (NULL, VISIBLY, new_dir_p))
		return;  /* User did `exit' or `logout': quit MC quietly */
	} else
#endif
	{
	    if (output_starts_shell){
		fprintf (stderr,
		 "Type `exit' to return to the Midnight Commander\n\r\n\r");
		my_system (1, shell, NULL);
	    } else
		get_key_code (0);
	}
	if (console_flag)
	    handle_console (CONSOLE_SAVE);

	if (!status_using_ncurses)
	    do_enter_ca_mode ();
	    
	if (alternate_plus_minus && (console_flag || xterm_flag)) {
	    fprintf (stderr, "\033="); fflush (stderr);
	}
	reset_prog_mode ();
	
#ifndef HAVE_X	
	if (use_mouse_p)
	    init_mouse ();
#endif	    

#ifdef HAVE_SUBSHELL_SUPPORT
	if (use_subshell){
	    load_prompt (0, 0);
	    if (new_dir)
		do_possible_cd (new_dir);
	    if (console_flag && output_lines)
		show_console_contents (output_start_y,
				       LINES-keybar_visible-output_lines-1,
				       LINES-keybar_visible-1);
	}
#endif
        touchwin (stdscr);
	repaint_screen ();
    }
}

#ifndef _OS_NT
static void do_link (int symbolic_link)
{
    struct stat s;
    char *dest, *src;
    int  stat_r;

    if (!symbolic_link){
	stat_r = mc_stat (selection (cpanel)->fname, &s);
	if (stat_r != 0){
	    message (1, " Error ", " Couldn't stat %s \n %s ",
		     selection (cpanel)->fname, unix_error_string (errno));
	    return;
	}
	if (!S_ISREG (s.st_mode))
	    return;
    }
    
    if (!symbolic_link){
        src = copy_strings (" Link ", name_trunc (selection (cpanel)->fname, 46), 
            " to:", NULL);
	dest = input_expand_dialog (" Link ", src, "");
	free (src);
	if (!dest)
	    return;
	if (!*dest) {
	    free (dest);
	    return;
	}
	save_cwds_stat ();
	if (-1 == mc_link (selection (cpanel)->fname, dest))
	    message (1, " Error ", " link: %s ", unix_error_string (errno));
    } else {
#ifdef OLD_SYMLINK_VERSION
        symlink_dialog (selection (cpanel)->fname, "", &dest, &src);
#else
	/* suggest the full path for symlink */
        char s[MC_MAXPATHLEN];
        char d[MC_MAXPATHLEN];
	
        strcpy(s, cpanel->cwd);
        if ( ! ((s[0] == '/') && (s[1] == 0)))
            strcat(s, "/");
        strcat(s, selection (cpanel)->fname);
        strcpy(d, opanel->cwd);
        if ( ! ((d[0] == '/') && (d[1] == 0)))
            strcat(d, "/");
        symlink_dialog (s, d, &dest, &src);
#endif
	if (!dest || !*dest) {
	    if (src)
	        free (src);
	    if (dest)
	        free (dest);
	    return;
	}
	if (src){
	    if (*src) {
	        save_cwds_stat ();
	        if (-1 == mc_symlink (dest, src))
		    message (1, " Error ", " symlink: %s ",
			     unix_error_string (errno));
	    }
	    free (src);
	}
    }
    free (dest);
    update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
    repaint_screen ();
}

void link_cmd (void)
{
    do_link (0);
}

void symlink_cmd (void)
{
    do_link (1);
}

void edit_symlink_cmd (void)
{
    if (S_ISLNK (selection (cpanel)->buf.st_mode)) {
	char buffer [MC_MAXPATHLEN], *p = selection (cpanel)->fname;
	int i;
	char *dest, *q = copy_strings (" Symlink ", name_trunc (p, 32), " points to:", NULL);
	
	i = readlink (p, buffer, MC_MAXPATHLEN);
	if (i > 0) {
	    buffer [i] = 0;
	    dest = input_expand_dialog (" Edit symlink ", q, buffer);
	    if (dest) {
		if (*dest && strcmp (buffer, dest)) {
		    save_cwds_stat ();
		    mc_unlink (p);
		    if (-1 == mc_symlink (dest, p))
		        message (1, " Error ", " edit symlink: %s ",
				 unix_error_string (errno));
		    update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
		    repaint_screen ();
		}
		free (dest);
	    }
	}
	free (q);
    }
}

void other_symlink_cmd (void)
{
    char *dest, *q, *p, *r, *s, *t;

    if (get_other_type () != view_listing)
        return;

    if (!strcmp (selection (opanel)->fname, ".."))
        return;
    if (cpanel->cwd [strlen (cpanel->cwd) - 1] != PATH_SEP)
        p = copy_strings (cpanel->cwd, PATH_SEP_STR, selection (cpanel)->fname, NULL);
    else
        p = copy_strings (cpanel->cwd, selection (cpanel)->fname, NULL);
    if (opanel->cwd [strlen (opanel->cwd) - 1] != PATH_SEP)
        r = copy_strings (opanel->cwd, PATH_SEP_STR, selection (cpanel)->fname, NULL);
    else
        r = copy_strings (opanel->cwd, selection (cpanel)->fname, NULL);
    q = copy_strings (" Link symbolically ", name_trunc (p, 32), " to:", NULL);
    dest = input_expand_dialog (" Relative symlink ", q, r);
    if (dest) {
	if (*dest) {
	    t = strrchr (dest, PATH_SEP);
	    if (t) {
		t[1] = 0;
		s = diff_two_paths (dest, p);
		t[1] = PATH_SEP;
		if (s) {
		    save_cwds_stat ();
		    if (-1 == mc_symlink (dest, s))
		        message (1, " Error ", " relative symlink: %s ",
				 unix_error_string (errno));
		    update_panels (UP_OPTIMIZE, UP_KEEPSEL, UP_KEEPSEL);
		    repaint_screen ();
		    free (s);
		}
	    }
	}
	free (dest);
    }
    free (q);
    free (p);
    free (r);
}
#endif

void help_cmd (void)
{
    interactive_display (LIBDIR "mc.hlp", "[main]");
}

void view_panel_cmd (void)
{
    view_cmd (cpanel);
}

void edit_panel_cmd (void)
{
    edit_cmd (cpanel);
}

void mkdir_panel_cmd (void)
{
    mkdir_cmd (cpanel);
}

/* Returns a random hint */
char *get_random_hint (void)
{
    char *data, *result, *eol;
    int  len;
    int start;
    
    /* Do not change hints more often than one minute */

#ifdef SCO_FLAVOR
    static time_t last;
    time_t now;

    time (&now);
    if ((now - last) < 60)
	return strdup ("");
    last = now;
#else
    static last_sec;
    static struct timeval tv;
    
    gettimeofday (&tv, NULL);
    if (!(tv.tv_sec> last_sec+60))
	return strdup (""); 
    last_sec = tv.tv_sec;
#endif

    data = load_file (LIBDIR "mc.hint");
    if (!data)
	return 0;

#ifdef SCO_FLAVOR
    srand ((short) now);
#else
    srand (tv.tv_sec);
#endif
    /* get a random entry */
    len = strlen (data);
    start = rand () % len;
    
    for (;start; start--){
	if (data [start] == '\n'){
	    start++;
	    break;
	}
    }
    eol = strchr (&data [start], '\n');
    if (eol)
	*eol = 0;
    result = strdup (&data [start]);
    free (data);
    return result;
}

#ifndef USE_VFS
#ifdef USE_NETCODE
#undef USE_NETCODE
#endif
#endif

#ifdef USE_NETCODE

static char *machine_str = " Enter machine name (F1 for details): ";

static void nice_cd (char *text, char *xtext, char *help, char *prefix, int to_home)
{
    char *machine;
    char *cd_path;

    if (!SELECTED_IS_PANEL)
	return;

    machine = input_dialog_help (text,
				 xtext,
				 help, "");
    if (!machine)
	return;

    cd_path = copy_strings (prefix, machine, to_home ? "/~/" : NULL, NULL);
    do_panel_cd (MENU_PANEL, cd_path);
    free (cd_path);
    free (machine);
}

void netlink_cmd (void)
{
    nice_cd (" Link to a remote machine ", machine_str,
	     "[Network File System]", "mc:", 1);
}

void ftplink_cmd (void)
{
    nice_cd (" FTP to machine ", machine_str,
	     "[FTP File System]", "ftp://", 1);
}

#ifdef HAVE_SETSOCKOPT
void source_routing (void)
{
    char *source;
    struct hostent *hp;
    
    source = input_dialog (" Socket source routing setup ",
			   " Enter host name to use as a source routing hop: ",
			   "");
    if (!source)
	return;

    hp = gethostbyname (source);
    if (!hp){
	message (1, " Host name ", " Error while looking up IP address ");
	return;
    }
    source_route = *((int *)hp->h_addr);
}
#endif /* HAVE_SETSOCKOPT */
#endif /* USE_NETCODE */

#ifdef USE_EXT2FSLIB
void undelete_cmd (void)
{
    nice_cd (" Undelete files on an ext2 file system ",
	     " Enter the file system name where you want to run the\n "
	     " undelete file system on: (F1 for details)",
	     "[Undelete File System]", "undel:", 0);
}
#endif

void quick_cd_cmd (void)
{
    char *p = cd_dialog ();

    if (p && *p) {
        char *q = copy_strings ("cd ", p, NULL);
        
        do_cd_command (q);
        free (q);
    }
    if (p)
        free (p);
}

#ifdef HAVE_DUSUM
void dirsizes_cmd (void)
{
    WPanel *panel = cpanel;
    int i, j = 0;
    char *cmd, *p, *q, *r;
    FILE *f;
#ifdef DUSUM_USEB
#   define dirsizes_command "du -s -b "
#else
#   define dirsizes_command "du -s "
#endif
#ifndef DUSUM_FACTOR
#    define DUSUM_FACTOR 512
#endif

    if (!vfs_current_is_local ())
        return;
    for (i = 0; i < panel->count; i++)
        if (S_ISDIR (panel->dir.list [i].buf.st_mode))
            j += strlen (panel->dir.list [i].fname) + 1;
    if (!j)
        return;
    cmd = xmalloc (strlen (dirsizes_command) + j + 1, "dirsizes_cmd");
    strcpy (cmd, dirsizes_command);
    p = strchr (cmd, 0);
    for (i = 0; i < panel->count; i++)
        if (S_ISDIR (panel->dir.list [i].buf.st_mode) && 
            strcmp (panel->dir.list [i].fname, "..")) {
            strcpy (p, panel->dir.list [i].fname);
            p = strchr (p, 0);
            *(p++) = ' ';
        }
    *(--p) = 0;
    open_error_pipe ();
    f = popen (cmd, "r");
    free (cmd);
    if (f != NULL) {
        /* Assume that du will display the directories in the order 
         * I've passed to it :( 
         */
        i = 0;
        p = xmalloc (1024, "dirsizes_cmd");
        while (fgets (p, 1024, f)) {
            j = atoi (p) * DUSUM_FACTOR;
            for (q = p; *q && *q != ' ' && *q != '\t'; q++);
            while (*q == ' ' || *q == '\t')
                q++;
            r = strchr (q, '\n');
            if (r == NULL)
                r = strchr (q, 0);
    	    for (; i < panel->count; i++)
                if (S_ISDIR (panel->dir.list [i].buf.st_mode))
                    if (!strncmp (q, panel->dir.list [i].fname,
                        r - q)) {
                        if (panel->dir.list [i].f.marked)
                            panel->total += j - 
			    ((panel->has_dir_sizes) ? panel->dir.list [i].buf.st_size : 0); 
                        panel->dir.list [i].buf.st_size = j;
                        break;
                    }
            if (i == panel->count)
                break;
        }
        if (pclose (f) < 0)
	    message (2, "Show directory sizes", "Pipe close failed");
	else
	    panel->has_dir_sizes = 1;
	close_error_pipe (0, 0);
	paint_panel (panel);
    } else
        close_error_pipe (1, "Cannon invoke du command.");
}
#endif
