// XXX TODO: allow 4.x defaults.

#import "WindowDepth.h"
#import "MyNXBrowserCell.h"
#import "common.h"

#define MAX_LINE_LEN 2048	// Maximum length of one preferences entry

#define WINDOWDEPTHLIMIT_NX	"NXWindowDepthLimit"
#define WINDOWDEPTHLIMIT_OS	"NSWindowDepthLimit"

#define GLOBAL_NX		"GLOBAL"
#define GLOBAL_OS		"NSGLobalDomain"

/* Used to store temporary information */
typedef struct {
    NXAtom owner;		// The name of the application
    int value;			// The WindowDepthLimit
    BOOL test;			// YES if using Test prefix
    BOOL nextstep;		// YES if NEXTSTEP default
    BOOL openstep;		// YES if OPENSTEP default (can be both).
} appInfo;

/* Names used in defaults database (non-test mode) */
static char *defaultnames[] = {
    "TwoBitGray",
    "EightBitGray",
    "TwelveBitRGB",
    "TwentyFourBitRGB"
};

/* Names used in defaults database (test mode) */
static char *defaulttestnames[] = {
    "TestTwoBitGray",
    "TestEightBitGray",
    "TestTwelveBitRGB",
    "TestTwentyFourBitRGB"
};

@interface WindowDepth(Private)
- setBrowserCell:(appInfo *)set withMatrix:matrix atRow:(int)row;
@end

@implementation WindowDepth(Private)

/*=========================================================================
 * NAME        : setBrowserCell:withMatrix:
 * DESCRIPTION : Insert a new cell in the browser.
 *=========================================================================*/
- setBrowserCell:(appInfo *)set withMatrix:matrix atRow:(int)row
{
    id cell;
    
    [matrix addRow];
    cell = [matrix cellAt:row :0];	
    [cell setStringValue:set->owner];
    [cell setTag:row];
    [cell setLoaded:YES];
    [cell setLeaf:YES];
    [cell setOwner:set->owner];
    [cell setDepth:set->value];
    [cell setTestMode:set->test];
    [cell setNextstep:set->nextstep];
    [cell setOpenstep:set->openstep];
    return self;
}

@end

@implementation WindowDepth

/*=========================================================================
 * NAME        : + new
 * DESCRIPTION : Called first time this section's button is clicked.
 *=========================================================================*/
+ new
{
    self = [super new];
    
    if (![NXApp loadNibForLayout: "WindowDepth" owner: self])
	return nil;
	
    // 'window' is an interface builder outlet.
    view = [window contentView];	// 'view' MUST be initialized
    [view removeFromSuperview];
    [window setContentView: 0];		// Don't need the window for anything
    [window free];			// So free it.
    
    // When multiple items (or none) are selected, no radio button is set
    [appRadio setEmptySelectionEnabled:YES];
    [appRadio clearSelectedCell];

    // Browser uses a custom Cell
    [browser setCellClass:[MyNXBrowserCell class]];
    
    return self;
}

/*=========================================================================
 * NAME        : willSelect:
 * DESCRIPTION : Before the view is added via addSubview.
 *=========================================================================*/
- willSelect: sender
{
    return self;
}

/*=========================================================================
 * NAME        : didSelect:
 * DESCRIPTION : After the view is added via addSubview.
 *=========================================================================*/
- didSelect: sender
{
    // Enable relevant menu items
    [NXApp enableEdit:COPY_ITEM];
    [NXApp enableWindow:CLOSE_ITEM];
    
    // Rebuild the list of apps and preferences
    [self rescanPrefs:self];
    
    return self;
}


/*=========================================================================
 * NAME        : willUnselect:
 * DESCRIPTION : Before removeFromSuperview
 *=========================================================================*/
- willUnselect: sender
{
    // Make sure that we aren't editing any text fields.
    [[NXApp appWindow] endEditingFor:self];  
    return self;
}

/*=========================================================================
 * NAME        : didUnselect:
 * DESCRIPTION : Before removeFromSuperview
 *=========================================================================*/
- didUnselect: sender
{
    // Hide our info panel
    [infoPanel orderOut:self];
    
    // Disable all Edit and Window menu items
    [NXApp enableEdit: NO];
    [NXApp enableWindow: NO];
    return self;
}

/*=========================================================================
 * NAME        : didHide:
 * DESCRIPTION : Application was just hidden.
 *=========================================================================*/
- didHide: sender
{
    return self;
}

/*=========================================================================
 * NAME        : didUnhide:
 * DESCRIPTION : Application was just unhidden.
 *=========================================================================*/
- didUnhide: sender
{
    return self;
}

