/* Implementation of SavePanel class
 *
 * Copyright (C)  1994  The Board of Trustees of  
 * The Leland Stanford Junior University.  All Rights Reserved.
 *
 * Authors: Jeff Kamerer and Libing Wang
 *
 * This file is part of an Objective-C class library for a window system
 *
 */

#include "SavePanel.h"

#include "Matrix.h"
#include "Motif.h"
#include "MList.h"
#include "NXBrowser.h"
#include "NXBrowserCell.h"
#include "ScrollView.h"
#include "stdmacros.h"
#include <objc/List.h>
#include "Application.h"
#include "Button.h"
#include "TextField.h"
#include <stdlib.h>
#include <ctype.h>          

#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <objc/hashtable.h>

#define LISTINCREMENT	10

/* the DIR_INDICATOR cannot be the same as the pathseparator--
	it will foul up setPath */
#define DIR_INDICATOR	"-->"
#define DIR_INDICATOR_FIRSTCHAR		'-'

/*
    isLower and sort are used to sort the files the same way that NeXT
    sorts them.  Fairly straightforward.
*/

static BOOL isLower(const char *wordOne, const char *wordTwo)
{
    int 	i = 0;
    char 	one, two;
    BOOL 	isLetterOne, isLetterTwo;

    while ((wordOne[i] != '\0') && (wordTwo[i] != '\0')) {
	one = tolower(wordOne[i]);
	isLetterOne = ((one >= 'a') && (one <= 'z'));
	two = tolower(wordTwo[i]);
	isLetterTwo = ((two >= 'a') && (two <= 'z'));
	if ((isLetterOne) && (!isLetterTwo))
		return YES;
	if ((!isLetterOne) && (isLetterTwo))
		return NO;
	if (one < two)
		return YES;
	if (one > two)
		return NO;
	i++;
    }
    if (wordOne[i] == '\0')
	return YES;
    return NO;
}

static char** sort(char **list, int length)
{
    int 	i, j, min;
    char 	*temp;

    for (i = 0; i < length; i++) {
	min = i;
	for (j = i + 1; j < length; j++) {
	    if (isLower(list[j], list[min])) {
		min = j;
	    }
	}
	if (min != i) {
	    temp = list[i];
	    list[i] = list[min];
	    list[min] = temp;
	}
    }
    return list;
}

@interface SavePanel(PrivateMethods)
- setUpPanel;
- decodeDirectoryAsSeen;
@end

@implementation SavePanel(PrivateMethods)

/*
	setUpPanel - I moved the sizing of all of the elements out of
	initContent... so that all of the elements could be easily resized
	relative to the panel when the panel is resized.  Of course I never
	implemented this... - Jeff Kamerer

	If invoked from initialization method, then component
	objects are created. - Paul Kunz
*/
- setUpPanel
{
    NXRect 	contentFrame;
    NXRect 	myFrame;

    [contentView getFrame:&contentFrame];
	
    myFrame.size.width  = contentFrame.size.width - 20;
    myFrame.size.height = contentFrame.size.height - 150;
    myFrame.origin.x = 10;
    myFrame.origin.y = 75;
    if ( browser ) {
	[browser setFrame:&myFrame];
    } else {
	browser = [[NXBrowser alloc] initFrame:&myFrame];
    }

    myFrame.size.width  = 75;
    myFrame.size.height = 35;
    myFrame.origin.x = contentFrame.size.width - 80;
    myFrame.origin.y = 5;
    if ( okButton ) {
	[okButton setFrame:&myFrame];
    } else {
	okButton = [[Button alloc] initFrame:&myFrame];
    }

    myFrame.origin.x = myFrame.origin.x - 80;
    if ( _cancelButton ) {
	[_cancelButton setFrame:&myFrame];
    } else {
	_cancelButton =  [[Button alloc] initFrame:&myFrame];
    }

    myFrame.size.width  = 30;
    myFrame.size.height = 10;
    myFrame.origin.x = 10;
    myFrame.origin.y = 45;
    if ( _formLabel ) {
	[_formLabel setFrame:&myFrame];
    } else {
	_formLabel = [[[TextField alloc] initFrame:&myFrame] _initAsLabel];
    }

    myFrame.size.width  = contentFrame.size.width - 58;
    myFrame.size.height = 21;
    myFrame.origin.x = 45;
    myFrame.origin.y = 45;
    if ( form ) {
	[form setFrame:&myFrame];
    } else {
	form = [[TextField alloc] initFrame:&myFrame];
    }

    return self;
}

