/*
 * browser.c : Device-independent browser routines
 *
 * George Ferguson, ferguson@cs.rochester.edu, 23 Apr 1993.
 * 28 Apr 1993: Remove some bogus status0("Ready") calls.
 *  8 Jul 1993: Don't need actions.h.
 */

#include <stdio.h>
#include "stringdefs.h"
#include "pfs.h"
#include "db.h"
#include "xtypes.h"
#include "appres.h"
#include "query.h"
#include "browser.h"
#include "selection.h"
#include "display.h"
#include "ftp-actions.h"
#include "alert.h"
#include "status.h"
#include "debug.h"
extern DbEntry *db;

/*
 * Functions defined here:
 */
void resetBrowser();
int paneDepth();
void selectBrowserItem(),unselectBrowserItem();
void displayEntries();
void resortBrowser();
int openBrowserAll(),openBrowserDirectories(),openBrowserFiles();
void shiftBrowserDown(), shiftBrowserUp(), shiftBrowserTop();
void setBrowserState();
int getBrowserState();

static void clearInfoForDepth(),displayInfoForDepth();
static void updateButtons(),resetParentEntries();
static void openBrowserDirectory();

/*
 * Data defined here:
 */
/*
 * These are the parents of the heads of the lists being displayed in
 * the browser panes.
 */
static DbEntry *parentEntries[MAX_DEPTH];

/*
 * This is the index of the leftmost pane's contents in the previous arrays.
 */
static int currentRootDepth;

static int browserState;

/*	-	-	-	-	-	-	-	-	*/

void
resetBrowser()
{
    clearBrowser();
    currentRootDepth = 0;
    resetSelections(0);
    resetParentEntries(0);
    setUpSensitive(0);
    setDownSensitive(0);
}

int
paneDepth(pane)
int pane;
{
    return(currentRootDepth+pane);
}

#ifdef DEBUG
/*ARGSUSED*/
static void
doPrint(dbp,list_index)
DbEntry dbp;
int list_index;
{
    printf("%d ",list_index);
}
#endif

void
selectBrowserItem(pane,item)
int pane,item;
{
    DbEntry *dbp;
    int depth;
    
    /* Adjust for shifted browser */
    depth = paneDepth(pane);
#ifdef DEBUG
    fprintf(stderr,"selecting item %d, pane=%d, depth=%d\n",item,pane,depth);
    fprintf(stderr,"  before: selections[] = ");
    if (hasSelection(depth))
	forEachSelectedItemAtDepth(depth,doPrint);
    else
	fprintf(stderr,"None");
    fprintf(stderr,"\n");
#endif
    if (isSelected(depth,item)) {
	DEBUG1("item %d already selected\n",item);
	return;
    }
    dbp = findEntryFromIndex(parentEntries[depth],item);
    DEBUG2("selected item is \"%s\"(%lx)\n",dbp->name,dbp);
    DEBUG1("clearing parents depth >= %d\n",depth+1);
    resetParentEntries(depth+1);
    DEBUG1("resetting selections for depth >= %d\n",depth+1);
    resetSelections(depth+1);
    DEBUG3("adding selection at depth %d = \"%s\"(%lx)\n",depth,dbp->name,dbp);
    addSelection(depth,dbp,item);
    DEBUG3("setting parentEntries[%d] = \"%s\"(%lx)\n",depth+1,dbp->name,dbp);
    parentEntries[depth+1] = dbp;
    DEBUG3("displaying entry \"%s\"(%lx) in pane %d\n",dbp->name,dbp,pane);
    /* Highlight the selected item */
    redrawSelectionsForPane(pane);
    /* Show selected info */
    displayInfoForDepth(depth);
    /* If we're not at the rightmost pane, then show the choices. */
    if (pane < NUM_BROWSER_PANES-1) {
	displayEntries(dbp,pane+1);
    }
#ifdef DEBUG
    fprintf(stderr,"  after: selections[] = ");
    if (hasSelection(depth))
	forEachSelectedItemAtDepth(depth,doPrint);
    else
	fprintf(stderr,"None");
    fprintf(stderr,"\n");
    fprintf(stderr,"done selecting item\n");
#endif
}

