#import "MiniSQLAdaptorChannel.h"
#import "MiniSQLAdaptorContext.h"
#import "MiniSQLAdaptor.h"

#import <foundation/NSArray.h>
#import <foundation/NSString.h>
#import <foundation/NSDictionary.h>
#import <foundation/NSUtilities.h>

#import <eoaccess/eoaccess.h>

#import <msql.h>

extern void
msqlFreeResult(m_result *result);

extern void
msqlFieldSeek(m_result *result, int pos);

#ifdef EOF10
@interface MiniSQLAdaptorChannel (EOF10Compatability)
- dictionaryWithObjects:(id *)o forAttributes:(NSArray *)a zone:(NSZone *)z;
@end

@implementation MiniSQLAdaptorChannel (EOF10Compatability)
- dictionaryWithObjects:(id *)o forAttributes:(NSArray *)a zone:(NSZone *)z
{
    NSMutableDictionary *d;
    int                  i, count;

    count = [a count];

    d = [[NSMutableDictionary allocWithZone:z] initWithCapacity:count];

    for (i = 0; i < count; i++) {
	[d setObject:o[i] forKey:[[a objectAtIndex:i] name]];
    }

    return[d autorelease];
}
@end
#endif

@interface MiniSQLAdaptorChannel (Private)
- (void) beginFetch;
- (void) endFetch;
@end

@implementation MiniSQLAdaptorChannel (Private)
- (void)beginFetch
{
    if (!isFetchInProgress) {
    	isFetchInProgress = YES;
    }
}

- (void)endFetch
{
    if (isFetchInProgress) {
    	isFetchInProgress = NO;
	
	[self setResultDescription:nil];

	if (result) {
	    msqlFreeResult(result);
	    result = NULL;
	}
    }
}
@end


@implementation MiniSQLAdaptorChannel

- initWithAdaptor:(MiniSQLAdaptor *)anAdaptor
    andContext:(MiniSQLAdaptorContext *)anAdaptorContext
{
    [super init];

    sock    = -1;
    adaptor = [anAdaptor retain];
    context = [anAdaptorContext retain];

    return self;
}


- (void)dealloc
{
    [self closeChannel];
    
    [adaptor release];
    [context release];
    
    [super   dealloc];
}


- (EOAdaptorContext *)adaptorContext
// Returns the EOAdaptorContext that controls transactions for the
// channel.
{
    return context;
}

- (BOOL)openChannel
// This method puts the channel and both its context and adaptor into a
// state where they are ready to perform database operations.  Returns YES
// on success, NO on failure for any reason.
{
    if ([self connectWithConnectionDictionary:[adaptor connectionDictionary]]){
	[adaptor miniSQLChannelDidConnect];
	return (isOpen = YES);
    } else {
	return (isOpen = NO);
    }
}

- (void)closeChannel
{
    if (sock >= 0) {
	if ([self isFetchInProgress]) {
	    [self cancelFetch];
	}
	
	[adaptor miniSQLChannelDidDisconnect];

	msqlClose(sock);
	sock = -1;
	
	isOpen = NO;
    }
}

- (BOOL)isOpen
{
    return isOpen;
}

- (BOOL)connectWithConnectionDictionary:(NSDictionary *)aDictionary
{
    NSString           *hostname;
    NSString           *database;

    if ((hostname = [aDictionary objectForKey:HOSTNAME]) &&
	(database = [aDictionary objectForKey:DATABASE])) {
	
	if ([database length] == 0) {
	    [adaptor reportError:@"Can not connect to Mini SQL without a database name."];
	    return NO;
	}
	
	if ([hostname length] == 0) {
	    sock = msqlConnect(NULL);
	} else {
	    sock = msqlConnect([hostname cString]);
	}

	if (sock < 0) {
	    NSString *error = [NSString stringWithFormat:@"Could not connect to Mini SQL server on host %@",
	    			([hostname length]) ? hostname : @"localhost"];
	    [adaptor reportError:error];
	    return NO;
	}
	if (msqlSelectDB(sock, ([database cString])) < 0) {
	    NSString *error = [NSString stringWithFormat:@"Could not open Mini SQL database %@", database];
	    [adaptor reportError:error];
	    msqlClose(sock);
	    return NO;
	}
	return YES;
    } else {
    	[adaptor reportError:@"Could not decypher connection dictionary."];
	return NO;
    }
}