/*
    The _directoryAsSeen is the directory path from the NXBrowser with
    all of the DIR_INDICATORs in it (I made it "-->").  This method removes
    the DIR_INDICATORs and puts the result in the instance variable directory.
*/
- decodeDirectoryAsSeen
{
    int 	i = 0;
    int 	indicatorLength = strlen(DIR_INDICATOR);
    char 	*pathPtr;	

    if ((!_directoryAsSeen) || (strlen(_directoryAsSeen) <= 1)) {
	return self;
    }
    pathPtr = _directoryAsSeen + 1;
    strcpy(directory, "/");

    while (pathPtr[0] != '\0') {
	for (i = 0; ((pathPtr[i] != '/') && (pathPtr[i] != '\0')); i++);
	pathPtr[i - indicatorLength] = '\0';
	strcat(directory, pathPtr);
	if (pathPtr[i] != '\0') {
	    strcat(directory, "/");
	}
	pathPtr[i - indicatorLength] = DIR_INDICATOR_FIRSTCHAR;
	pathPtr += i;
	if (pathPtr[0] == '/') {
		pathPtr++;
	}
    }
    return self;
}

@end


@implementation SavePanel

+ newContent:(const NXRect *)contentRect style:(int)aStyle
	backing:(int)bufferingType buttonMask:(int)mask defer:(BOOL)flag
{
    return [[SavePanel alloc] initContent:contentRect style:aStyle backing:bufferingType buttonMask:mask defer:flag];
}

- free
{
    const char **myFilenames;
    free(filename);
    myFilenames = filenames;
    while (myFilenames && *myFilenames) {
	free((char *)*myFilenames);
	myFilenames++;
    }
    free(filenames);
    free(directory);
    free(_directoryAsSeen);
    return [super free];
}

- init
{
    NXRect 	myFrame;

    myFrame.size.width  = 300;
    myFrame.size.height = 400;
    myFrame.origin.x = 0;
    myFrame.origin.y = 0;

    return [self initContent:&myFrame style:0 backing:0 buttonMask:0
		defer:YES];
}

- initContent:(const NXRect *)contentRect style:(int)aStyle
	backing:(int)backingType buttonMask:(int)mask defer:(BOOL)flag
{
    NXRect	contentFrame;
    NXRect	myFrame;
    FILE	*file;

    [super initContent:contentRect style:aStyle backing:backingType
	buttonMask:mask defer:flag];
    [self setUpPanel]; 
    instancename = "SavePanel";
	
    filename = malloc(MAXPATHLEN * sizeof(char));
    filenames = NULL;
    directory = malloc(MAXPATHLEN * sizeof(char));
    _directoryAsSeen = malloc(MAXPATHLEN*strlen(DIR_INDICATOR) * sizeof(char));
    filename[0] = directory[0] = _directoryAsSeen[0] = '\0';
    _beenUsed = NO;
    [super setTitle:""];

    [contentView getFrame:&contentFrame];
    myFrame.size.width  = 300;
    myFrame.size.height = 25;
    myFrame.origin.x = 10;
    myFrame.origin.y = contentFrame.size.height - myFrame.size.height -10;
    _titleText = [[TextField alloc] _initAsLabel];
    [_titleText setFrame:&myFrame];
    [_titleText setStringValue:"Save"];
    [contentView addSubview:_titleText];

    [browser setMaxVisibleColumns:2];
    [browser setDelegate:self];
    [browser setTarget:self];
    [browser setAction:@selector(highlighted:)];
    [browser setDoubleAction:@selector(clickOK:)];
    [contentView addSubview:browser];

    [okButton setTitle:"OK"];
    [okButton setTarget:self];
    [okButton setAction:@selector(ok:)];
    [contentView addSubview:okButton];

    [_cancelButton setTitle:"Cancel"];
    [_cancelButton setTarget:self];
    [_cancelButton setAction:@selector(cancel:)];
    [contentView addSubview:_cancelButton];

    [_formLabel setStringValue:"File:"];
    [contentView addSubview:_formLabel];

    [form setTarget:self];
    [form setAction:@selector(clickOK:)];
    [contentView addSubview:form];

    file = popen("csh -f -c 'echo ~'", "r");
    fscanf(file, "%s", directory);
    pclose(file);

    return self;
}