void
unselectBrowserItem(pane,item)
int pane,item;
{
     int depth,i;
    
    /* Adjust for shifted browser */
    depth = paneDepth(pane);
#ifdef DEBUG
    fprintf(stderr,"unselecting item %d, pane=%d, depth=%d\n",item,pane,depth);
    fprintf(stderr,"  before: selections[] = ");
    if (hasSelection(depth))
	forEachSelectedItemAtDepth(depth,doPrint);
    else
	fprintf(stderr,"None");
    fprintf(stderr,"\n");
#endif
    unhighlightBrowserItem(pane,item);
    /* Clear all panes below this one */
    for (i=NUM_BROWSER_PANES-1; i > pane; i--)
	clearBrowserPane(i);
    /* Clear all info including this one since de-selected */
    clearInfoForDepth(depth);
    DEBUG2("removing selection at depth %d = item %d\n",depth,item);
    removeSelection(depth,NULL,item);
    DEBUG1("clearing parents depth >= %d\n",depth+1);
    resetParentEntries(depth+1);
#ifdef DEBUG
    printf("  after: selections[] = ");
    if (hasSelection(depth))
	forEachSelectedItemAtDepth(depth,doPrint);
    else
	printf("None");
    printf("\n");
    fprintf(stderr,"done deselecting item\n");
#endif
}

static void
clearInfoForDepth(depth)
int depth;
{
    switch (depth) {
	case 0:	setHostText("");
		/* fall through */
	case 1:	setLocationText("");
		/* fall through */
	case 2:	setFileText("");
		setSizeText("");
		setModesText("");
		setDateText("");
		break;
    }
    DEBUG1("cleared info for depth %d\n",depth);
}

static void
displayInfoForDepth(depth)
int depth;
{
    SelectedItem *item;
    char buf[MAX_VPATH];
    int i;

    DEBUG1("  display info for depth %d:\n",depth);
    if ((item=getSelection(0)) != NULL) {
	setHostText(item->entry->name);
	DEBUG1("    host: \"%s\"\n",item->entry->name);
    }
    /* Special case if location was selected */
    if (depth == 1) {
	if ((item=getSelection(1)) != NULL) {
	    setLocationText(item->entry->name);
	    DEBUG1("    loc: \"%s\"\n",item->entry->name);
	}
    } else {	/* Otherwise normal case, get location and file */
	strcpy(buf,"");
	for (i=1; i < depth; i++)
	    if ((item=getSelection(i)) != NULL) {
		(void)strcat(buf,item->entry->name);
		if (i < depth-1 &&
		    buf[strlen(buf)-1] != '/')
		    strcat(buf,"/");
	    }
	setLocationText(buf);
	DEBUG1("    loc: \"%s\"\n",buf);
	if ((item=getSelection(depth)) != NULL) {
	    setFileText(item->entry->name);
#ifdef MSDOS
	    sprintf(buf,"%lu",item->entry->size);
#else
	    sprintf(buf,"%d",item->entry->size);
#endif
	    setSizeText(buf);
	    setModesText(item->entry->modes);
	    setDateText(item->entry->date);
	    DEBUG1("    file: \"%s\"\n",item->entry->name);
	}
    }
}