- (NSArray *)describeEntities
// Returns an array of default entities constructed from meta data
// returned by the database--for example, from the catalog or system
// tables.
{
    NSMutableArray     *entities = [NSMutableArray arrayWithCapacity:10];

    result = msqlListTables(sock);
    while ((cur = msqlFetchRow(result))) {
	NSString           *name = [NSString stringWithCString:cur[0]];
	EOEntity           *entity;
	
	entity = [[(EOEntity*)[EOEntity alloc] initWithName:name] autorelease];

	[entity setExternalName:name];
	[entity setReadOnly:NO];
	
	[entities addObject:entity];
    }
    msqlFreeResult(result);

    return entities;
}

- (NSArray *) describeAttributesForEntity:(EOEntity *) anEntity
{
    NSMutableArray     *attributes = [NSMutableArray arrayWithCapacity:10];
    NSMutableArray     *keyAttributes = [NSMutableArray arrayWithCapacity:10];

    result = msqlListFields(sock,[[anEntity name] cString]);
    while ((curField = msqlFetchField(result))) {
	NSString           *name = [NSString stringWithCString:curField->name];
	EOAttribute        *attribute;

	attribute = [[(EOAttribute *)[EOAttribute alloc] initWithName:name] autorelease];

	[attribute setColumnName:name];
	[attribute setReadOnly:NO];
	
	switch (curField->type) {
	case INT_TYPE:
	    [attribute setExternalType:@"INT"];
	    [attribute setValueType:[NSString stringWithCString:@encode(int)]];
	    [attribute setValueClassName:"NSNumber"];	    
	    break;
	case REAL_TYPE:
	    [attribute setExternalType:@"REAL"];
	    [attribute setValueType:[NSString stringWithCString:@encode(double)]];
	    [attribute setValueClassName:"NSNumber"];	    
	    break;
	case CHAR_TYPE:
	    [attribute setExternalType:@"CHAR"];
	    [attribute setValueType:[NSString stringWithCString:@encode(char*)]];
	    [attribute setValueClassName:"NSString"];	    
	    break;
	}

    // add the attribute to our list of primary keys, if 
    // it is flagged as such... 
	if (IS_PRI_KEY(curField->flags)) {
	    [keyAttributes addObject:attribute];
	}
    // in any case, add the attribute to our list of 
    // attributes... 
	[attributes addObject:attribute];

    // and add it to the entity as well (this isn't 
    // done for us)... 
	[anEntity addAttribute:attribute];
    }
    msqlFreeResult(result);

 // pass our array of primary keys ot the entity... 
    [anEntity setPrimaryKeyAttributes:keyAttributes];

    return attributes;
}


- (NSArray *)describeRelationshipsForEntity:(EOEntity *)entity
    // Returns an array of default attributes or relationships for entity,
    // constructed from meta-data in the database server.  These methods don't
    // actually assign the attributes or relationships to the entity--you have
    // to do that yourself.  See EOEntity.h for descriptions of -addAttribute:
    // and -addRelationship:.  These methods are obsolete.
{
    return nil;
}


- (NSArray *)describeTableNames
    // Reads and returns an array of table names from the database.  This
    // method in conjunction with describeModelWithTableNames: is used for
    // building a default model in EOModeler.
{
    NSMutableArray     *names = [NSMutableArray arrayWithCapacity:10];

    result = msqlListTables(sock);
    while ((cur = msqlFetchRow(result))) {
	[names addObject:[NSString stringWithCString:cur[0]]];
    }
    msqlFreeResult(result);

    return names;
}


