/* 
   SQLServerAdaptor.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 <config.h>

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

#if !NeXT_foundation_LIBRARY
#include <Foundation/NSProcessInfo.h>
#endif

#include <extensions/support.h>
#include <extensions/NSException.h>

#include <eoaccess/common.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EOExpressionArray.h>

#include "SQLServerAdaptor.h"
#include "SQLServerContext.h"
#include "SQLServerChannel.h"
#include "SQLServerExceptions.h"
#include "SQLServerSQLExpression.h"
#include "SQLServerValues.h"

NSString* _sqlserverErrorMessage = nil;
NSString* _sqlserverInfoMessage = nil;

@implementation SQLServerAdaptor

enum { MAX_RETRIES = 3 };

// Class variables
static BOOL logsMessages = YES;
static BOOL logsErrors = YES;
static int timeOutInterval = 0;
static int loginTimeOut = 0;
static unsigned _retries = 0;

static int
error_handler(DBPROCESS* dbprocess,
	      int severity,
	      int dbErr,
	      int osErr,
	      char* dbErrStr,
	      char* osErrStr)
{
    if(logsErrors)
	NSLog(@"SQLServer error: %s\n", dbErrStr);

   [_sqlserverErrorMessage release];
   _sqlserverErrorMessage = [[NSString stringWithCString:dbErrStr] retain];

    if(severity == EXTIME) {
	if(_retries++ < MAX_RETRIES)
	    return INT_CONTINUE;
	else {
	    _retries = 0;
	    return INT_CANCEL;
	}
    }
    else
	_retries = 0;

    return INT_CANCEL;
}

static int
message_handler(DBPROCESS* dbprocess,
		DBINT msgno,
		int msgstate,
		int severity,
		char *msgtext,
		char *srvname,
		char *procname,
		DBUSMALLINT line)
{
    if(logsMessages) {
	NSLog(@"SQLServer message: message %ld, level %d, state %d",
		msgno, severity, msgstate);
	if(srvname)
	    NSLog(@"Server '%s', ", srvname);
	if(procname)
	  NSLog(@"Procedure '%s', ", procname);
	if(line > 0)
	    NSLog(@"Line %d", line);

	NSLog(@"\n\t%s\n", msgtext);
    }

    [_sqlserverInfoMessage release];
    _sqlserverInfoMessage = [[NSString stringWithCString:msgtext] retain];

    return 0;
}

+ (void)initialize
{
    dbinit();
    dberrhandle((DBERRHANDLE_PROC)error_handler);
    dbmsghandle((DBMSGHANDLE_PROC)message_handler);
}

- init
{
    return [self initWithName:@"SQLServer"];
}

- initWithName:(NSString*)_name
{
    /* Don't use -dictionaryWithObjectsAndKeys: of NSDictionary because the
       compiler generates bad code when the number of arguments is greater than
       20 (at least on Intel running NeXTStep). */
    NSArray* values = [NSArray arrayWithObjects:
				[NSNumber numberWithInt:SQLINT1],
				[NSNumber numberWithInt:SQLINT2],
				[NSNumber numberWithInt:SQLINT4],
				[NSNumber numberWithInt:SQLFLT8],
				[NSNumber numberWithInt:SQLCHAR],
				[NSNumber numberWithInt:SQLVARCHAR],
				[NSNumber numberWithInt:SQLBINARY],
				[NSNumber numberWithInt:SQLVARBINARY],
				[NSNumber numberWithInt:SQLMONEY],
				[NSNumber numberWithInt:SQLDATETIME],
				[NSNumber numberWithInt:SQLBIT],
				[NSNumber numberWithInt:SQLTEXT],
				[NSNumber numberWithInt:SQLIMAGE],
				nil];
    NSArray* keys = [NSArray arrayWithObjects:
				@"tinyint",
				@"smallint",
				@"int",
				@"float",
				@"char",
				@"varchar",
				@"binary",
				@"varbinary",
				@"money",
				@"datetime",
				@"bit",
				@"text",
				@"image",
				nil];

    [super initWithName:_name];
    typesByName = [[NSMutableDictionary dictionaryWithObjects:values
					forKeys:keys]
			retain];
    loginRecord = dblogin();
    dbProcessPool = [NSMutableArray new];
    return self;
}

