/* 
   CompiledTest.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
   Date: 1996

   This file is part of the GNUstep Database Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSObjCRuntime.h>

#include <extensions/support.h>

#include <eoaccess/EOModel.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EORelationship.h>
#include <eoaccess/EOAttributeOrdering.h>

#include <eoaccess/EONull.h>

#include "CompiledTest.h"

@implementation CompiledTest

- (void)dealloc
{
    [model release];
    [testDictionary release];
    [mainTestName release];
    [currentTestName release];
    [testInfo release];
    
    [super dealloc];
}

- initWithTestDict:(NSDictionary*)aTestDict
  modelDict:(NSDictionary*)aModelDict 
  forTest:(NSString*)aTestName
{
    id pool;
    
    pool = [[NSAutoreleasePool alloc] init];
    model = [[EOModel alloc] initWithPropertyList:aModelDict];
    [pool release];
    [self check:(model != nil) format:@"invalid model dictionary"];
    
    [self check:
	(aTestDict != nil && [aTestDict isKindOfClass:[NSDictionary class]])
    	format:@"test info must be a dictionary"];
    
    [self check:(aTestName != nil) format:@"invalid null test name"];

    testDictionary = [aTestDict retain];
    mainTestName = [aTestName retain];
    
    flags.verboseMask = YES;
    flags.breakOnErr  = YES;
    
    return self;
}

- (void)runMainTest
{
    [self 
	runTest:[testDictionary objectForKey:mainTestName]
	name:mainTestName];
}

/* 
 * Runs a test 
 *   gets the test info dictionary
 *   invokes test init, run, cleanup methods
 */

- (void)runTest:(NSDictionary*)dict name:(NSString*)name
{
    NSString* method;
    SEL selector;
    NSString* savedName;
    NSDictionary* savedDict;
    id pool;
    NSArray* arry;
    NSEnumerator* en;
    
    savedName = currentTestName;
    currentTestName = name;
    savedDict = testInfo;
    testInfo = dict;
    [self logMessage:@"Starting Test %@.", currentTestName];

    [self check:
	(dict != nil && [dict isKindOfClass:[NSDictionary class]])
    	format:@"test info must be a dictionary"];
    
    // "inherits"
    arry = [testInfo objectForKey:@"inherits"];
    if (arry) {
	NSMutableDictionary* merge = [[NSMutableDictionary new] autorelease];

	en = [arry objectEnumerator];
	while ((method = [en nextObject]))
	    [merge addEntriesFromDictionary:
		[testDictionary objectForKey:method]];
	
	[merge addEntriesFromDictionary:testInfo];
	testInfo = merge;
    }
    
    // "initMethods"
    arry = [testInfo objectForKey:@"initMethods"];
    if (arry) {
	en = [arry objectEnumerator];
	while ((method = [en nextObject])) {
	    selector = NSSelectorFromString(method);
	    [self check:
		(selector != NULL && [self respondsToSelector:selector])
		format:@"invalid method `%@' in `initMethods'", method];
	    pool = [[NSAutoreleasePool alloc] init];
	    [self perform:selector];
	    [pool release];
	}
    }
    
    // "runMethod"
    arry = [testInfo objectForKey:@"runMethods"];
    [self check:(arry != nil)
	format:@"must have `runMethods' entry in test"];
    if (arry) {
	en = [arry objectEnumerator];
	while ((method = [en nextObject])) {
	    selector = NSSelectorFromString(method);
	    [self check:
		(selector != NULL && [self respondsToSelector:selector])
		format:@"invalid method `%@' in `runMethods'", method];
	    pool = [[NSAutoreleasePool alloc] init];
	    [self perform:selector];
	    [pool release];
	}
    }

    // "cleanupMethods"
    arry = [testInfo objectForKey:@"cleanupMethods"];
    if (arry) {
	en = [arry objectEnumerator];
	while ((method = [en nextObject])) {
	    selector = NSSelectorFromString(method);
	    [self check:
		(selector != NULL && [self respondsToSelector:selector])
		format:@"invalid method `%@' in `cleanupMethods'", method];
	    pool = [[NSAutoreleasePool alloc] init];
	    [self perform:selector];
	    [pool release];
	}
    }

    [self logMessage:@"Ending Test %@.", currentTestName];
    currentTestName = savedName;
    testInfo = savedDict;
}

/*
 * Test Method
 *   does nothing
 */

- (void)testNothing
{
    NSLog(@"Ok.");
}

/*
 * Test Method
 *   compound test - runs a list of tests
 *   gets test (array of test names) and invokes runTest for each
 */

- (void)testListOfTests
{
    NSArray* 		tests = [testInfo objectForKey:@"tests"];
    NSEnumerator* 	enumTest = [tests objectEnumerator];
    NSString*		method = [testInfo objectForKey:@"runTestMethod"];
    NSMutableDictionary*info;
    NSString* 		subName;
    
    [self check:(tests != nil && [tests isKindOfClass:[NSArray class]])
	format: @"`listOfTests' test must have a `tests' entry with the"
		@"names of tests to run in sequence"];
    
    while ((subName = [enumTest nextObject])) {
	info = [testDictionary objectForKey:subName];
	[self check:(info != nil && [info isKindOfClass:[NSDictionary class]])
	    format:@"could not find test info, key `%@', from test dictionary",
		subName];
	if (method) {
	    info = [[info mutableCopy] autorelease];
	    [info setObject:method forKey:@"runMethod"];
	}
	[self runTest:info name:subName];
    }
}

