/** 
   Copyright (C) 2004 Free Software Foundation, Inc.
   
   Written by:  Richard Frith-Macdonald <rfm@gnu.org>
   Date:	April 2004
   
   This file is part of the SQLClient Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 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
   Lesser General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.

   $Date$ $Revision$
   */ 

#import	<Foundation/Foundation.h>
#import	<Performance/GSCache.h>
#import	"SQLClient.h"

#ifndef PGPORT
#define PGPORT
#endif

@interface	Logger : NSObject
- (void) notified: (NSNotification*)n;
@end

@implementation	Logger
- (void) notified: (NSNotification*)n
{
  NSLog(@"Received %@", n);
}
@end

int
main()
{
  NSAutoreleasePool	*pool = [NSAutoreleasePool new];
  SQLClientPool         *sp;
  SQLClient		*db;
  NSUserDefaults	*defs;
  NSMutableArray	*records;
  SQLRecord		*record;
  unsigned char		dbuf[256];
  unsigned int		i;
  NSData		*data;
  NSString		*name;
  Logger		*l;

  defs = [NSUserDefaults standardUserDefaults];
  [defs registerDefaults:
    [NSDictionary dictionaryWithObjectsAndKeys:
      [NSDictionary dictionaryWithObjectsAndKeys:
	[NSDictionary dictionaryWithObjectsAndKeys:
	  @"template1@localhost" PGPORT, @"Database",
	  @"postgres", @"User",
	  @"postgres", @"Password",
	  @"Postgres", @"ServerType",
	  nil],
	@"test",
	nil],
      @"SQLClientReferences",
      nil]
    ];

  sp = [[[SQLClientPool alloc] initWithConfiguration: nil
                                                name: @"test"
                                                 max: 2
                                                 min: 1] autorelease];
#if 0
{
  NSAutoreleasePool     *p;
  NSAutoreleasePool     *q;
  SQLClient             *c0;
  SQLClient             *c1;

  [sp setDebugging: 4];
  p = [NSAutoreleasePool new];
  c0 = [sp provideClientExclusive];
  c1 = [sp provideClientExclusive];
  NSLog(@"Got two clients from pool");
  [c0 connect];
  [c1 connect];
  NSLog(@"Now putting clients back in pool again");
  [sp swallowClient: c0];
  [sp swallowClient: c1];
  NSLog(@"And emptying autorelease pool");
  [p release];
  p = [NSAutoreleasePool new];
  [sp setPurgeAll: 60 min: 1];
  [NSThread sleepForTimeInterval: 1.0];
  NSLog(@"Expecting purge to disconnect one client");
  [sp purge];
  c0 = [sp provideClientExclusive];
  c1 = [sp provideClientExclusive];
  NSLog(@"Expecting connected: %@", [c0 connected] ? @"YES" : @"NO");
  NSLog(@"Expecting not connected: %@", [c1 connected] ? @"NO" : @"YES");

  NSLog(@"Pool has provided both it's clients ... now try for another with a 15 second timeout");
  [sp provideClientBeforeDate: [NSDate dateWithTimeIntervalSinceNow: 15.0]
                    exclusive: YES];
  NSLog(@"Emptying autorelease pool ... clients should be put back in pool");
  [p release];
  p = [NSAutoreleasePool new];
  NSLog(@"Getting two clients again");
  c0 = [sp provideClientExclusive];
  c1 = [sp provideClientExclusive];
  NSLog(@"Pool has provided both it's clients ... now try for another with a 15 second timeout");
  [sp provideClientBeforeDate: [NSDate dateWithTimeIntervalSinceNow: 15.0]
                    exclusive: YES];
  NSLog(@"Emptying autorelease pool again");
  [p release];
  p = [NSAutoreleasePool new];
  c0 = [sp provideClient];
  q = [NSAutoreleasePool new];
  c1 = [sp provideClient];
  if (c0 != c1)
    {
      NSLog(@"ERROR was expecting provideClient to give the same object");
      exit(1);
    }
  [q release];
  [c0 connect]; 
  [p release];

  NSLog(@"Expect to get client immediately");
}
#endif
  db = [sp provideClientExclusive];
  [sp swallowClient: db];

  [sp queryString: @"SELECT CURRENT_TIMESTAMP", nil];
  db = [sp provideClientExclusive];

  l = [Logger new];
  [[NSNotificationCenter defaultCenter] addObserver: l
	selector: @selector(notified:)
	name: SQLClientDidConnectNotification
	object: db];
  [[NSNotificationCenter defaultCenter] addObserver: l
	selector: @selector(notified:)
	name: SQLClientDidDisconnectNotification
	object: db];

  if ((name = [defs stringForKey: @"Producer"]) != nil)
    {
      NS_DURING
	{
	  [db execute: @"CREATE TABLE Queue ( "
	    @"ID SERIAL, "
	    @"Consumer CHAR(40) NOT NULL, "
	    @"ServiceID INT NOT NULL, "
	    @"Status CHAR(1) DEFAULT 'Q' NOT NULL, "
	    @"Delivery TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, "
	    @"Reference CHAR(128), "
	    @"Destination CHAR(15) NOT NULL, "
	    @"Payload CHAR(250) DEFAULT '' NOT NULL,"
	    @")",
	    nil];
	  [db execute:
	    @"CREATE UNIQUE INDEX QueueIDX ON Queue (ID)",
	    nil];
	  [db execute:
	    @"CREATE INDEX ServiceIDX ON Queue (ServiceID)",
	    nil];
	  [db execute:
	    @"CREATE INDEX ConsumerIDX ON Queue (Consumer,Status,Delivery)",
	    nil];
	  [db execute:
	    @"CREATE INDEX ReferenceIDX ON Queue (Reference,Consumer)",
	    nil];
	}
      NS_HANDLER
	{
	  NSLog(@"%@", localException);
	}
      NS_ENDHANDLER
      NSLog(@"Start producing");
      for (i = 0; i < 100000; i++)
	{
	  NSAutoreleasePool	*arp = [NSAutoreleasePool new];
	  NSString	*destination = [NSString stringWithFormat: @"%d", i];
	  NSString	*sid = [NSString stringWithFormat: @"%d", i%100];

          if (i % 1000 == 999)
            {
              [db postNotificationName: @"Producing"
                               payload: [NSString stringWithFormat: @"%d", i]];
            }
	  [db execute: @"INSERT INTO Queue (Consumer, Destination,"
            @" ServiceID, Payload) VALUES (",
	    [db quote: name], @", ",
            [db quote: destination], @", ",
            sid, @", ",
	    @"'helo there'",
            @")", nil];
	  [arp release];
	}
      NSLog(@"End producing");
    }
  else if ((name = [defs stringForKey: @"Consumer"]) != nil)
    {
      [db addObserver: l 
             selector: @selector(notified:)
                 name: @"Producing"];
      NSLog(@"Start consuming");
      for (i = 0; i < 100000;)
	{
	  NSAutoreleasePool	*arp = [NSAutoreleasePool new];
	  unsigned		count;
	  int			j;

	  [db begin];
	  records = [db query: @"SELECT * FROM Queue WHERE Consumer = ",
	    [db quote: name],
	    @" AND Status = 'Q' AND Delivery < CURRENT_TIMESTAMP",
	    @" ORDER BY Delivery LIMIT 1000 FOR UPDATE"  , nil];
	  count = [records count];
	  if (count == 0)
	    {
	      [db commit];
	      [NSThread sleepForTimeInterval: 1.0];
	      [db begin];
	      records = [db query: @"SELECT * FROM Queue WHERE Consumer = ",
		[db quote: name],
		@" AND Status = 'Q' AND Delivery < CURRENT_TIMESTAMP",
		@" ORDER BY Delivery LIMIT 50 FOR UPDATE"  , nil];
	      count = [records count];
	      if (count == 0)
		{
		  break;
		}
	    }

	  for (j = 0; j < count; j++)
	    {
	      SQLRecord	*record = [records objectAtIndex: j];
	      NSString	*reference = [record objectForKey: @"ID"];

	      [db execute: @"UPDATE Queue SET Status = 'S', Reference = ",
		[db quote: reference], @" WHERE ID = ",
		[record objectForKey: @"ID"], nil];

	      [db execute: @"UPDATE Queue SET Status = 'D'",
		@" WHERE Consumer = ", [db quote: name],
		@" AND Reference = ", [db quote: reference],
		nil];
	    }
	  [db commit];
	  i += count;
	  [arp release];
	}
      NSLog(@"End consuming (%d records)", i);
/*
      [db execute: @"DROP INDEX ReferenceIDX", nil];
      [db execute: @"DROP INDEX ServiceIDX", nil];
      [db execute: @"DROP INDEX ConsumerIDX", nil];
      [db execute: @"DROP INDEX QueueIDX", nil];
      [db execute: @"DROP TABLE Queue", nil];
*/
    }
  else
    {
      NSString	*oddChars;
      NSString	*oddNoNul;
      NSString	*nonLatin;
      id	e1, e2, e3, e4, e5;
      id        d;
      id	d0;
      id	d1;
      id	d2;
      id	r0;
      id	r1;

      oddChars = @"'a\\b'c\0\r\nd'\\ed\\";
      oddNoNul = @"'a\\b'c\r\nd'\\ed\\";
      nonLatin = [[NSString stringWithCString: "\"\\U2A11\""] propertyList];
      for (i = 0; i < 256; i++)
	{
	  dbuf[i] = i;
	}
      data = [NSData dataWithBytes: dbuf length: i];

      NS_DURING
      [db execute: @"drop table xxx", nil];
      NS_HANDLER
      NS_ENDHANDLER

      [db setDurationLogging: 0];

      [db begin];
      [db execute: @"create table xxx ( "
	@"id int, "
	@"k char(40), "
	@"char1 char(1), "
	@"boolval BOOL, "
	@"intval int, "
	@"when1 timestamp with time zone, "
	@"when2 timestamp, "
	@"b bytea,"
        @"extra1 int[],"
        @"extra2 varchar[],"
        @"extra3 bytea[],"
        @"extra4 boolean[],"
        @"extra5 timestamp with time zone[]"
	@")",
	nil];

      if (1 != [db execute: @"INSERT into xxx (id, k, char1, boolval, intval,"
        @" when1, when2, b, extra1, extra2, extra3, extra4, extra5) "
	@"values (1,"
	@"'{hello', "
	@"'X', "
	@"TRUE, "
	@"1, "
	@"CURRENT_TIMESTAMP, "
	@"CURRENT_TIMESTAMP, ",
	data, @", ",
        [db quoteArray:
          (e1 = [NSArray arrayWithObjects: @"1", @"2", [NSNull null], nil])
              toString: nil
        quotingStrings: NO], @", ",
        [db quoteArray:
          (e2 = [NSArray arrayWithObjects: @"on,e", @"t'wo", @"many", nil])
              toString: nil
        quotingStrings: YES], @", ",
        [db quoteArray:
          (e3 = [NSArray arrayWithObjects: data, nil])
              toString: nil
        quotingStrings: YES], @", ",
        [db quoteArray:
          (e4  =[NSArray arrayWithObjects: @"TRUE", @"FALSE", nil])
              toString: nil
        quotingStrings: NO], @", ",
        [db quoteArray:
          (e5 = [NSArray arrayWithObjects: [NSDate date], nil])
              toString: nil
        quotingStrings: YES], @")",
	nil])
        {
          NSLog(@"Insert failed to return row count");
        }

[db setDebugging: 0];
[db query: @"select * from xxx", nil];
[db setDebugging: 0];

      [db execute: @"INSERT into xxx "
	@"(id, k, char1, boolval, intval, when1, when2, b) "
	@"values (2,"
	@"'hello', "
	@"'X', "
	@"TRUE, "
	@"1, ",
	[NSDate date], @", ",
	[NSDate date], @", ",
	[NSData dataWithBytes: "" length: 0],
	@")",
	nil];
      [db execute: @"INSERT into xxx "
	@"(id, k, char1, boolval, intval, when1, when2, b) "
	@"values (3,",
	[db quote: oddChars],
	@", ",
	[db quote: nonLatin],
	@",TRUE, "
	@"1, ",
	[NSDate date], @", ",
	[NSDate date], @", ",
	[NSData dataWithBytes: "" length: 0],
	@")",
	nil];
      [db commit];

      r0 = [db cache: 1 query: @"select * from xxx order by id", nil];
      r1 = [db cache: 1 query: @"select * from xxx order by id", nil];
      NSCAssert([r0 lastObject] == [r1 lastObject], @"Cache failed");
      [NSThread sleepForTimeInterval: 2.0];
      records = [db cache: 1 query: @"select * from xxx order by id", nil];
      NSCAssert([r0 lastObject] != [records lastObject], @"Lifetime failed");

      db = [[[SQLClient alloc] initWithConfiguration: nil
                                                name: @"test"] autorelease];
      [db addObserver: l 
             selector: @selector(notified:)
                 name: @"foo"];

      [db postNotificationName: @"foo" payload: @"hello"];

      [db execute: @"drop table xxx", nil];

      if ([records count] != 3)
	{
	  NSLog(@"Expected 3 records but got %lu", [records count]);
	}
      else
	{
          int   i;

	  record = [records objectAtIndex: 0];
	  if ([[record objectForKey: @"b"] isEqual: data] == NO)
	    {
	      NSLog(@"Retrieved data does not match saved data %@ %@",
		data, [record objectForKey: @"b"]);
	    }
	  record = [records objectAtIndex: 1];
	  if ([[record objectForKey: @"b"] isEqual: [NSData data]] == NO)
	    {
	      NSLog(@"Retrieved empty data does not match saved data");
	    }
	  record = [records objectAtIndex: 2];
	  if ([[record objectForKey: @"char1"] isEqual: nonLatin] == NO)
	    {
	      NSLog(@"Retrieved non-latin does not match saved string");
	    }
          id o = [[record objectForKey: @"k"] stringByTrimmingSpaces];
	  if ([o isEqual: oddNoNul] == NO)
	    {
	      NSLog(@"Retrieved odd chars (%@) does not match oddNoNul (%@)",
                o, oddNoNul);
	    }
          else
            {
              NSLog(@"Embedded nul correctly removed");
            }
	  record = [records objectAtIndex: 0];
          o = [record objectForKey: @"extra1"];
	  if ([o isEqual: e1] == NO)
	    {
	      NSLog(@"Retrieved extra1 (%@) does not match saved (%@)", o, e1);
	    }
          o = [record objectForKey: @"extra2"];
	  if ([o isEqual: e2] == NO)
	    {
	      NSLog(@"Retrieved extra2 (%@) does not match saved (%@)", o, e2);
	    }
          o = [record objectForKey: @"extra3"];
	  if ([o isEqual: e3] == NO)
	    {
	      NSLog(@"Retrieved extra3 (%@) does not match saved (%@)", o, e3);
	    }
          o = [record objectForKey: @"extra4"];
	  if ([o count] != [e4 count])
	    {
	      NSLog(@"Retrieved extra4 (%@) does not match saved (%@)", o, e4);
	    }
	  for (i = 0; i < [o count]; i++)
	    {
	      if ([[o objectAtIndex: i] boolValue]
	        != [[e4 objectAtIndex: i] boolValue])
		{
		  NSLog(@"Retrieved extra4 (%@) does not match saved (%@)",
		    o, e4);
		}
	    }
          o = [record objectForKey: @"extra5"];
	  if ([o count] != [e5 count])
	    {
	      NSLog(@"Retrieved extra5 (%@) does not match saved (%@)", o, e5);
	    }
	  for (i = 0; i < [o count]; i++)
	    {
	      if (floor([[o objectAtIndex: i] timeIntervalSinceReferenceDate])
	        != floor([[e5 objectAtIndex: i] timeIntervalSinceReferenceDate]))
		{
		  NSLog(@"Retrieved extra5 (%@) does not match saved (%@)",
		    o, e5);
		}
	    }
	}

      NSLog(@"Records - %@", [GSCache class]);

      d = [NSCalendarDate date];
      [d setTimeZone: [NSTimeZone timeZoneForSecondsFromGMT: 240]];
      [db begin];
      [db execute: @"create table xxx ( "
	@"id int, "
	@"when0 timestamp with time zone, "
	@"when1 timestamp with time zone, "
	@"when2 timestamp)",
	nil];
      [db execute: @"INSERT into xxx (id, when0, when1, when2) "
	@"values (99,",
	d, @", ",
	[NSDate distantPast], @", ",
	[NSDate distantFuture], @")",
	nil];
      r0 = [[db query: @"select * from xxx where id=99", nil] lastObject];
      [db execute: @"drop table xxx", nil];
      [db commit];

      d0 = [r0 objectForKey:@"when0"];
      NSCAssert(floor([d0 timeIntervalSinceReferenceDate])
        == floor([d timeIntervalSinceReferenceDate]),
        NSInternalInconsistencyException);
      d1 = [r0 objectForKey:@"when1"];
      NSCAssert([d1 timeIntervalSinceReferenceDate]
        == [[NSDate distantPast] timeIntervalSinceReferenceDate],
        NSInternalInconsistencyException);
      d2 = [r0 objectForKey:@"when2"];
      NSCAssert([d2 timeIntervalSinceReferenceDate]
        == [[NSDate distantFuture] timeIntervalSinceReferenceDate],
        NSInternalInconsistencyException);
    }

  NSLog(@"Pool stats:\n%@", [sp statistics]);

  [pool release];
  return 0;
}

