/* 
   SQLServerValues.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Author: Scott Christley <scottc@net-community.com>
   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/NSString.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSUtilities.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 "SQLServerAdaptor.h"
#include "SQLServerChannel.h"
#include "SQLServerValues.h"
#include "SQLServerExceptions.h"

void __sqlserver_values_linking_function (void)
{
}

@implementation NSString (SQLServerValueCreation)

+ valueFromBytes:(const char*)bytes
  length:(unsigned)length
  sqlserverType:(int)type
  attribute:(EOAttribute*)attribute
  adaptorChannel:(SQLServerChannel*)channel
  zone:(NSZone*)zone
{
    char buffer[256];

    switch (type) {
	case SQLTEXT:
	case SQLIMAGE:
	case SQLBINARY:
	case SQLVARBINARY:
	case SQLCHAR:
	case SQLVARCHAR:
	    return [[[self allocWithZone:zone]
			initWithCString:bytes length:length]
			autorelease];
	case SQLBIT:
	    return [[[self allocWithZone:zone]
			initWithCString:Ltoa (*(DBBIT*)bytes, buffer, 10)]
			autorelease];
	case SQLINT1:
	    return [[[self allocWithZone:zone]
			initWithCString:Ltoa (*(DBTINYINT*)bytes, buffer, 10)]
			autorelease];
	case SQLINT2:
	    return [[[self allocWithZone:zone]
			initWithCString:Ltoa (*(DBSMALLINT*)bytes, buffer, 10)]
			autorelease];
	case SQLINT4:
	    return [[[self allocWithZone:zone]
			initWithCString:Ltoa (*(DBINT*)bytes, buffer, 10)]
			autorelease];
	case SQLFLT8: {
	    id value = [[NSNumber numberWithDouble:*(DBFLT8*)bytes]
			    stringValue];
	    if ([self isKindOfClass:[NSMutableString class]])
		value = [[value mutableCopyWithZone:zone] autorelease];
	    return value;
	}
	case SQLMONEY:
	case SQLDATETIME:
	    /* Convert the type value using the dbconvert() function */
	    if (dbconvert ([channel dbProcess],
			   type, (LPCBYTE)bytes, length,
			   SQLCHAR, buffer, -1) == -1)
		return nil;
	    else return [[[self allocWithZone:zone] initWithCString:buffer]
			    autorelease];
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [self description], type]);
    }

    return nil;
}

- (NSString*)stringValueForSQLServerType:(int)type
  attribute:(EOAttribute*)attribute
{
    switch (type) {
	case SQLBINARY:
	case SQLVARBINARY:
	    return [[NSData dataWithBytes:[self cString]
			    length:[self cStringLength]]
			stringValueForSQLServerType:type attribute:attribute];
	case SQLCHAR:
	case SQLVARCHAR:
	case SQLDATETIME:
	case SQLTEXT:
	    return [[[[EOQuotedExpression alloc]
		    initWithExpression:self quote:@"'" escape:@"''"]
		    autorelease]
		    expressionValueForContext:nil];
	case SQLBIT:
	case SQLINT1:
	case SQLINT2:
	case SQLINT4:
	case SQLFLT8:
	    return self;
	case SQLMONEY:
	    return [NSString stringWithFormat:@"$%@", self];
	case SQLIMAGE:
	    NSAssert (0, @"image type should not be handled here!");
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [isa description], type]);
    }

    return nil;
}

/* We need to override the -hash and -isEqual: methods
   to correspond to how SQLServer compares strings */
- (unsigned int) hashForGDL
{
  unsigned ret = 0;
  unsigned ctr = 0;
  unsigned char_count = 0;
  const char *t = [self cStringNoCopy];
  int len = strlen(t);
  const char *s = (t + len - 1);
  int c, d;
  int done = 0;

  // skip trailing blanks
  while ((s >= t) && (!done))
    {
      c = (int)*s;
      if (!isspace(c))
	done = 1;
      else
	s--;
    }
  // uppercase letters for hash
  while ((s >= t) && char_count++ < NSHashStringLength)
    {
      c = (int)*s;
      d = toupper(c);
      ret ^= d << ctr;
      ctr = (ctr + 1) % sizeof (void*);
      s--;
    }
  return ret;
}