/*=========================================================================
 * NAME        : rescanPrefs:
 * DESCRIPTION : Rescan the user's preferences.
 *=========================================================================*/
- rescanPrefs:sender
{
    // Cause the browser to reload
    [browser loadColumnZero];
    
    return self;
}

/*=========================================================================
 * NAME        : itemSelected:
 * DESCRIPTION : Target of NXBrowser.
 *=========================================================================*/
- itemSelected:sender
{
    id matrix;
    List *aList;
    int i, count;

    // Get a list of all the selected cells in the browser
    matrix = [browser matrixInColumn:0];
    aList = [[List alloc] init];
    [matrix getSelectedCells:aList];
    count = [aList count];

    // Set the radio button and switch to match the selection
    if (count == 0) {
        [appRadio clearSelectedCell];
	[appRadio display];
	[appSwitch setState:0];
        [appRadio setEnabled:NO];
	[appSwitch setEnabled:NO];
    } else {
        id cell = [aList objectAt:0];
	int depth = [cell depth];
	BOOL testMode = [cell testMode];

        [appRadio selectCellWithTag:depth];
	[appSwitch setState:testMode];
	for (i = 1;  i < count;  i++) {
	    cell = [aList objectAt:i];
	    if ([cell depth] != depth || [cell testMode] != testMode) {
		[appRadio clearSelectedCell];
		[appRadio display];
		[appSwitch setState:0];
		break;
	    }
	}
	[appRadio setEnabled:YES];
	[appSwitch setEnabled:YES];
    }
    [aList free];
    return self;
}

/*=========================================================================
 * NAME        : browser:fillMatrix:inColumn:
 * DESCRIPTION : Delegate message from browser requesting a reload.
 *=========================================================================*/
- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    char command[MAXPATHLEN+1+256];
    char line[MAX_LINE_LEN+1];
    appInfo set;
    FILE *fp;
    char *kind, *owner, *variable, *value;
    NXAtom newOwner, prevOwner;
    NXAtom globalNX = NXUniqueString(GLOBAL_NX);
    NXAtom globalOS = NXUniqueString(GLOBAL_OS);
    int i;
    int globalSetting = USE_DEFAULT;
    BOOL globalTestSetting = NO;
    int count;

    if (![[NXBundle bundleForClass:[self class]] getPath:command
	  forResource:"scanprefs" ofType:"sh"])
    {
        NXLogError("WindowDepth.preferences: unable to find (\"scanprefs.sh\") in bundle.");
        NXBeep();
	return 0;
    }
    strcat(command, " " WINDOWDEPTHLIMIT_NX " " WINDOWDEPTHLIMIT_OS);

    /* Call the 'scanprefs.sh' command as a subprocess
       This returns a uniqued list of the form:
	<kind> <owner>
       or
        <kind> <owner> <key> <value>
       where <kind> is either `N' for NEXTSTEP, or `O' for OPENSTEP.
     */
    fp = popen(command, "r");
    if (!fp) {
        NXLogError("WindowDepth.preferences: unable to popen(\"%s\")", command);
        NXBeep();
	return 0;
    }
    
    // Get ready to read, erase previous information
    prevOwner = NULL;
    set.owner = NULL;
    count = 0;
    
    // Process the subprocess output, line by line
    /* XXX display will be inaccurate if NEXTSTEP and OPENSTEP versions
       have different defaults for the same owner.  In that case it
       uses the last value encountered (which happens to be OPENSTEP
       due to the way the list is sorted by scanprefs.sh. */
    while (fgets(line, MAX_LINE_LEN, fp)) {
        
	// Isolate the first two fields
	if (!(kind = strtok(line, " \"\n\t"))) {
	    continue;
	}
        if (!(owner = strtok(NULL, " \"\n\t"))) {
	    continue;
	}
	
	// New owner found?
	newOwner = NXUniqueString(owner);
	if (newOwner != prevOwner) {
	    
	    // Add the previous owner to browser, unless it's GLOBAL
	    if (set.owner) {
	        if (set.owner != globalNX && set.owner != globalOS) {
		    [self setBrowserCell:&set withMatrix:matrix atRow:count];
		    count++;
		} else if (set.value != USE_DEFAULT) {
		    globalSetting = set.value;
		    globalTestSetting = set.test;
		}
	    }
	    
	    // Set up this entry
	    set.owner = newOwner;
	    set.value = USE_DEFAULT;
	    set.test = NO;
	    set.nextstep = NO;
	    set.openstep = NO;
	    prevOwner = newOwner;
        }
	if (*kind == 'N') set.nextstep = YES;
	if (*kind == 'O') set.openstep = YES;

	// Does it have a value?
	if (!(variable = strtok(NULL, " \"\n\t"))) { 
	    set.value = USE_DEFAULT;
	    set.test = NO;
	    continue;
	}
	// (scanprefs should only return those keys we asked for.)

	// Yes, find the setting
	if (!(value = strtok(NULL, " \"\n\t"))) {
	    set.value = USE_DEFAULT;
	    set.test = NO;
	    continue;
	}

        // Is this a setting we know?
	for (i = 0; i < USE_DEFAULT; i++) {
	    if (!strcmp(value, defaultnames[i])) {
	        set.value = i;
		set.test = NO;
	    }
	}
	
	// Not found so try the Test modes
	if (i == USE_DEFAULT) {
	    for (i = 0; i < USE_DEFAULT; i++) {
	        if (!strcmp(value, defaulttestnames[i])) {
		    set.value = i;
		    set.test = YES;
		}
	    }
	}
    }

    // Write the last owner
    if (set.owner) {
	if (set.owner != globalNX && set.owner != globalOS) {
	    [self setBrowserCell:&set withMatrix:matrix atRow:count];
	    count++;
	} else if (set.value != USE_DEFAULT) {
	    globalSetting = set.value;
	    globalTestSetting = set.test;
	}
    }

    // Deallocate resources
    pclose(fp);

    // Set the radio buttons for the GLOBAL setting
    [defaultRadio selectCellWithTag:globalSetting];
    [defaultSwitch setState:globalTestSetting];
    
    // Nothing is currently selected in the browser
    [appRadio clearSelectedCell];
    [appSwitch setState:0];
    [appRadio setEnabled:NO];
    [appSwitch setEnabled:NO];
    
    return count;    
}