- (EOModel *)describeModelWithTableNames:(NSArray *)tableNames
    // Constructs a default model out of the database's meta data.  It also
    // put the adaptor name and connection dictionary in the new model.  This
    // method obsoletes describeEntities, describeAttributes..., and
    // describeRelationships...
{
    EOModel            *model;

    NSString           *name;
    NSEnumerator       *nameEnumerator = [tableNames objectEnumerator];

    model = [EOModel alloc];
    model = [model initWithName:[[adaptor connectionDictionary] objectForKey:DATABASE]];
    
    [model setAdaptorName:[(NSObject *)[adaptor class] description]];
    [model setConnectionDictionary:[adaptor connectionDictionary]];

    while ((name = [nameEnumerator nextObject]) != nil) {
	EOEntity           *entity;
	EOAttribute        *attribute;
	NSEnumerator       *attributeEnumerator;

	entity = [[(EOEntity *)[EOEntity alloc] initWithName:name] autorelease];

	[entity setExternalName:name];
	[entity setReadOnly:NO];

	attributeEnumerator = [[self describeAttributesForEntity:entity] objectEnumerator];

	while ((attribute = [attributeEnumerator nextObject]) != nil) {
	    [entity addAttribute:attribute];
	}
	[model addEntity:entity];
    }

    return model;
}


- (BOOL)insertRow:(NSDictionary *)row forEntity:(EOEntity *)entity
// Inserts the attributes of row into the database.  row is an
// NSDictionary whose keys are attribute names and whose values are the
// values that will be inserted.  Returns YES on success, NO on failure
// for any reason.
{
    NSString           *query;
    BOOL                didSucceed;

    id			insertExpression;

    if (_delegateRespondsTo.willInsertRow) {
	EODelegateResponse  response;

	row = [[[NSMutableDictionary allocWithZone:[row zone]]
		initWithDictionary:row]
		autorelease];
	response = [_delegate adaptorChannel:self 
		willInsertRow:(NSMutableDictionary *)row
		forEntity:entity];

	if (response == EODelegateOverrides) {
	    return YES;
	}
	if (response != EODelegateApproves) {
	    return NO;
	}
    }
  
    if (!row || !entity) {
	return NO;
    }
   
    [self setResultDescription:nil];

    insertExpression = [[adaptor expressionClass]
	    insertExpressionForRow:row
	    entity:entity
	    channel:self];
	
    query = [insertExpression
	    expressionValueForContext:insertExpression];
    
    didSucceed = [self evaluateExpression:query];

    if (didSucceed) {
    	if (_delegateRespondsTo.didInsertRow) {
	    [_delegate adaptorChannel:self
		didInsertRow:row
		forEntity:entity];
	}
    }

    return didSucceed;
}



- (BOOL)updateRow:(NSDictionary *)row
    describedByQualifier:(EOQualifier *)qualifier
// Updates the row described by qualifier so that its values are equal to
// those in row.  row is an NSDictionary of attribute name/value pairs.
// Returns YES on success, NO on failure for any reason.
{
    NSString           *query;
    BOOL                didSucceed;

    id			updateExpression;


    if (_delegateRespondsTo.willUpdateRow) {
	EODelegateResponse  response;

	row = [[[NSMutableDictionary allocWithZone:[row zone]]
	    initWithDictionary:row]
	    autorelease];

	response = [_delegate adaptorChannel:self
	    willUpdateRow:(NSMutableDictionary *)row
	    describedByQualifier:qualifier];

	if (response == EODelegateOverrides) {
	    return YES;
	}
	if (response != EODelegateApproves) {
	    return NO;
	}
    }

 // FIXME: should we report an error here? 
    if (!row || !qualifier) {
	return NO;
    }

 // cache a copy of the attributes we are to select...    
    [self setResultDescription:nil];

    updateExpression = [[adaptor expressionClass] 
	updateExpressionForRow:row
	qualifier:qualifier
	channel:self];

    query = [updateExpression
	expressionValueForContext:updateExpression];

    didSucceed = [self evaluateExpression:query];

    if (didSucceed && _delegateRespondsTo.didUpdateRow) {
	[_delegate
	    adaptorChannel:self
	    didUpdateRow:row
	    describedByQualifier:qualifier];
    }
    return YES;
}