void
displayEntries(parent,pane)
DbEntry *parent;
int pane;
{
    DbEntry *dbp;
    SelectedItem *sel;
    int i,depth;

    depth = paneDepth(pane);
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to display entries from NULL list");
	return;
    }
    DEBUG3("displaying parent \"%s\" in pane %d (depth %d)\n",
	   (parent?parent->name:"<NIL>"),pane,depth);
    /* Clear this and all subordinate panes */
    for (i=pane; i < NUM_BROWSER_PANES; i++) {
	DEBUG1("  clearing pane %d\n",i);
	clearBrowserPane(i);
    }
    /* Set the strings to be displayed in this pane */
    for (i=0, dbp=parent->entries; dbp != NULL; dbp=dbp->next) {
	DEBUG2("  item %d: \"%s\"\n",i,dbp->name);
	setBrowserItem(pane,i++,dbp->name);
    }
    /* If there's anything to display, display it */
    if (i > 0)
	redrawBrowserPane(pane);
    /* If there's only one thing then select it */
    if (i == 1) {
	DEBUG0("  solo -> select\n");
	addSelection(depth,parent->entries,0);
    }
    /* If there's something selected (either because we just selected the
       only thing or because we're scrolling left), then redo the
       highlighting and display the correct info. If we're not at the
       rightmost pane, then recusively draw the next pane. */
    if ((sel=getSelection(depth)) != NULL) {
	redrawSelectionsForPane(pane);
	displayInfoForDepth(depth);
	if (pane < NUM_BROWSER_PANES - 1)
	    displayEntries(sel->entry,pane+1);
    } else {
	/* Otherwise there's nothing selected here */
	clearInfoForDepth(depth);
    }
    /* Save which list is being displayed in the pane */
    parentEntries[paneDepth(pane)] = parent;
    /* Get the shift buttons right */
    updateButtons();
}

static void
updateButtons()
{
    SelectedItem *sel;

    setUpSensitive(currentRootDepth > 0);
    if ((sel=getSelection(currentRootDepth+NUM_BROWSER_PANES-1)) != NULL &&
	sel->entry->entries != NULL)
	setDownSensitive(1);
    else
	setDownSensitive(0);
}

static void
resetParentEntries(first)
int first;
{
    int i;

    for (i=first; i < MAX_DEPTH; i++)
	parentEntries[i] = NULL;
}

/*	-	-	-	-	-	-	-	-	*/
/* Resort the browser based on the appResources */