/*
 * Init/Cleanup methods
 */

- (void)enableBreakOnError		{flags.breakOnErr = YES;}
- (void)disableBreakOnError		{flags.breakOnErr = NO;}

- (void)enableMessages			{flags.verboseMask = YES;}
- (void)disableMessages			{flags.verboseMask = NO;}

/* 
 * Check and log
 */

- (void)check:(BOOL)aFlag format:(NSString*)format, ...
{
    va_list va;
    id fmt;
    
    if (aFlag)
	return;

    fmt = [@"Error: " stringByAppendingString:format];
    va_start(va, format);
    NSLogv(fmt, va);
    va_end(va);
    
    if (flags.breakOnErr) {
	NSLog(@"Aborting ...");
	abort();
    }
}

- (void)logMessage:(NSString*)format, ...
{
    va_list va;
    id fmt;

    if (!flags.verboseMask)
	return;

    va_start(va, format);
    fmt = [currentTestName stringByAppendingString:@": "];
    fmt = [fmt stringByAppendingString:format];
    NSLogv(fmt, va);
    va_end(va);
}

/*
 * Test info dictionary access
 */

- valueForAttribute:(EOAttribute*)attr from:vstr
{
    if ([vstr isKindOfClass:[NSString class]] && 
        [vstr compare:@"null"
	    options:NSCaseInsensitiveSearch] == NSOrderedSame)
	return [EONull null];
    
    return [attr convertValueToModel:vstr];
}

- (NSArray*)rowsForEntity:(EOEntity*)anEntity
  attributes:(NSArray*)attributes
  values:(NSArray*)values
{
    NSMutableArray* rows = [[NSMutableArray new] autorelease];
    NSEnumerator* en;
    id valRow;
    int i, n = [attributes count];
    
    [self check:(values != nil && [values isKindOfClass:[NSArray class]])
	format:@"list of rows must be an array of arrays/dictionaries"];
    
    en = [values objectEnumerator];
    while ((valRow = [en nextObject])) {
	NSMutableDictionary* row = [[NSMutableDictionary new] autorelease];
	
	if ([valRow isKindOfClass:[NSArray class]]) {
	    [self check:(n == [valRow count]) 
		format:@"row values count must equal number of attributes"];
	    for (i = 0; i < n; i++) {
		EOAttribute* attr = [attributes objectAtIndex:i];
		id val = [valRow objectAtIndex:i];
		id name = [attr name];
		
		val = [self valueForAttribute:attr from:val];
		if (val)
		    [row setObject:val forKey:name];
		else
		[self check:NO
		    format:@"could not convert value `%@' for attribute `%@'",
			[valRow objectAtIndex:i], name];
	    }
	}
	else if ([valRow isKindOfClass:[NSDictionary class]]) {
	    NSEnumerator* de = [valRow keyEnumerator];
	    id key;
	    
	    while ((key = [de nextObject])) {
		id val = [valRow objectForKey:key];

		if (val)
		    [row setObject:val forKey:key];
		else
		[self check:NO
		    format:@"could not convert value `%@' for attribute `%@'",
			[valRow objectForKey:key], key];
	    }
	}
	else
	    [self check:NO format:@"row description must be array/dictionary"];
	[rows addObject:row];
    }
    
    return rows;
}

- (NSArray*)attributesForEntity:(EOEntity*)entity names:(NSArray*)names
{
    NSMutableArray* attrs = [[NSMutableArray new] autorelease];
    EOAttribute* attr;
    NSString* name;
    NSEnumerator* en;
    
    [self check:(names != nil && [names isKindOfClass:[NSArray class]])
	format:@"list of attributes must be an array of strings"];

    en = [names objectEnumerator];
    while ((name = [en nextObject])) {
	attr = [entity attributeNamed:name];
	if (attr)
	    [attrs addObject:attr];
	else
	    [self check:NO format:@"invalid attribute name %@", name];
    }
    
    return attrs;
}

- (NSArray*)sortOrderingForEntity:(EOEntity*)entity order:(NSArray*)order
{
    NSMutableArray* ret = [[NSMutableArray new] autorelease];
    NSEnumerator* en = [order objectEnumerator];
    NSArray* ord;
    
    while ((ord = [en nextObject])) {
	id atrName, atrOrd, attr;
	EOOrdering eoord = EOAnyOrder;
	
	[self check:[ord isKindOfClass:[NSArray class]] && [ord count] == 2
	    format:@"attribute order must be a two element array"];
	
	atrName = [ord objectAtIndex:0];
	atrOrd = [ord objectAtIndex:1];
	attr = [entity attributeNamed:atrName];
	
	[self check:(attr != nil)
	    format:@"bad attribute name in attribute ordering"];
	
	if ([atrOrd isEqual:@"A"])
	    eoord = EOAscendingOrder;
	else if ([atrOrd isEqual:@"D"])
	    eoord = EODescendingOrder;
	else
	    [self check:NO format:@"attribute ordering must be 'A' or 'D'"];
	
	[ret addObject:[EOAttributeOrdering
	    attributeOrderingWithAttribute:attr ordering:eoord]];
    }
    
    return ret;
}

@end /* CompiledTest */

