/*
StoreManager.m - Copyright (c) 1992 NeXT Computer, Inc.

You may freely copy, distribute and reuse the code in this example.
NeXT Computer, Inc. disclaims any warranty of any kind, expressed or implied,
as to its fitness for any particular use.
*/

#import <appkit/appkit.h>
#import <sys/param.h>
#import <bsd/libc.h>
#import <objc/NXBundle.h>
#import <store/IXStoreBlock.h>
#import "DataWrapper.h"
#import "StoreManager.h"

@interface StoreManager(Private)

- loadNib;
- (int)browser:sender fillMatrix:matrix inColumn:(int)column;
- browserClick:sender;
- setBrowser:anObject;
- windowWillClose:sender;
- clearText;
- appendToText:(const char *)aString;

@end

@implementation StoreManager(Private)
- loadNib
{
char buf[MAXPATHLEN+1];
NXBundle *aBundle = [NXBundle bundleForClass:[self class]];

if ([aBundle getPath:buf forResource:"StoreManager" ofType:"nib"]) {
if ([NXApp loadNibFile:buf owner:self
withNames:NO fromZone:[NXApp zone]]) {
[win makeKeyAndOrderFront:self];
[win setDelegate:self];
return self;
}
}

return nil;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
int row;
id cell;
char str[512]&C@ int cnt = [blocks count];

for (row=0; row < cnt; row++) {
int theValue;
DataWrapper *aWrapper;
unsigned int blockNum;

blockNum = (unsigned int) [[blocks objectAt:row] value];
aWrapper = (DataWrapper *)
IXReadObjectFromStore(storeFile, blockNum, [NXApp zone]);
[storeFile closeBlock:blockNum]; // not required for single context
theValue = [aWrapper value];
[aWrapper free];

cell = [matrix cellAt:row :0];
if (cell == nil) {
[matrix insertRowAt:row];
cell = [matrix cellAt:row :0];
}

sprintf(str, "%8d\t%8d\t%s",
[[blocks objectAt:row] value],
theValue,
[[blocks objectAt:row] key]);
[cell setStringValue:str];
[cell setLoaded:YES];
[cell setLeaf:YES];
}

return row;
}

- browserClick:sender
{
int row;
const char *str;
int block, value;
const char *key;

str = [[[browser matrixInColumn:0] selectedCell] stringValue];
row = [[browser matrixInColumn:0] selectedRow];
sscanf(str, "%8d\t%8d", &block, &value);
/*
* the key starts at the first char after the first two tabs
*/
key = [[blocks objectAt:row] key];

[[keyForm cellAt:0 :0] setStringValue:key];
[[keyForm cellAt:1 :0] setIntValue:value];

return self;
}

- setBrowser:anObject
{
[anObject setTarget:self];
[anObject setAction:@selector(browserClick:)];
[anObject setDoubleAction:@selector(browserClick:)];
browser = anObject;
return self;
}

- windowWillClose:sender
{
[self free];
[win setDelegate:nil];
return (id) 1;
}

- clearText
{
[text setText:""];
[text display];
return self;
}

- appendToText:(const char *)aString
{
int currentLength = [text textLength];
[text setSel:currentLength :currentLength];
[text replaceSel:aString];
[text scrollSelToVisible];

return self;
}
@end

@implementation StoreManager

- init
{
[super init];
blocks = [[List alloc] init];
return self;
}

- add:sender
{
unsigned int newBlock;
DataWrapper *aWrapper = [[DataWrapper alloc] init];

/*
* get the values from the keyForm
*/
[aWrapper setKey:[[keyForm cellAt:0 :0] stringValue]];
[aWrapper setValue:[[keyForm cellAt:1 :0] intValue]];

/*
* get a new object and store it -- also update the list object
* initial size doesn't matter; size will be adjusted &CAeeded when
* the wrapper is archived
*/
if ([storeFile createBlock:&newBlock ofSize:1]) {
IXWriteRootObjectToStore(storeFile, newBlock, aWrapper);
[storeFile closeBlock:newBlock];

/*
* we will reuse aWrapper as an auxilliary.
* also store the list of blocks.
*/
[aWrapper setValue:newBlock];
[blocks addObject:aWrapper];
IXWriteRootObjectToStore(storeFile, 1, blocks);
[win setDocEdited:YES];
[browser loadColumnZero];
} else {
NXRunAlertPanel([NXApp appName],
"Add operation failed", "OK", NULL, NULL);
return nil;
}

[keyForm selectTextAt:0 :0];
return self;
}

- delete:sender
{
unsigned int num = [[browser matrixInColumn:0] selectedRow];
DataWrapper *aWrapper = [blocks objectAt:num];

if (!aWrapper) {
NXRunAlertPanel([NXApp appName],
"You must select a row to delete", "OK", NULL, NULL);
return nil;
}

[blocks removeObjectAt:num];
[storeFile freeBlock:(unsigned int)[aWrapper value]];
IXWriteRootObjectToStore(storeFile, 1, blocks);
[aWrapper free];
[browser loadColumnZero];
return self;
}