void
resortBrowser()
{
    /* Reset browser to leftmost position */
    DEBUG0("resortBrowser: resetting browser\n");
    resetBrowser();
    DEBUG0("resortBrowser: sorting entries\n");
    switch(appResources.sortType) {
	case GfName:
	    sortEntriesRecursively(db,cmpEntryNames);
	    break;
	case GfDate:
	    sortEntriesRecursively(db,cmpEntryDates);
	    break;
	case GfWeight:
	    sortEntriesRecursively(db,cmpEntryWeights);
	    break;
    }
    /* Display results in browser */
    DEBUG0("resortBrowser: displaying entries\n");
    displayEntries(db,0);
    DEBUG0("resortBrowser: done\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Routines to open (ie. expand) the browser */

static int numOpened;

/*
 * Open any selected items in the browser. Return the number of items
 * that were successfully opened.
 */
int
openBrowserAll()
{
    DEBUG1("openBrowserAll: depth = %d\n",currentRootDepth);
    if (!hasSelection(NUM_BROWSER_PANES-1)) {
	alert0("You must select something to open.");
	return(0);
    }
    numOpened = 0;
    /* Do the directories */
    forEachSelectedItem(openBrowserDirectory);
    /* Do the files */
    ftpOpenSelectedItems();
    DEBUG1("done openBrowserAll, returning %d\n",numOpened);
    return(numOpened);
}

int
openBrowserDirectories()
{
    DEBUG1("openBrowserDirectories: depth = %d\n",currentRootDepth);
    if (!hasSelection(2)) {
	alert0("You must select a directory to open.");
	return(0);
    }
    numOpened = 0;
    forEachSelectedItem(openBrowserDirectory);
    status0("Ready");
    DEBUG1("done openBrowserDirectories, returning %d\n",numOpened);
    return(numOpened);
}

int
openBrowserFiles()
{
    DEBUG1("openBrowserFiles: depth = %d\n",currentRootDepth);
    if (!hasSelection(2)) {
	alert0("You must select a file to open.");
	return(0);
    }
    numOpened = 0;
    ftpOpenSelectedItems();
    DEBUG1("done openBrowserFiles, returning %d\n",numOpened);
    return(numOpened);
}

/*
 * Open a selected item. Ignore any selections that aren't directories.
 */
/*ARGSUSED*/
static void
openBrowserDirectory(dbp,list_index)
DbEntry *dbp;
int list_index;
{
    VLINK vl,links;

    DEBUG1("openBrowserDirectory: \"%s\"\n",dbp->name);
    if (dbp->type != DB_DIRECTORY)
	return;
    if (dbp->entries == NULL) {
	/* It's not been expanded already... */
	if ((vl=dbp->vlink) == NULL) {
	    alert0("ERROR: NULL vlink to openBrowserDirectory!");
	    return;
	}
	/* Disable querying */
	setBrowserState(BROWSER_DIRSEND);
	DEBUG1("getting entries for \"%s\"\n",dbp->name);
	links = stringQuery(vl->host,vl->filename);
	DEBUG0("calling handleProsperoErrors\n");
	(void)handleProsperoErrors();
	if (links != NULL)
	    numOpened += 1;
#ifdef DEBUG
	fprintf(stderr,"unprocessed results of stringQuery:\n");
	for (vl=links; vl != NULL; vl=vl->next)
	    fprintf(stderr,"  \"%s\"\n",vl->filename);
	fprintf(stderr,"calling parseStringQueryResults\n");
#endif
	/* Process the results into the database */
	switch(appResources.sortType) {
	    case GfName:
		(void)parseStringQueryResults(dbp,links,cmpEntryNames);
		break;
	    case GfDate:
		(void)parseStringQueryResults(dbp,links,cmpEntryDates);
		break;
	    case GfWeight:
		(void)parseStringQueryResults(dbp,links,cmpEntryWeights);
		break;
	}
	/* Re-enable querying */
	setBrowserState(BROWSER_READY);
    }
    DEBUG0("done openBrowserDirectory\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Routines for shifting the browser up, down, top. */

void
shiftBrowserDown()
{
    DEBUG1("doing shiftDown, depth = %d\n",currentRootDepth);
    if (currentRootDepth >= MAX_DEPTH-1) {
	alert1("Browser only goes %d levels deep!",(char *)MAX_DEPTH);
	return;
    }
    if (parentEntries[currentRootDepth+1] == NULL) {
	alert0("Nothing to display below!");
	return;
    }
    currentRootDepth += 1;
    DEBUG1("currentRootDepth is now %d\n",currentRootDepth);
    displayEntries(parentEntries[currentRootDepth],0);
    DEBUG0("done shiftDown\n");
}

void
shiftBrowserUp()
{
    DEBUG1("doing shiftUp, depth = %d\n",currentRootDepth);
    if (currentRootDepth == 0) {
	beep();
	return;
    }
    currentRootDepth -= 1;
    DEBUG1("currentRootDepth is now %d\n",currentRootDepth);
    displayEntries(parentEntries[currentRootDepth],0);
    DEBUG0("done shiftUp\n");
}

void
shiftBrowserTop()
{
    DEBUG1("doing shiftTop, depth = %d\n",currentRootDepth);
    currentRootDepth = 0;
    DEBUG1("currentRootDepth is now %d\n",currentRootDepth);
    displayEntries(db,0);
    DEBUG0("done shiftTop\n");
}

/*	-	-	-	-	-	-	-	-	*/
/* Routines for getting/setting the state of the browser */
/*
 * Non-zero state => busy
 */

void
setBrowserState(state)
int state;
{
    setQuerySensitive(state == BROWSER_READY);
    setAbortSensitive(state != BROWSER_READY);
    browserState = state;
}

int
getBrowserState()
{
    return(browserState);
}