- (void)dealloc
{
    NSEnumerator* enumerator;
    DBPROCESS* dbprocess;

    [typesByName release];
    dbfreelogin(loginRecord);

    enumerator = [dbProcessPool objectEnumerator];
    while((dbprocess = [[enumerator nextObject] pointerValue]))
	dbclose(dbprocess);
    [dbProcessPool release];

    [super dealloc];
}

- (int)typeForName:(NSString*)typeName
{
    int value = [[typesByName objectForKey:typeName] intValue];

    if(!value) {
	/* Search in systypes for typeName and return the value. Create a new
	   channel and fetch the user specific types. If it's not possible
	   this method returns -1. */
	id pool = [NSAutoreleasePool new];
	id adaptorContext = [self createAdaptorContext];
	id adaptorChannel = [adaptorContext createAdaptorChannel];
	NSArray* attrs;
	NSDictionary* row;
	id attrName, sybaseTypeName;

	[adaptorChannel setDebugEnabled:YES];
	if (![adaptorChannel openChannel])
	    return -1;

	if (![adaptorContext beginTransaction])
	    return -1;
	if (![adaptorChannel evaluateExpression:@"SELECT s.name, st.name "
			    @"FROM systypes s, systypes st "
			    @"WHERE s.type = st.type AND s.usertype > 99 "
			    @"AND st.usertype != 18 AND st.usertype < 100"])
	    return -1;

	attrs = [adaptorChannel describeResults];
	attrName = [[attrs objectAtIndex:0] name];
	sybaseTypeName = [[attrs objectAtIndex:1] name];

	while ((row = [adaptorChannel fetchAttributes:attrs withZone:NULL])) {
	    id userType = [row objectForKey:attrName];
	    id definition = [row objectForKey:sybaseTypeName];
	    int sybaseType = [self typeForName:definition];
	    [typesByName setObject:[NSNumber numberWithInt:sybaseType]
			 forKey:userType];
	}

	if (![adaptorContext commitTransaction])
	    return -1;

	[adaptorChannel closeChannel];

	value = [[typesByName objectForKey:typeName] intValue];
	[pool release];
    }

    return value;
}

+ (void)setLoginTimeOutInterval:(int)seconds
{
    loginTimeOut = seconds;
    dbsetlogintime(loginTimeOut);
}

+ (void)setTimeOutInterval:(int)seconds
{
    timeOutInterval = seconds;
    dbsettime(timeOutInterval);
}

+ (void)setLogsMessages:(BOOL)flag		{ logsMessages = flag; }
+ (BOOL)logsMessages				{ return logsMessages; }
+ (void)setLogsErrors:(BOOL)flag		{ logsErrors = flag; }
+ (BOOL)logsErrors				{ return logsErrors; }
+ (int)timeOutInterval				{ return timeOutInterval; }
+ (int)loginTimeOutInterval			{ return loginTimeOut; }
- (void)setMaxTextSizeDefault:(int)textSize	{ maxTextSize = textSize; }
- (int)maxTextSizeDefault			{ return maxTextSize; }
- (void)setShareDBProcesses:(BOOL)flag		{ shareDBProcesses = flag; }
- (BOOL)shareDBProcesses			{ return shareDBProcesses; }
- (void)setDBProcessPoolLimit:(int)value	{ dbProcessPoolLimit = value; }
- (int)dbProcessPoolLimit			{ return dbProcessPoolLimit; }

/* Inherited methods */

- (Class)expressionClass
{
    return [SQLServerSQLExpression class];
}

- (Class)adaptorChannelClass
{
    return [SQLServerChannel class];
}

- (Class)adaptorContextClass
{
    return [SQLServerContext class];
}

- formatValue:value forAttribute:(EOAttribute*)attribute
{
    DBINT sqlserverType = [self typeForName:[attribute externalType]];

    return [value stringValueForSQLServerType:sqlserverType 
		  attribute:attribute];
}