- (BOOL) isEqualForGDL: (id)anObject
{
  const char *s1, *s2;
  const char *t1, *t2;
  int c1, c2, d1, d2;
  int len1, len2;
  BOOL are_equal = YES;
  BOOL done;

  if (![anObject isKindOf:[NSString class]])
    return NO;

  s1 = [self cStringNoCopy];
  s2 = [anObject cStringNoCopy];
  len1 = strlen(s1);
  len2 = strlen(s2);
  t1 = (s1 + len1 - 1);
  t2 = (s2 + len2 - 1);

  // eliminate trailing whitespace
  done = NO;
  while ((t1 >= s1) && (!done))
    {
      c1 = (int)*t1;
      if (!isspace(c1))
	done = YES;
      else
	t1--;
    }
  done = NO;
  while ((t2 >= s2) && (!done))
    {
      c2 = (int)*t2;
      if (!isspace(c2))
	done = YES;
      else
	t2--;
    }

  // case insensitive comparison
  done = NO;
  while ((t1 >= s1) && (t2 >= s2) && (!done))
    {
      c1 = (int)*t1;
      c2 = (int)*t2;
      d1 = toupper(c1);
      d2 = toupper(c2);
      if (d1 != d2)
	{
	  done = YES;
	  are_equal = NO;
	}
      else
	{
	  t1--;
	  t2--;
	}
    }

  if ((t1 >= s1) || (t2 >= s2))
    are_equal = NO;

  return are_equal;
}

@end /* NSString (SQLServerValueCreation) */


@implementation NSNumber (SQLServerValueCreation)

+ valueFromBytes:(const char*)bytes
  length:(unsigned)length
  sqlserverType:(int)type
  attribute:(EOAttribute*)attribute
  adaptorChannel:(SQLServerChannel*)channel
  zone:(NSZone*)zone
{
#define RETURN_NUMBER(DATABASE_TYPE, defaultMethod) \
    if (!valueType) \
	return [NSNumber defaultMethod *(DATABASE_TYPE*)bytes]; \
    if ([valueType isEqual:@"c"]) \
	return [NSNumber numberWithChar:*(DATABASE_TYPE*)bytes]; \
    if ([valueType isEqual:@"C"]) \
	return [NSNumber numberWithUnsignedChar:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"s"]) \
	return [NSNumber numberWithShort:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"S"]) \
	return [NSNumber numberWithUnsignedShort:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"i"]) \
	return [NSNumber numberWithInt:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"I"]) \
	return [NSNumber numberWithUnsignedInt:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"f"]) \
	return [NSNumber numberWithFloat:*(DATABASE_TYPE*)bytes]; \
    else if ([valueType isEqual:@"d"]) \
	return [NSNumber numberWithDouble:*(DATABASE_TYPE*)bytes]; \
    else THROW([[InvalidValueTypeException alloc] initWithType:valueType]);

    NSString* valueType = [attribute valueType];

    switch (type) {
	case SQLBIT:
	    RETURN_NUMBER(DBBIT, numberWithChar:);
	case SQLINT1:
	    RETURN_NUMBER(DBTINYINT, numberWithChar:);
	case SQLINT2:
	    RETURN_NUMBER(DBSMALLINT, numberWithShort:);
	case SQLINT4:
	    RETURN_NUMBER(DBINT, numberWithInt:);
	case SQLFLT8:
	    RETURN_NUMBER(DBFLT8, numberWithDouble:);
	case SQLMONEY: {
	    double value;
	    /* Convert the money value using the dbconvert() function */
	    if (dbconvert ([channel dbProcess],
			   type, (LPCBYTE)bytes, length,
			   SQLFLT8, (LPBYTE)&value, sizeof(double)) == -1)
		return nil;
	    else {
		bytes = (void*)&value;
		RETURN_NUMBER(double, numberWithDouble:);
	    }
	}
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [self description], type]);
    }

    return nil;

#undef RETURN_NUMBER
}

- (NSString*)stringValueForSQLServerType:(int)type
  attribute:(EOAttribute*)attribute
{
    switch (type) {
	case SQLBIT:
	case SQLINT1:
	case SQLINT2:
	case SQLINT4:
	case SQLFLT8:
	    return [self stringValue];
	case SQLMONEY:
	    return [NSString stringWithFormat:@"$%.4f", [self doubleValue]];
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [isa description], type]);
    }

    return nil;
}

@end /* NSNumber (SQLServerValueCreation) */


@implementation NSData (SQLServerValueCreation)

+ valueFromBytes:(const char*)bytes
  length:(unsigned)length
  sqlserverType:(int)type
  attribute:(EOAttribute*)attribute
  adaptorChannel:(SQLServerChannel*)channel
  zone:(NSZone*)zone
{
    switch (type) {
	case SQLTEXT:
	case SQLIMAGE:
	case SQLBINARY:
	case SQLVARBINARY:
	case SQLCHAR:
	case SQLVARCHAR:
	case SQLINT1:
	case SQLINT2:
	case SQLINT4:
	case SQLFLT8:
	    return [[[self allocWithZone:zone]
			initWithBytes:bytes length:length]
			autorelease];
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [self description], type]);
    }

    return nil;
}

