/* 
   MySQLValues.m

   Copyright (C) 2000 Free Software Foundation, Inc.

   Author: Stephen Peters <portnoy@portnoy.org>
   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/NSData.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSString.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSObjCRuntime.h>

#include <extensions/NSException.h>

#include <eoaccess/common.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EOCustomValues.h>
#include <eoaccess/exceptions/EOFExceptions.h>
#include <eoaccess/EOQuotedExpression.h>

#include <eoadaptors/MySQL/MySQLAdaptor.h>
#include <eoadaptors/MySQL/MySQLChannel.h>
#include <eoadaptors/MySQL/MySQLValues.h>
#include <eoadaptors/MySQL/MySQLExceptions.h>

void __mysql_values_linking_function (void)
{
}

@implementation NSString (MySQLValueCreation)

+ mysqlValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(MySQLChannel*)channel
  zone:(NSZone*)zone
{
    if (length == 0 || bytes == NULL)
	return [EONull null];

    return owned ?
	[[[NSString allocWithZone:zone]
	    initWithCStringNoCopy:bytes length:length freeWhenDone:YES] 
	    autorelease] :
	[[[NSString allocWithZone:zone]
	    initWithCString:bytes length:length] autorelease];
}

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    return [[[[EOQuotedExpression alloc]
		 initWithExpression:self quote:@"'" escape:@"\\'"]
		autorelease]
	       expressionValueForContext:nil];
    return nil;
}

@end /* NSString (MySQLValueCreation) */


@implementation NSNumber (MySQLValueCreation)

+ mysqlValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(MySQLChannel*)channel
  zone:(NSZone*)zone
{
    if (length == 0 || bytes == NULL)
	return [EONull null];
    
    if ([[attribute externalType] isEqualToString:@"bool"]) {
	if (bytes[0] == 't' && bytes[1] == 0)
	    return [NSNumber numberWithBool:YES];
	if (bytes[0] == 'f' && bytes[1] == 0)
	    return [NSNumber numberWithBool:NO];
	
	if (owned)
	    Free(bytes);
	
	THROW([[DataTypeMappingNotSupportedException alloc]
		initWithFormat:@"MySQL cannot make BOOL NSNumber"
		@"from string value %s", bytes]);
    }

    return [NSNumber numberWithString:owned ?
		[[[NSString allocWithZone:zone]
		    initWithCStringNoCopy:bytes length:length freeWhenDone:YES]
		    autorelease] :
		[[[NSString allocWithZone:zone]
		    initWithCStringNoCopy:bytes length:length freeWhenDone:NO]
		    autorelease]
	    type:[attribute valueType]];	
}

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute;
{
    if ([[attribute externalType] isEqualToString:@"bool"])
	return [self boolValue] ? @"'t'" : @"'f'";

    return [self description];
}

@end /* NSNumber (MySQLValueCreation) */


@implementation NSData (MySQLValueCreation)

+ mysqlValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(MySQLChannel*)channel
  zone:(NSZone*)zone
{
    if (length == 0 || bytes == NULL)
	return [EONull null];
	
    if (owned)
	return [[[self allocWithZone:zone]
	    initWithBytesNoCopy:bytes length:length]
	    autorelease];
    else
	return [[[self allocWithZone:zone]
	    initWithBytes:bytes length:length]
	    autorelease];
}

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    return [[NSString stringWithCString:[self bytes] length:[self length]]
	    stringValueForMySQLType:type attribute:attribute];
}

@end /* NSData (MySQLValueCreation) */


@implementation NSCalendarDate (MySQLValueCreation)