/*=========================================================================
 * NAME        : setDefault:
 * DESCRIPTION : Target of Default radio button matrix.
 *=========================================================================*/
- setDefault:sender
{
    char command[MAXPATHLEN+1+256];
    int offset = [[defaultRadio selectedCell] tag];
    
    if (![[NXBundle bundleForClass:[self class]] getPath:command
	  forResource:"setpref" ofType:"sh"])
    {
        NXLogError("WindowDepth.preferences: unable to find (\"setpref.sh\") in bundle.");
        NXBeep();
	return 0;
    }
    if (offset == USE_DEFAULT) {
	[defaultSwitch setState:0];
	strcat(command, " -r");
    }
    strcat(command, " -g -N " WINDOWDEPTHLIMIT_NX " -O " WINDOWDEPTHLIMIT_OS " ");
    if (offset != USE_DEFAULT) {
	if ([defaultSwitch state]) {
	    strcat(command, defaulttestnames[offset]);
	} else {
	    strcat(command, defaultnames[offset]);
	}
    }
    system(command);
    return self;
}

/*=========================================================================
 * NAME        : setApp:
 * DESCRIPTION : Target of Application radio button matrix.
 *=========================================================================*/
- setApp:sender
{
    char script[MAXPATHLEN+1];
    char command[MAXPATHLEN+1+256];
    id matrix;
    List *aList;
    int count, total;
    NXAtom owner;
    id cell;
    int offset = [[appRadio selectedCell] tag];

    if (![[NXBundle bundleForClass:[self class]] getPath:script
	  forResource:"setpref" ofType:"sh"])
    {
        NXLogError("WindowDepth.preferences: unable to find (\"setpref.sh\") in bundle.");
        NXBeep();
	return 0;
    }

    // Get a list of all the selected cells in the browser
    matrix = [browser matrixInColumn:0];
    aList = [[List alloc] init];
    [matrix getSelectedCells:aList];
    
    // Modify the defaults database to match
    total = [aList count];
    for (count = 0; count < total; count++) {
        cell = [aList objectAt:count];
	owner = [cell owner];
	strcpy(command, script);
        if (offset == USE_DEFAULT) {
	    [appSwitch setState:0];
            strcat(command, " -r");
        }
	strcat(command, " ");
	strcat(command, owner);
	if ([cell nextstep]) {
	    strcat(command, " -N " WINDOWDEPTHLIMIT_NX " ");
	}
	if ([cell openstep]) {
	    strcat(command, " -O " WINDOWDEPTHLIMIT_OS " ");
	}
	if (offset != USE_DEFAULT) {
	    if ([appSwitch state]) {
		strcat(command, defaulttestnames[offset]);
	    } else {
		strcat(command, defaultnames[offset]);
	    }
        }
        system(command);

	[cell setStringValue:owner];
	[cell setLoaded:YES];
	[cell setDepth:offset];
	[cell setTestMode:[appSwitch state]];
    }

    [aList free];
    [browser displayColumn:0];
    return self;
}

@end
