/* 
   Postgres95Values.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/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/Postgres95/Postgres95Adaptor.h>
#include <eoadaptors/Postgres95/Postgres95Channel.h>
#include <eoadaptors/Postgres95/Postgres95Values.h>
#include <eoadaptors/Postgres95/Postgres95Exceptions.h>

void __postgres95_values_linking_function (void)
{
}

@implementation NSString (Postgres95ValueCreation)

+ postgres95ValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(Postgres95Channel*)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];
}

- stringValueForPostgres95Type:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    if ([type isEqual:@"bytea"])
	    return [[NSData dataWithBytes:[self cString]
			    length:[self cStringLength]]
		    stringValueForPostgres95Type:type attribute:attribute];
    else
	    return [[[[EOQuotedExpression alloc]
		    initWithExpression:self quote:@"'" escape:@"\\'"]
		    autorelease]
		    expressionValueForContext:nil];
    return nil;
}

@end /* NSString (Postgres95ValueCreation) */


@implementation NSNumber (Postgres95ValueCreation)

+ postgres95ValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(Postgres95Channel*)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:@"Postgres95 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]];	
}

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

    return [self description];
}

@end /* NSNumber (Postgres95ValueCreation) */


@implementation NSData (Postgres95ValueCreation)

+ postgres95ValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(Postgres95Channel*)channel
  zone:(NSZone*)zone
{
    if (length == 0 || bytes == NULL)
	return [EONull null];
	
    if ([[attribute externalType] isEqualToString:@"bytea"]) {
	char* data;
	char* cdata;
	int dlen;
	
	if ((length < 4) || ((length & 1) == 1))
	THROW([[Postgres95Exception alloc] initWithFormat:
		@"Postgres95: invalid format for bytea/NSData."
		@"Attribute %@", [attribute name]]);
	
	bytes += 2; length -= 2;
	dlen = length / 2;
	cdata = data = NSZoneMalloc(zone, dlen);
	
#define c2h(c) ((c <= '9') ? (c - '0') : (c - 'A' + 10))
	
	while (length > 0) {
	    *cdata = (c2h(bytes[0]) << 4) + c2h(bytes[1]);
	    bytes += 2; cdata++; length -= 2;
	}

#undef c2h
	if (owned)
	    Free(bytes);
	
	return [[[NSData allocWithZone:zone]
		initWithBytesNoCopy:data length:dlen]
		autorelease];
    }

    if (owned)
	return [[[self allocWithZone:zone]
	    initWithBytesNoCopy:bytes length:length]
	    autorelease];
    else
	return [[[self allocWithZone:zone]
	    initWithBytes:bytes length:length]
	    autorelease];
}

- stringValueForPostgres95Type:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    if ([[attribute externalType] isEqualToString:@"bytea"]) {
	const char* bytes = [self bytes];
	int length = [self length];
	int final_length;
	char *description, *temp;
	int i;
    
	if (!length)
	    return @"''";

	final_length = 4 + 2 * length + 1;
	description = Malloc (final_length);
	temp = description + 3;
    
	description[0] = 0;
	strcat (description, "'0x");
	for (i = 0; i < length; i++, temp += 2)
	    sprintf (temp, "%02X", (unsigned char)bytes[i]);
	temp[0] = '\'';
	temp[1] = 0;
	
	return [[[NSString alloc] 
		initWithCStringNoCopy:description
		length:final_length-1
		freeWhenDone:YES]
	    autorelease];
    }
    
    return [[NSString stringWithCString:[self bytes] length:[self length]]
	    stringValueForPostgres95Type:type attribute:attribute];
}

@end /* NSData (Postgres95ValueCreation) */


@implementation NSCalendarDate (Postgres95ValueCreation)

//static id CALENDAR_FORMAT = @"%a %b %d %H:%M:%S %Y %Z";
//static id CALENDAR_FORMAT = @"%b %d %Y %I:%M%p %Z";
static id CALENDAR_FORMAT = nil;
+(void)setPostgres95Format:(NSString*)_format
{
  ASSIGN(CALENDAR_FORMAT,_format); 
};

+ postgres95ValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(Postgres95Channel*)channel
  zone:(NSZone*)zone
{
  NSString* externalType = [attribute externalType];
  id date, fmt, tz;
  if (!CALENDAR_FORMAT)
	[NSCalendarDate setPostgres95Format:[NSString stringWithCString:"%b %d %Y %I:%M%p %Z"]];

  if (length == 0 || bytes == NULL || strncasecmp(bytes,"invalid",7)==0)
	return [EONull null];

  // abstime and varchar are valid NSCalendarDate external types
  if ((([externalType isEqualToString:@"abstime"] == NO)
	   && ([externalType isEqualToString:@"varchar"] == NO))
	  || owned == YES)
	THROW([[DataTypeMappingNotSupportedException alloc]
			initWithFormat:@"Postgres95 cannot map external type %@ "
			@"to NSCalendarDate in attribute %@",
			externalType, [attribute name]]);
  date = [NSCalendarDate dateWithString:[NSString stringWithCString:bytes]
						 calendarFormat:CALENDAR_FORMAT];
  
  fmt = [attribute calendarFormat];
  if (fmt)
	[date setCalendarFormat:fmt];
  
  tz = [attribute clientTimeZone];
  if (tz)
	[date setTimeZone:tz];
  
  return date;
}

- stringValueForPostgres95Type:(NSString*)type
  attribute:(EOAttribute*)attribute
{
    NSString* externalType = [attribute externalType];
	if (!CALENDAR_FORMAT)
	  [NSCalendarDate setPostgres95Format:[NSString stringWithCString:"%b %d %Y %I:%M%p %Z"]];
  
    if ([externalType isEqualToString:@"abstime"]) {
	id tz = [attribute serverTimeZone];
	id date;

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

	return [NSString stringWithFormat:@"'%@'",
		    [date descriptionWithCalendarFormat:CALENDAR_FORMAT]];
    }

    THROW([[DataTypeMappingNotSupportedException alloc]
	    initWithFormat:@"Postgres95 cannot map NSCalendarDate in "
			    @"attribute %@ to external type %@",
			    [attribute name], externalType]);
    return nil;
}

@end /* NSCalendarDate (Postgres95ValueCreation) */


@implementation EONull (Postgres95ValueCreation)

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

@end


@implementation NSObject (Postgres95ValueCreation)

+ postgres95ValueFromBytes:(char*)bytes
  length:(int)length
  owned:(BOOL)owned
  attribute:(EOAttribute*)attribute
  adaptorChannel:(Postgres95Channel*)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:@"Postgres95 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;
}

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

@end /* NSObject (Postgres95ValueCreation) */