- (int)runModal
{
    int 	result;

    [self makeKeyAndOrderFront:self];
    if (!_beenUsed) {
	_beenUsed = YES;
	[self setDirectory:directory];
    }
    filetypes = NULL;
    result = [NXApp runModalFor:self];
    return result;
}

- (int)runModalForDirectory:(const char *)path file:(const char *)file_name
{
    if (path) {
	[self setDirectory:path];
    }
    if (file_name) {
	[form setStringValue:file_name];
	[form selectText:self];
    }
    _beenUsed = YES;
    return [self runModal];
}

- cancel:sender
{
    [self performClose:self];
    [NXApp stopModal:NX_CANCELTAG];
    return self;
}

- ok:sender
{
    const char 	*formString = [form stringValue];

    if (formString[0] == '\0') {
	return self;
    }
    strcpy(filename, directory);
    strcat(filename, "/");
    strcat(filename, formString);
    [self performClose:self];
    [NXApp stopModal:NX_OKTAG];
    return self;
}

- (const char *)filename
{
    return filename;
}

- (const char *)directory
{
    return directory;
}

/*
    setDirectory also has to encode the DIR_INDICATORs into
    _directoryAsSeen and set the path of the browser with that.
*/
- setDirectory:(const char *)path
{
    int 	i = 0;
    char 	*pathPtr;
    char 	temp;

    if (!path) {
	return self;
    }
    if (directory != path) {
	strcpy(directory, path);
    }
    if (!_beenUsed) {
	return self;
    }
    if (directory[0] == '/') {
	strcpy(_directoryAsSeen, "/");
	pathPtr = directory + 1;
    } else {
	_directoryAsSeen[0] = '\0';
	pathPtr = directory;
    }

    while (pathPtr[0] != '\0') {
	for (i = 0; ((pathPtr[i] != '/') && (pathPtr[i] != '\0')); i++);
	temp = pathPtr[i];
	pathPtr[i] = '\0';
	strcat(_directoryAsSeen, pathPtr);
	strcat(_directoryAsSeen, DIR_INDICATOR);
	pathPtr[i] = temp;
	if ((pathPtr[i] == '/') && (pathPtr[i + 1] != '\0')) {
	    strcat(_directoryAsSeen, "/");
	}
	while (pathPtr[i] == '/') {
	    i++;
	}
	pathPtr += i;
    }
    [browser setPath:_directoryAsSeen];
    return self;
}

- setPrompt:(const char *)prompt
{
    return self;
}

- setTitle:(const char *)aTitle
{
    [_titleText setStringValue:aTitle];
    return self;
}

- (const char *)requiredFileType
{
    return requiredType;
}

- setRequiredFileType:(const char *)type
{
    requiredType = NXCopyStringBuffer(type);
    /* do something appropriate */
    return self;
}

- setAccessoryView:aView
{
    /* not urgent for phase 1, we can work around not having it */
    return self;
}

@end

@implementation SavePanel(delegateMethods)

