/* 
   EOAdaptorChannel.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Date: October 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/NSDate.h>
#include <Foundation/NSValue.h>

#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include <eoaccess/common.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EOAdaptor.h>
#include <eoaccess/EOAdaptorContext.h>
#include <eoaccess/EOAdaptorChannel.h>
#include <eoaccess/EOSQLExpression.h>
#include <eoaccess/exceptions/EOFExceptions.h>

@implementation EOAdaptorChannel

+ (NSCalendarDate*)dateForAttribute:(EOAttribute*)attr 
  year:(int)year month:(unsigned)month day:(unsigned)day 
  hour:(unsigned)hour minute:(unsigned)minute second:(unsigned)second 
  zone:(NSZone*)zone
{
    NSTimeZone* serverTimeZone = [attr serverTimeZone];
    NSTimeZone* clientTimeZone = [attr clientTimeZone];
    NSCalendarDate* date;
    NSString* fmt;

    if (!serverTimeZone)
	serverTimeZone = [NSTimeZone localTimeZone];
    if (!clientTimeZone)
	clientTimeZone = [NSTimeZone localTimeZone];
    date = [[[NSCalendarDate allocWithZone:zone]
		initWithYear:year month:month day:day hour:hour
		    minute:minute second:second timeZone:serverTimeZone]
		autorelease];
    [date setTimeZone:clientTimeZone];
    fmt = [attr calendarFormat];
    [date setCalendarFormat:fmt ? fmt : [EOAttribute defaultCalendarFormat]];
    return date;
}

- initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext
{
    ASSIGN(adaptorContext, _adaptorContext);
    TRY {
	[adaptorContext channelDidInit:self];
    } END_TRY
    CATCH(TooManyOpenedChannelsException) {
	[self autorelease];
	RERAISE;
    } END_CATCH

    return self;
}

- (void)dealloc
{
    [adaptorContext channelWillDealloc:self];
    [adaptorContext release];
    [super dealloc];
}

- (BOOL)openChannel
{
    if(isOpen)
	return NO;
    isOpen = YES;

    return YES;
}

- (void)closeChannel
{
    if([self isFetchInProgress])
	[self cancelFetch];
    isOpen = NO;
}

- (BOOL)insertRow:(NSDictionary*)row forEntity:(EOEntity*)entity
{
    EOSQLExpression* sqlexpr;
    NSMutableDictionary* mrow = row;

    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    if(!row || !entity)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"row and entity arguments for insertRow:forEntity:"
			   @"must not be the nil object"]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willInsertRow) {
	EODelegateResponse response;

	mrow = [[row mutableCopyWithZone:[row zone]] autorelease];
	response = [delegate adaptorChannel:self
			     willInsertRow:mrow
			     forEntity:entity];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    sqlexpr = [[[adaptorContext adaptor] expressionClass]
		    insertExpressionForRow:mrow entity:entity channel:self];
    if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	return NO;

    if(delegateRespondsTo.didInsertRow)
	[delegate adaptorChannel:self didInsertRow:mrow forEntity:entity];

    return YES;
}

- (BOOL)updateRow:(NSDictionary*)row
  describedByQualifier:(EOQualifier*)qualifier
{
    EOSQLExpression* sqlexpr;
    NSMutableDictionary* mrow = row;

    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    if(!row)
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:@"row argument for updateRow:describedByQualifier: "
			   @"must not be the nil object"]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willUpdateRow) {
	EODelegateResponse response;

	mrow = [[row mutableCopyWithZone:[row zone]] autorelease];
	response = [delegate adaptorChannel:self
			     willUpdateRow:mrow
			     describedByQualifier:qualifier];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    sqlexpr = [[[adaptorContext adaptor] expressionClass]
					updateExpressionForRow:mrow
					qualifier:qualifier
					channel:self];
    if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	return NO;

    if(delegateRespondsTo.didUpdateRow)
	[delegate adaptorChannel:self
		  didUpdateRow:mrow
		  describedByQualifier:qualifier];
    return YES;
}

- (BOOL)deleteRowsDescribedByQualifier:(EOQualifier*)qualifier
{
    EOSQLExpression* sqlexpr;

    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willDeleteRows) {
	EODelegateResponse response
		= [delegate adaptorChannel:self
			    willDeleteRowsDescribedByQualifier:qualifier];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    sqlexpr = [[[adaptorContext adaptor] expressionClass]
		    deleteExpressionWithQualifier:qualifier channel:self];
    if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	return NO;

    if(delegateRespondsTo.didDeleteRows)
	[delegate adaptorChannel:self
		  didDeleteRowsDescribedByQualifier:qualifier];

    return YES;
}

- (BOOL)selectAttributes:(NSArray*)attributes
  describedByQualifier:(EOQualifier*)qualifier
  fetchOrder:(NSArray*)fetchOrder
  lock:(BOOL)lockFlag
{
    EOSQLExpression* sqlexpr;
    NSMutableArray* mattrs = attributes;
    NSMutableArray* mfetch = fetchOrder;

    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    if (!attributes)
	THROW([[InvalidArgumentException alloc]
		initWithFormat:@"attributes argument for selectAttributes:"
			       @"describedByQualifier:fetchOrder:lock: "
			       @"must not be the nil object"]);

    if([self isFetchInProgress])
	THROW([AdaptorIsFetchingException exceptionWithAdaptor:self]);

    if(![adaptorContext transactionNestingLevel])
	THROW([NoTransactionInProgressException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willSelectAttributes) {
	EODelegateResponse response;

	mattrs = [[attributes mutableCopyWithZone:[attributes zone]]
		    autorelease];
	mfetch = [[fetchOrder mutableCopyWithZone:[fetchOrder zone]]
		    autorelease];
	response = [delegate adaptorChannel:self
			     willSelectAttributes:mattrs
			     describedByQualifier:qualifier
			     fetchOrder:mfetch
			     lock:lockFlag];
	if(response == EODelegateRejects)
	    return NO;
	else if(response == EODelegateOverrides)
	    return YES;
    }

    sqlexpr = [[[adaptorContext adaptor] expressionClass]
		    selectExpressionForAttributes:attributes
		    lock:lockFlag
		    qualifier:qualifier
		    fetchOrder:fetchOrder
		    channel:self];
    if(![self evaluateExpression:[sqlexpr expressionValueForContext:nil]])
	return NO;

    if(delegateRespondsTo.didSelectAttributes)
	[delegate adaptorChannel:self
		  didSelectAttributes:mattrs
		  describedByQualifier:qualifier
		  fetchOrder:mfetch
		  lock:lockFlag];

    return YES;
}

- (NSArray*)describeResults
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSMutableDictionary*)fetchAttributes:(NSArray*)attributes 
  withZone:(NSZone*)zone
{
    NSMutableDictionary* row = nil;

    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    if (!zone)
	zone = NSDefaultMallocZone();

    if(![self isFetchInProgress])
	THROW([AdaptorIsNotFetchingException exceptionWithAdaptor:self]);

    if(delegateRespondsTo.willFetchAttributes)
	row = [delegate adaptorChannel:self
			willFetchAttributes:attributes
			withZone:zone];

    /* NOTE: primaryFetchAttributes:withZone: have to set the isFetchInProgress
       status */
    if(!row)
	row = [self primaryFetchAttributes:attributes withZone:zone];

    if(row) {
	if(delegateRespondsTo.didFetchAttributes)
	    row = [delegate adaptorChannel:self
			    didFetchAttributes:row
			    withZone:zone];
	if(delegateRespondsTo.didChangeResultSet)
	    [delegate adaptorChannelDidChangeResultSet:self];
    }
    else {
	/* Do not set here the isFetchInProgress status since only subclasses
	   can know whether there are more SELECT commands to be executed.
	   Setting the status here to NO will overwrite the adaptor subclass
	   intention. */
	if(delegateRespondsTo.didFinishFetching)
	    [delegate adaptorChannelDidFinishFetching:self];
    }

    return row;
}