- (NSString*)stringValueForSQLServerType:(int)type
  attribute:(EOAttribute*)attribute
{
    const char* bytes = [self bytes];

    switch (type) {
	case SQLINT1:
	    return [[NSNumber numberWithChar:*(char*)bytes] stringValue];
	case SQLINT2:
	    return [[NSNumber numberWithShort:*(short*)bytes] stringValue];
	case SQLINT4:
	    return [[NSNumber numberWithInt:*(int*)bytes] stringValue];
	case SQLFLT8:
	    return [[NSNumber numberWithDouble:*(double*)bytes] stringValue];
	case SQLCHAR:
	case SQLVARCHAR:
	case SQLBINARY:
	case SQLVARBINARY:
	case SQLIMAGE: {
	    int length = [self length];
	    int final_length;
	    char *description, *temp;
	    int i;

	    if (!length)
		return @"NULL";

	    final_length = 2 + 2 * length + 1;
	    description = Malloc (final_length);
	    temp = description + 2;

	    description[0] = 0;
	    strcat (description, "0x");
	    for (i = 0; i < length; i++, temp += 2)
		sprintf (temp, "%02X", (unsigned char)bytes[i]);
	    *temp = 0;
	    return [[[NSString alloc] 
		    initWithCStringNoCopy:description
		    length:Strlen(description)
		    freeWhenDone:YES]
		autorelease];
	}
	case SQLTEXT:
	    NSAssert (0, @"text type should not be handled here!");
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [isa description], type]);
    }

    return nil;
}

@end /* NSData (SQLServerValueCreation) */


@implementation NSCalendarDate (SQLServerValueCreation)

+ valueFromBytes:(const char*)bytes
  length:(unsigned)length
  sqlserverType:(int)type
  attribute:(EOAttribute*)attribute
  adaptorChannel:(SQLServerChannel*)channel
  zone:(NSZone*)zone
{
    DBDATEREC daterec;

    switch (type) {
	case SQLDATETIME:
	    if (dbdatecrack ([channel dbProcess], &daterec, (DBDATETIME*)bytes)
		== SUCCEED) {
		return [[channel class]
			    dateForAttribute:attribute
					year:daterec.year
				       month:daterec.month
					 day:daterec.day
					hour:daterec.hour
				      minute:daterec.minute
				      second:daterec.second
					zone:zone];
	    }
	default:
	    THROW([[DataTypeMappingNotSupportedException alloc]
		    initWithFormat:@"mapping between %@ and sqlserver type %d "
				@"not supported", [self description], type]);
    }

    return nil;
}

- (NSString*)stringValueForSQLServerType:(int)type
  attribute:(EOAttribute*)attribute
{
    if (type == SQLDATETIME)
	return [[[[EOQuotedExpression alloc]
		    initWithExpression:[self descriptionWithCalendarFormat:
						@"%b %d %Y %I:%M:%S%p"]
		    quote:@"'" escape:@"''"]
		    autorelease]
		    expressionValueForContext:nil];
    else
	THROW([[DataTypeMappingNotSupportedException alloc]
		initWithFormat:@"mapping between %@ and sqlserver type %d not "
			       @"supported", [isa description], type]);

    return nil;
}

@end /* NSCalendarDate (SQLServerValueCreation) */


@implementation EONull (SQLServerValueCreation)

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

@end


@implementation NSObject (SQLServerValueCreation)

+ valueFromBytes:(const char*)bytes
  length:(unsigned)length
  sqlserverType:(int)type
  attribute:(EOAttribute*)attribute
  adaptorChannel:(SQLServerChannel*)channel
  zone:(NSZone*)zone
{
    if ([self instancesRespondToSelector:@selector(initWithString:type:)])
	return [[[self allocWithZone:zone]
	    initWithString:[NSString stringWithCString:bytes length:length]
	    type:[attribute valueType]]
	    autorelease];
    else if ([self instancesRespondToSelector:@selector(initWithData:type:)])
	return [[[self allocWithZone:zone]
	    initWithData:[NSData dataWithBytes:bytes length:length]
	    type:[attribute valueType]]
	    autorelease];
    else
	THROW([[DataTypeMappingNotSupportedException alloc]
		initWithFormat:@"mapping between %@ and sqlserver type %d not "
			       @"supported", [self description], type]);

    return nil;
}

- (NSString*)stringValueForSQLServerType:(int)type
  attribute:(EOAttribute*)attribute
{
    if ([self respondsToSelector:@selector(stringForType:)])
	return [[self stringForType:[attribute valueType]]
		    stringValueForSQLServerType:type attribute:attribute];
    else if ([self respondsToSelector:@selector(dataForType:)])
	return [[self dataForType:[attribute valueType]]
		    stringValueForSQLServerType:type attribute:attribute];
    else
	THROW([[DataTypeMappingNotSupportedException alloc]
		initWithFormat:@"mapping between %@ and sqlserver type %d not "
			       @"supported", [isa description], type]);

    return nil;
}

@end /* NSObject (SQLServerValueCreation) */