/* the browser passes an empty matrix for this delegate to fill in */
- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    NXBrowserCell	*aCell;
    DIR			*dirp;
    struct dirent	*entry;
    struct stat 	statbuf;
    int			i, k;
    char		file_path[MAXPATHLEN];
    char		*p, *q;
    char**		namesList;
    int			numNames;
    int			namesSpaceAvail;
    char		*tempString;
    int			specialType;

    if (column == 0) {
	strcpy(directory, "/");
    } else {
	[sender getPath:_directoryAsSeen toColumn:column-1];
	[self decodeDirectoryAsSeen];
    }
    if ((dirp = opendir(directory)) == NULL ) {
	return 0;
    }

    strcpy(file_path, directory);
    if (column != 0) strcat(file_path, "/");
    p = file_path + strlen(file_path);

    namesList = malloc(LISTINCREMENT * sizeof(char*));
    numNames = 0;
    namesSpaceAvail = 10;
    while( (entry = readdir(dirp) ) != NULL ) {
	if (strcmp(entry->d_name, ".") == 0 || 
	    strcmp(entry->d_name, "..") == 0) {
	    continue; /* skip self and parent */
	}

	tempString = malloc(sizeof(char) * (strlen(entry->d_name)
	    + strlen(DIR_INDICATOR) + 1));
	strcpy (tempString, entry->d_name);

/* Special code used by runModalForTypes */
/* Select either directories or special types only */

	if (filetypes != NULL) {
	    *p = '\0';
	    strcat(p, tempString);
	    stat(file_path, &statbuf);
	    if ((statbuf.st_mode & S_IFMT) == S_IFDIR) goto select;

	    q = strrchr(tempString, '.');
	    if (q == NULL) {
	    	continue; 	/* not what we're looking for */
	    } else {
		q++;
	    	k=0;
	    	while (filetypes[k] != NULL) {
		    if (!strcmp(q, filetypes[k])) goto select;
		    k++;
	    	}
		continue; 	/* not what we're looking for */
	    }
	}
	select:

	if (numNames == namesSpaceAvail) {
	    namesSpaceAvail += LISTINCREMENT;
	    namesList = (char**)realloc((void *)namesList, namesSpaceAvail * sizeof(char*));
	}
	namesList[numNames++] = tempString;
    }

    sort(namesList, numNames);

    i = 0;
    while (i < numNames) {
	if (strcmp(namesList[i], ".") == 0 || 
	    strcmp(namesList[i], "..") == 0) {
	    continue; /* skip self and parent */
	}

	[matrix addRow];
	aCell = [matrix cellAt:i :0];

	*p = '\0';
	strcat( p, namesList[i] );
	stat( file_path, &statbuf );

	specialType = 0;	/* Treat special type as a 'leaf' */
	if (filetypes != NULL) {
	    q = strrchr(namesList[i], '.');
	    if (q != NULL) {
		q++;
	    	k=0;
	    	while (filetypes[k] != NULL) {
		    if (!strcmp(q, filetypes[k])) specialType = 1;
		    k++;
	    	}
	    }
	}

	if ( (statbuf.st_mode & S_IFMT) == S_IFDIR  && specialType == 0) {
	    strcat(namesList[i], DIR_INDICATOR);
	    [aCell setStringValue:namesList[i]];
	    [aCell setLeaf:NO];
	} else {
	    [aCell setStringValue:namesList[i]];
	    [aCell setLeaf:YES];
	}

	free(namesList[i]);
	[aCell setLoaded:YES];
	i++;
    }

    free(namesList);
    return i;
}

/* single click action from browser */
- highlighted:sender
{
    int 	column;

    column = [sender selectedColumn];
    if ([[sender selectedCell] isLeaf]) {
	[form setStringValue:[[sender selectedCell] stringValue]];
	[form selectText:self];
	if (column > 0) {
	    [sender getPath:_directoryAsSeen toColumn:column - 1];
	    [self decodeDirectoryAsSeen];
	} else {
	    strcpy(directory, "/");
	}
    } else {
	[sender getPath:_directoryAsSeen toColumn:column];
	[self decodeDirectoryAsSeen];
    }
    return self;
}

/* double click action from browser, return key action from form */
- clickOK:sender
{
    [okButton performClick:sender];
    return self;
}
@end