- (BOOL)isFetchInProgress
{
    return isFetchInProgress;
}

- (void)cancelFetch
{
    if (!isOpen)
	THROW ([ChannelIsNotOpenedException new]);

    isFetchInProgress = NO;
}

- (NSMutableDictionary*)dictionaryWithObjects:(id*)objects 
  forAttributes:(NSArray*)attributes zone:(NSZone*)zone
{
    [self notImplemented:_cmd];
    return nil;
}

- (NSMutableDictionary*)primaryFetchAttributes:(NSArray*)attributes 
  withZone:(NSZone*)zone
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (BOOL)evaluateExpression:(NSString*)anExpression
{
    [self subclassResponsibility:_cmd];
    return NO;
}

- (EOAdaptorContext*)adaptorContext
{
    return adaptorContext;
}

- (EOModel*)describeModelWithTableNames:(NSArray*)tableNames
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSArray*)describeTableNames
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (BOOL)readTypesForEntity:(EOEntity*)anEntity
{
    [self subclassResponsibility:_cmd];
    return NO;
}

- (BOOL)readTypeForAttribute:(EOAttribute*)anAttribute
{
    [self subclassResponsibility:_cmd];
    return NO;
}

- delegate
{
    return delegate;
}

- (void)setDelegate:_delegate
{
    delegate = _delegate;

    delegateRespondsTo.willInsertRow = 
	[delegate respondsToSelector:
		@selector(adaptorChannel:willInsertRow:forEntity:)];
    delegateRespondsTo.didInsertRow = 
	[delegate respondsToSelector:
		@selector(adaptorChannel:didInsertRow:forEntity:)];
    delegateRespondsTo.willUpdateRow =
	[delegate respondsToSelector:
		@selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
    delegateRespondsTo.didUpdateRow =
	[delegate respondsToSelector:
		@selector(adaptorChannel:didUpdateRow:describedByQualifier:)];
    delegateRespondsTo.willDeleteRows =
	[delegate respondsToSelector:
		@selector(adaptorChannel:willDeleteRowsDescribedByQualifier:)];
    delegateRespondsTo.didDeleteRows =
	[delegate respondsToSelector:
		@selector(adaptorChannel:didDeleteRowsDescribedByQualifier:)];
    delegateRespondsTo.willSelectAttributes =
	[delegate respondsToSelector:
		@selector(adaptorChannel:willSelectAttributes:
			  describedByQualifier:fetchOrder:lock:)];
    delegateRespondsTo.didSelectAttributes =
	[delegate respondsToSelector:
		@selector(adaptorChannel:didSelectAttributes:
			  describedByQualifier:fetchOrder:lock:)];
    delegateRespondsTo.willFetchAttributes =
	[delegate respondsToSelector:
		@selector(adaptorChannel:willFetchAttributes:withZone:)];
    delegateRespondsTo.didFetchAttributes =
	[delegate respondsToSelector:
		@selector(adaptorChannel:didFetchAttributes:withZone:)];
    delegateRespondsTo.didChangeResultSet =
	[delegate respondsToSelector:
		@selector(adaptorChannelDidChangeResultSet:)];
    delegateRespondsTo.didFinishFetching =
	[delegate respondsToSelector:
		@selector(adaptorChannelDidFinishFetching:)];
    delegateRespondsTo.willEvaluateExpression =
	[delegate respondsToSelector:
		@selector(adaptorChannel:willEvaluateExpression:)];
    delegateRespondsTo.didEvaluateExpression =
	[delegate respondsToSelector:
		@selector(adaptorChannel:didEvaluateExpression:)];
}

- (void)setDebugEnabled:(BOOL)flag
{
    debugEnabled = flag;
}

- (BOOL)isDebugEnabled			{ return debugEnabled; }
- (BOOL)isOpen				{ return isOpen; }

@end /* EOAdaptorChannel */