- (BOOL)deleteRowsDescribedByQualifier:(EOQualifier *)qualifier
// Deletes the row described by the qualifier.  Returns YES on success,
// NO on failure for any reason.
{
    NSString           *query;
    BOOL                didSucceed;

    id			deleteExpression;

    if (_delegateRespondsTo.willDeleteRows) {
    	EODelegateResponse response;
	
	response = [_delegate
	    adaptorChannel:self
	    willDeleteRowsDescribedByQualifier:qualifier];
	if (response == EODelegateOverrides) {
	    return YES;
	}
	if (response != EODelegateApproves) {
	    return NO;
	}
    }

 // FIXME: should we report an error here?
    if (!qualifier) {
    	return NO;
    }

 // cache a copy of the attributes we are to select...    
    [self setResultDescription:nil];
    
    deleteExpression = [[adaptor expressionClass] 
	deleteExpressionWithQualifier:qualifier
	channel:self];

    query = [deleteExpression
    	expressionValueForContext:deleteExpression];

    didSucceed = [self evaluateExpression:query];
	
    if (didSucceed && _delegateRespondsTo.didDeleteRows) {
	[_delegate
	    adaptorChannel:self
	    didDeleteRowsDescribedByQualifier:qualifier];
    }

    return didSucceed;
}


- (BOOL) selectAttributes:(NSArray *)attributes
    describedByQualifier:(EOQualifier *)qualifier
    fetchOrder:(NSArray *)fetchOrder
    lock:(BOOL)flag
// Selects the given attributes in rows matching the qualifier.  The
// selected rows compose one or more result sets, each row of which will
// be returned by subsequent -fetchAttributes:withZone: messages according
// to the given fetchOrder (see EOAttributeOrdering.h).  If flag is YES,
// the rows are locked if possible so that no other user can modify them.
// Returns YES on success, NO on failure for any reason.
{
    NSString           *query;
    BOOL                didSucceed;

    if (_delegateRespondsTo.willSelectAttributes) {
	EODelegateResponse  response;
	NSMutableArray *a = [NSMutableArray arrayWithCapacity:[attributes count]];
	[a addObjectsFromArray:attributes];
	
	attributes = [[attributes mutableCopy] autorelease];
	fetchOrder = [[fetchOrder mutableCopy] autorelease];

	response   = 
	    [_delegate adaptorChannel:self 
		willSelectAttributes:(NSMutableArray *)a 
		describedByQualifier:qualifier 
		fetchOrder:(NSMutableArray *)fetchOrder
		lock:flag];

	if (response == EODelegateOverrides) {
	    return YES;
	}
	if (response != EODelegateApproves) {
	    return NO;
	}
    }
  
    [self setResultDescription:attributes];
   
    query = [[qualifier entity] externalQuery];

    if (!query || (qualifier && ![qualifier isEmpty])) {
        id selectExpression =
	    [[adaptor expressionClass] 
		selectExpressionForAttributes:attributes
		lock:flag 
		qualifier:qualifier
		fetchOrder:fetchOrder
		channel:self];
		
	query = [selectExpression expressionValueForContext:selectExpression];
    }

    didSucceed = [self evaluateExpression:query];

    if (didSucceed) {
	if (_delegateRespondsTo.didSelectAttributes) {
	    [_delegate adaptorChannel:self
		didSelectAttributes:attributes
		describedByQualifier:qualifier
		fetchOrder:fetchOrder
		lock:flag];
	}
    }

    return didSucceed;
}


- setResultDescription:(NSArray *) attributes
{
    [selectedAttributes autorelease];
    selectedAttributes = [attributes retain];

    return self;
}


- (NSArray *)describeResults
// Returns an array of attributes describing the properties available in
// the current result set, as determined by -selectAttributes:...  or a
// select statement evaluated by -evaluateExpression:.
{
    return selectedAttributes;
}