+ mysqlValueFromBytes:(char*)bytes
	       length:(int)length
		owned:(BOOL)owned
	    attribute:(EOAttribute*)attribute
       adaptorChannel:(MySQLChannel*)channel
		 zone:(NSZone*)zone
{
    NSString* format = nil;
    NSString* externalType = [attribute externalType];
    id date, fmt, tz;

    // modify externalType to remove length codes
    {
	NSRange rng = [externalType rangeOfString:@"("];
	if (rng.length > 0) 
	    externalType = [externalType substringToIndex:rng.location];
    }
    
    if (length == 0 || bytes == NULL)
	return [EONull null];

    if ([externalType isEqualToString:@"time"] ||
	[externalType hasPrefix: @"time("]) {
	// handle time specially -- we generate a time interval based
	// on the reference date.
	int hour, min, sec;
	int interval;
	char *idx;
	hour = atoi(bytes);
	idx = bytes + length - 5;
	min = atoi(idx);
	idx = bytes + length - 2;
	sec = atoi(idx);
	
	if (hour < 0) 
	    interval = hour * 3600 - min * 60 - sec;
	else
	    interval = hour * 3600 + min * 60 + sec;

	date = [NSCalendarDate 
		   dateWithTimeIntervalSinceReferenceDate: interval];
	
    } else {

	// generate the right calendar format.
	if ([externalType isEqualToString:@"datetime"] == YES) {
	    format = @"%Y-%m-%d %H:%M:%S";
	} else if ([externalType isEqualToString:@"date"] == YES) {
	    format = @"%Y-%m-%d";
	} else if ([externalType isEqualToString:@"year"] == YES) {
	    if (length == 2) 
		format = @"%y";
	    else
		format = @"%Y";
	} else if ([externalType isEqualToString:@"timestamp"] == YES) {
	    // use different formats based on the length
	    switch (length) {
	    case 14:
		format = @"%Y%m%d%H%M%S"; break;
	    case 12: 
		format = @"%y%m%d%H%M%S"; break;
	    case 10:
		format = @"%y%m%d%H%M"; break;
	    case 8:
		format = @"%Y%m%d"; break;
	    case 6:
		format = @"%y%m%d"; break;
	    case 4:
		format = @"%y%m"; break;
	    case 2:
		format = @"%y"; break;
	    }
	} else {
	    THROW([[DataTypeMappingNotSupportedException alloc]
		      initWithFormat:@"MySQL cannot map external type %@ "
		      @"to NSCalendarDate in attribute %@",
		      externalType, [attribute name]]);
	}
	
	date = [NSCalendarDate dateWithString:
				   [NSString stringWithCString:bytes]
			       calendarFormat:format];
    }
    
    fmt = [attribute calendarFormat];
    if (fmt)
	[date setCalendarFormat:fmt];
    
    tz = [attribute clientTimeZone];
    if (tz)
	[date setTimeZone:tz];
    
    return date;
}

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    NSString* externalType = [attribute externalType];
    NSString* format = nil;
    id date,tz;


    tz = [attribute serverTimeZone];
    if (tz) {
	date = [[self copy] autorelease];
	[date setTimeZone:tz];
    }
    else
	date = self;

    if ([externalType isEqualToString: @"time"] ||
	[externalType hasPrefix: @"time("]) {
	// calculate time based on reference date and format it.
	int interval = [date timeIntervalSinceReferenceDate];
	int hr,min,sec;
	if (interval < 0) {
	    sec = (-interval) % 60;
	    min = ((-interval) % 3600)/60;
	} else {
	    sec = interval % 60;
	    min = (interval % 3600)/60;
	} 
	    
	hr = interval / 3600;
	
	return [NSString stringWithFormat:@"'%d:%d:%d'",hr,min,sec];
	
    } else {
	if ([externalType hasPrefix:@"year"] == YES) {
	    format = @"%Y";
	} else {
	    format = @"%Y-%m-%d %H:%M:%S";
	}
	
	return [NSString stringWithFormat:@"'%@'",
			 [date descriptionWithCalendarFormat:format]];
    }
}

@end /* NSCalendarDate (MySQLValueCreation) */


@implementation EONull (MySQLValueCreation)

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    return @"NULL";
}

@end


@implementation NSObject (MySQLValueCreation)

+ mysqlValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(MySQLChannel*)channel
  zone:(NSZone*)zone
{
    if (length == 0 || bytes == NULL)
	return [EONull null];

    if ([self instancesRespondToSelector:@selector(initWithString:type:)])
	return [[[self allocWithZone:zone]
	    initWithString:owned ?
		[[[NSString allocWithZone:zone]
		    initWithCStringNoCopy:bytes length:length freeWhenDone:YES] 
		    autorelease] :
		[[[NSString allocWithZone:zone]
		    initWithCString:bytes length:length] autorelease]
	    type:[attribute valueType]] autorelease];
    
    if ([self instancesRespondToSelector:@selector(initWithData:type:)])
	return [[[self allocWithZone:zone]
	    initWithData:owned ?
		[[[NSData allocWithZone:zone]
		    initWithBytesNoCopy:bytes length:length] autorelease] :
		[[[NSData allocWithZone:zone]
		    initWithBytes:bytes length:length] autorelease]
	    type:[attribute valueType]] autorelease];

    if (owned)
	Free(bytes);

    THROW([[DataTypeMappingNotSupportedException alloc]
	initWithFormat:@"MySQL cannot map string value to class %@"
	    @"because it does not responds to `initWithString:type:' or "
	    @"`initWithData:type:'. Attribute involved is %@, type %@",
	    NSStringFromClass(self),
	    [attribute name],
	    [attribute valueType]]);

    return nil;
}

- stringValueForMySQLType:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    if ([self respondsToSelector:@selector(stringForType:)])
	return [[self stringForType:[attribute valueType]]
		    stringValueForMySQLType:type attribute:attribute];
    else if ([self respondsToSelector:@selector(dataForType:)])
	return [[self dataForType:[attribute valueType]]
		    stringValueForMySQLType:type attribute:attribute];
    else
	THROW([[DataTypeMappingNotSupportedException alloc]
		initWithFormat:@"MySQL cannot map value class %@ "
		@"because its instances does not responds to either "
		@" `stringForType:' or `dataForType:'. ",
		NSStringFromClass([self class])]);
    return nil;
}

@end /* NSObject (MySQLValueCreation) */