/*
* Verify the first twenty blocks...
*/
- verify:sender
{
BOOL found;
unsigned int size;
unsigned int i, cnt;
char buffer[512];

[self clearText];
[self appendToText:"VERIFYING FIRST TWENTY ENTRIES\n"];
for (i = 0; i < 20; i++) {
NX_DURING
if (size = [storeFile sizeOfBlock:i]) {
/*
* block exists... does it exist in blocks?
*/
int count = [blocks count];
for (cnt=0, found = NO; cnt < count; cnt++) {
if ([[blocks objectAt:cnt] value] == i) {
/* block found */
found = YES;
sprintf(buffer,
"Block %u exists - found in list; size:%u\n", i, size);
[self appendToText:buffer];
break;
}
}

if (found == NO) {
DataWrapper *aWrapper;
if (i == 1) {
/* This is the boot block */
sprintf(buffer,
"Block 1 is the boot block; size:%u\n", size);
[self appendToText:buffer];
} else {
aWrapper = (DataWrapper *)
IXReadObjectFromStore(storeFile, i, [NXApp zone]);
[storeFile closeBlock:i];
sprintf(buffer,
"Block %u exists - not in list; key=\"%s\" value=%d size:%u\n",
i, [aWrapper key], [aWrapper value], size);
[self appendToText:buffer];
[aWrapper free];
}
}
}&CB_HANDLER
/*
* If this were a multi-threaded application we should check
* the exception code, to see if indeed the block does not exist.
* Since it is single-threaded, it only raises the exception if
* the block does not exist...
*/
sprintf(buffer, "Block %u does not exist *****\n", i);
[self appendToText:buffer];
NX_ENDHANDLER
}

return self;
}

- refresh:sender
{
[browser loadColumnZero];
return self;
}

- replace:sender
{
const char *str;
int block, value;
DataWrapper *aWrapper = [[DataWrapper alloc] init];

str = [[[browser matrixInColumn:0] selectedCell] stringValue];
if (!str) {
NXRunAlertPanel([NXApp appName],
"You must select a row to replace", "OK", NULL, NULL);
return nil;
}

sscanf(str, "%8d\t%8d", &block, &value);

[aWrapper setKey:[[keyForm cellAt:0 :0] stringValue]];
[aWrapper setValue:[[keyForm cellAt:1 :0] intValue]];

IXWriteRootObjectToStore(storeFile, (unsigned int)block, aWrapper);
[storeFile closeBlock:block];

[[blocks objectAt:[[browser matrixInColumn:0] selectedRow]]
setKey:[aWrapper key]];
[aWrapper free];

/*
* also write the list of blocks...
*/
IXWriteRootObjectToStore(storeFile, 1, blocks);
[browser loadColumnZero];
return self;
}

- new:sender
{
unsigned int blockHandle;

/*
* force it to create a storeFile
*/
[self saveAs:self];
if (!storeFile) {
NXRunAlertPanel([NXApp appName],
"Cannot create a new IXStoreFile", "OK", NULL, NULL);
return nil;
}

/*
* the first block that we allocate is always block 1, and we use this as
* our boot block. In this case, it is a bare bones example, and we will
* use the boot block to store the list blocks which enumerate the blocks
* we have allocated so far. The list is kept consistent with the store.
*/
if (![storeFile createBlock:&blockHandle ofSize:1]) {
/* could not get a handle */
NXRunAlertPanel([NXApp appName],
"Cannot create the boot block", "OK", NULL, NULL);
[storeFile free];
return nil;
}

/*
* At this point, we don't care to do anything else... we have opened
* a store, allocated block 1 (which we will use as our boot block),
* and we have created a List (not yet associated with the boot block).
*/

if (![self loadNib]) {
NXRunAlertPanel([NXApp appName],
"Cannot load the store file", "OK", NULL, NULL);
return nil;
}

[win setTitle:[storeFile filename]];
&C7owser loadColumnZero];
return self;
}

char *storeFileTypes[] = { "store", 0 };

- open:sender
{
id openPanel = [OpenPanel new];

if ([openPanel runModalForTypes:storeFileTypes]) {
storeFile = [[IXStoreFile alloc]
initFromFile:[openPanel filename] forWriting:YES];
if (!storeFile) {
NXRunAlertPanel([NXApp appName],
"Cannot open the IXStoreFile", "OK", NULL, NULL);
return [self free];
}
}

/*
* load the blocks list from the boot block
*/
blocks = (List *) IXReadObjectFromStore(storeFile, 1, [NXApp zone]);

[self loadNib];
[win setTitle:[storeFile filename]];
[browser loadColumnZero];
return self;
}

- saveAs:sender
{
id savePanel = [SavePanel new];

[savePanel setRequiredFileType:"store"];
if (![savePanel runModalForDirectory:"." file:"testfile.store"]) {
NXRunAlertPanel([NXApp appName],
"You must save to create a new IXStoreFile", "OK", NULL, NULL);
return nil;
}

storeFile = [[IXStoreFile alloc] initWithFile:[savePanel filename]];
return self;
}

- free
{
[blocks free];
[storeFile commitTransaction];
[storeFile free];
return [super free];
}

@end