- (BOOL)isValidQualifierType:(NSString*)typeName
{
    DBINT sqlserverType = [self typeForName:typeName];

    switch (sqlserverType) {
	case SQLINT1:
	case SQLINT2:
	case SQLINT4:
	case SQLFLT8:
	case SQLCHAR:
	case SQLVARCHAR:
	case SQLMONEY:
	case SQLDATETIME:
	case SQLBIT:
	    return YES;
	default:
	    return NO;
    }
}

/* Specific methods */

- (DBPROCESS*)_createDBProcess
{
    DBPROCESS* dbprocess;
    const char* serverName
	    = [[connectionDictionary objectForKey:@"databaseServer"] cString];
    const char* databaseName;

    if(!serverName) {
	serverName = [[connectionDictionary objectForKey:@"hostName"] cString];
	/* Provide a reasonable default for the server name */
	if (!serverName)
	    serverName = "SQLSERVER";
    }

    /*    if([connectionDictionary objectForKey:@"ifacefile"])
	dbsetifile([[connectionDictionary objectForKey:@"ifacefile"]
			cString]); */

    if([connectionDictionary objectForKey:@"userName"])
	DBSETLUSER(loginRecord,
		   [[connectionDictionary objectForKey:@"userName"] cString]);

    if([connectionDictionary objectForKey:@"password"])
	DBSETLPWD(loginRecord,
		  [[connectionDictionary objectForKey:@"password"] cString]);

#if 0
#if !NeXT_foundation_LIBRARY
    if([[NSProcessInfo processInfo] processName])
	DBSETLAPP(loginRecord,
		  [[[NSProcessInfo processInfo] processName] cString]);
#endif
#endif

    dbprocess = dbopen(loginRecord, serverName);

    /* Set the maximum text size */
    if(dbprocess && maxTextSize)
      {
	NSString *s = [NSString stringWithFormat:@"%d", maxTextSize];
	dbsetopt(dbprocess, DBTEXTLIMIT, [s cString]);
      }

    /* Change the database */
    databaseName = [[connectionDictionary objectForKey:@"databaseName"]
			cString];
    if(dbprocess && databaseName)
	dbuse(dbprocess, databaseName);

    return dbprocess;
}

- (DBPROCESS*)newDBProcess
{
    DBPROCESS* dbprocess;

    if(shareDBProcesses) {
	if([dbProcessPool count]) {
	    /* If we have any free DBPROCESS return it */
	    dbprocess = [[dbProcessPool lastObject] pointerValue];
	    [dbProcessPool removeLastObject];
	    return dbprocess;
	}
	else {
	    /* Create a new DBPROCESS and return it. The caller owns it and
	       he should call the relaseDBProcess when it finishes with the
	       DBPROCESS. */
	    if(dbProcessesInUse + 1 > dbProcessPoolLimit)
		THROW([[TooManyDBProcessesInUse alloc]
			initWithFormat:@"Too many DBPROCESSES used by this "
				       @"adaptor: %d", dbProcessPoolLimit]);
	    dbProcessesInUse++;
	    dbprocess = [self _createDBProcess];
	}
    }
    else
	dbprocess = [self _createDBProcess];

    return dbprocess;
}

- (void)releaseDBProcess:(DBPROCESS*)dbprocess
{
    if(shareDBProcesses) {
	dbProcessesInUse--;
	[dbProcessPool addObject:[NSValue value:dbprocess
					  withObjCType:@encode(DBPROCESS*)]];
    }
    else
	dbclose(dbprocess);

}

- (BOOL)hasValidConnectionDictionary
{
  DBPROCESS *dbprocess = NULL;

  // Test by trying to create a dbprocess
  NSLog(@"Check if valid connection dictionary\n");
  dbprocess = [self newDBProcess];
  if (dbprocess)
    {
      [self releaseDBProcess: dbprocess];
      return YES;
    }
  else
    return NO;
}

@end /* SQLServerAdaptor */