- (BOOL)evaluateExpression:(NSString *)expression
// Sends expression to the database server for evaluation.  Returns YES
// if no error occurs, NO if any error occurs.
{
    EODelegateResponse response;

    if (_delegateRespondsTo.willEvaluateExpression) {
	response = [_delegate adaptorChannel:self willEvaluateExpression:(NSMutableString *) expression];
	if (response == EODelegateOverrides) {
	    return YES;
	}
	if (response != EODelegateApproves) {
	    return NO;
	}
    }

    if (expression && [expression length]) {
	if (result) {
	    msqlFreeResult(result);
	    result = NULL;
	}
	if (msqlQuery(sock, [expression cString]) < 0) {
	    NSString *error = [NSString stringWithFormat:
		@"Adaptor channel could not evaluate expression \"%@\".",
		expression];	    
	    [adaptor reportError:error];
	    return NO;
	} else {
	    if ([self isDebugEnabled]) {
		NSLog(@"Adaptor channel evaluating expression \"%@\"",
		      expression);
	    }
	    if (_delegateRespondsTo.didEvaluateExpression) {
		[_delegate adaptorChannel:self 
		    didEvaluateExpression:expression];
	    }

	    result = msqlStoreResult();
	    if (result && result->numRows > 0) {
	    	[self beginFetch];
	    }
	    return YES;
	}
    } else {
	return NO;
    }
}


- (NSMutableDictionary *)fetchAttributes:(NSArray *)attributes 
    withZone:(NSZone *)zone
    // Fetches the next row from the result set of the last
    // -selectAttributes:...  message and returns values for the attribute
    // names in attributes.  When there are no more rows in the current result
    // set, this method returns nil, and invokes the delegate method
    // -adaptorChannelDidChangeResultSet: if there are more results sets.
    // When there are no more rows or result sets, this method returns NO,
    // ends the fetch, and invokes -adaptorDidFinishFetching:.
    // -isFetchInProgress returns YES until the fetch is cancelled or until
    // this method exhausts all result sets and returns nil.
{
    NSZone *fetchZone;
    attributes = [[attributes mutableCopy] autorelease];

    [self beginFetch];
	
 // if the fetch succeeds, then return YES, otherwise return NO.

    if (_delegateRespondsTo.willFetchAttributes) {
	NSMutableDictionary *row;

	row = [_delegate adaptorChannel:self 
	    willFetchAttributes:(NSMutableArray *)attributes
	    withZone:zone];

	if (row) {
	    return row;
	}
    }

    if (result &&
        (cur = msqlFetchRow(result))) {
	NSMutableDictionary *row;

	int                 i,
			    nelem = msqlNumFields(result);		    

	id		   *objects;
	
	
    // When the zone argument is NULL, use the default NSZone for the
    // application.  This patch is to make the MSQL Adaptor behave
    // similarly to the Sybase, QuickBase and Oracle adaptors.
    
	if (zone != NULL) {
	    fetchZone = zone;
	} else {
	    fetchZone = NSDefaultMallocZone();
	}

	objects = NSZoneMalloc(fetchZone, sizeof(id) * nelem);
	
	for (i = 0; i < nelem; i++) {
	    if (cur[i] != NULL) {
		objects[i] = [adaptor valueForCString:cur[i]
			      attribute:[attributes objectAtIndex:i]
			      zone:fetchZone];
	    } else {
		objects[i] = [EONull null];
	    }
	}

	row = [self dictionaryWithObjects:objects
	       forAttributes:attributes
	       zone:zone];
	
    	if (_delegateRespondsTo.didFetchAttributes) {
	    [_delegate adaptorChannel:self
		didFetchAttributes:row
		withZone:zone];
	}
	
	NSZoneFree(fetchZone, objects);
		
	msqlFieldSeek(result, 0);
	return row;
    } else {
	[self endFetch];

	if (_delegateRespondsTo.didFetchAttributes) {
	    [_delegate adaptorChannelDidChangeResultSet:self];
	}
	
	return (NSMutableDictionary *)nil;
    }
}


- (void)cancelFetch
    // Clears the result set created by the last selectAttributes:...
    // message, and terminates the current fetch, so that
    // -isFetchInProgress returns NO.
{
    [self endFetch];
}


- (BOOL)isFetchInProgress
{
    return isFetchInProgress;
}

@end
