/**
   Copyright (C) 2010 Free Software Foundation, Inc.

   Written by:  Richard Frith-Macdonald <rfm@gnu.org>
   Date:        September 2010

   This file is part of the Performance 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.
   */

#import <Foundation/NSArray.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSTimer.h>
#import <Foundation/NSException.h>
#import <Foundation/NSUserDefaults.h>
#import	"GSIOThreadPool.h"

/* Protect changes to a thread's counter
 */
static NSRecursiveLock   *classLock = nil;

@interface	GSIOThread (Private)
- (NSUInteger) _count;
- (void) _finish: (NSTimer*)t;
- (void) _setCount: (NSUInteger)c;
@end

@implementation	GSIOThread (Private)

+ (void) initialize
{
  if (nil == classLock)
    {
      classLock = [NSRecursiveLock new];
    }
}

- (NSUInteger) _count
{
  return _count;
}

/* Force termination of this thread.
 */
- (void) _finish: (NSTimer*)t
{
  _timer = nil;
  [self shutdown];
  [NSThread exit];
}

- (void) _setCount: (NSUInteger)c
{
  if (NSNotFound != _count)
    {
      _count = c;
    }
}

@end

@implementation	GSIOThread

/* Run the thread's main runloop until terminated.
 */
- (void) main
{
  NSAutoreleasePool     *pool = [NSAutoreleasePool new];
  NSDate		*when = [NSDate distantFuture];
  NSTimeInterval	delay = [when timeIntervalSinceNow];

  [self startup];
  _timer = [NSTimer scheduledTimerWithTimeInterval: delay
					    target: self
					  selector: @selector(_finish:)
					  userInfo: nil
					   repeats: NO];
  [[NSRunLoop currentRunLoop] run];
  [pool release];
}

- (void) shutdown
{
  return;
}

- (void) startup
{
  return;
}

/* End execution of the thread by the specified date.
 */
- (void) terminate: (NSDate*)when
{
  if (YES == [self isFinished])
    {
      return;
    }

  if (NO == [when isKindOfClass: [NSDate class]])
    {
      when = [NSDate date];
    }
  [self retain];
  if ([NSThread currentThread] == self)
    {
      NSTimeInterval	delay = [when timeIntervalSinceNow];

      [_timer invalidate];

      [classLock lock];
      if (0 == _count || delay <= 0.0)
        {
          _count = NSNotFound;      // Mark as terminating
          _timer = nil;
          delay = 0.0;
        }
      [classLock unlock];

      if (delay > 0.0)
        {
          _timer = [NSTimer scheduledTimerWithTimeInterval: delay
                                                    target: self
                                                  selector: @selector(_finish:)
                                                  userInfo: nil
                                                   repeats: NO];
        }
      else
        {
          [self _finish: nil];
        }
    }
  else if ([self isExecuting])
    {
      NSRunLoop *loop;

      /* We need to execute -terminate: in the thread itself,
       * and wait until either the thread actually finishes or
       * until our time limit expires, whichever comes first.
       */
      [self performSelector: _cmd
                   onThread: self
                 withObject: when
              waitUntilDone: NO];

      loop = [NSRunLoop currentRunLoop];
      while ([self isExecuting] && [when timeIntervalSinceNow] > 0.0)
        {
          NSDate        *d;

          d = [[NSDate alloc] initWithTimeIntervalSinceNow: 0.01];

          NS_DURING
            {
              [loop runUntilDate: d];
            }
          NS_HANDLER
            {
              NSLog(@"Problem waiting for thread termination: %@",
                localException);
            }
          NS_ENDHANDLER
          [d release];
        }
    }
  [self release];
}
@end


@implementation	GSIOThreadPool

static GSIOThreadPool	*shared = nil;

/* Return the thread with the lowest usage.
 */
static GSIOThread *
best(NSMutableArray *a)
{
  NSUInteger	c = [a count];
  NSUInteger	l = NSNotFound;
  GSIOThread	*t = nil;

  while (c-- > 0)
    {
      GSIOThread	*o = [a objectAtIndex: c];

      if ([o isExecuting])
        {
          NSUInteger    i;

          if ((i = [o _count]) < l)
            {
              t = o;
              l = i;
            }
        }
      else if ([o isCancelled] || [o isFinished])
        {
          [a removeObjectAtIndex: c];
        }
    }
  return t;
}

+ (void) initialize
{
  if ([GSIOThreadPool class] == self && nil == shared)
    {
      NSInteger size;

      [GSIOThread class];
      size = [[NSUserDefaults standardUserDefaults]
        integerForKey: @"GSIOThreadPoolSize"];
      if (size < 0)
        {
          size = 0;
        }
      shared = [self new];
      [shared setThreads: size];
    }
}

+ (GSIOThreadPool*) sharedPool
{
  return shared;
}

- (NSThread*) acquireThread
{
  GSIOThread	*t;
  NSUInteger    c;

  if (0 == maxThreads)
    {
      return [NSThread mainThread];
    }

  [classLock lock];
  t = best(threads);
  if (nil == t || ((c = [t _count]) > 0 && [threads count] < maxThreads))
    {
      NSString	*n;

      t = [threadClass new];
      if (nil == (n = poolName))
	{
	  n = @"GSIOThreadPool";
	}
      n = [NSString stringWithFormat: @"%@-%u", n, ++created];
      [t setName: n];
      [threads addObject: t];
      [t release];
      [t start];
      c = 0;
    }
  [t _setCount: c + 1];
  [classLock unlock];
  return t;
}

- (NSUInteger) countForThread: (NSThread*)aThread
{
  NSUInteger	count = 0;

  [classLock lock];
  if ([threads indexOfObjectIdenticalTo: aThread] != NSNotFound)
    {
      count = [((GSIOThread*)aThread) _count];
    }
  [classLock unlock];
  return count;
}

- (void) dealloc
{
#if defined(GNUSTEP) || (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
  GSIOThread	*thread;
  NSDate	*when = [NSDate dateWithTimeIntervalSinceNow: timeout];

  [classLock lock];
  while ((thread = [threads lastObject]) != nil)
    {
      [thread performSelector: @selector(terminate:)
		     onThread: thread
		   withObject: when
		waitUntilDone: NO];
      [threads removeObjectIdenticalTo: thread];
    }
  [threads release];
  [classLock unlock];
#endif
  DESTROY(poolName);
  [super dealloc];
}

- (id) init
{
#if defined(GNUSTEP) || (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
  if ((self = [super init]) != nil)
    {
      threads = [NSMutableArray new];
      threadClass = [GSIOThread class];
    }
#else
  [self release];
  NSLog(@"WARNING, your OSX system is too old to use GSIOThreadPool");
  return nil;
#endif
  return self;
}

- (NSUInteger) maxThreads
{
  return maxThreads;
}

- (NSString*) poolName
{
  NSString	*n;

  [classLock lock];
  n = RETAIN(poolName);
  [classLock unlock];
  return AUTORELEASE(n);
}

- (void) setPoolName: (NSString*)aName
{
  NSString      *s = nil;

  if (aName)
    {
      s = AUTORELEASE([aName copy]);
      NSAssert([s isKindOfClass: [NSString class]], NSInvalidArgumentException);
    }
  [classLock lock];
  ASSIGN(poolName, s);
  [classLock unlock];
}

- (void) setThreads: (NSUInteger)max
{
  maxThreads = max;
}

- (void) setThreadClass: (Class)aClass
{
  if (NO == [aClass isSubclassOfClass: [GSIOThread class]])
    {
      [NSException raise: NSInvalidArgumentException
                  format: @"Thread class must be a subclass of GSIOThread"];
    }
  threadClass = aClass;
}

- (void) setTimeout: (NSTimeInterval)t
{
  timeout = t;
}

- (NSTimeInterval) timeout
{
  return timeout;
}

- (void) unacquireThread: (NSThread*)aThread
{
  [classLock lock];
  if ([threads indexOfObjectIdenticalTo: aThread] != NSNotFound)
    {
      NSUInteger        c;

      c = [((GSIOThread*)aThread) _count];
      if (0 == c)
	{
          [classLock unlock];
	  [NSException raise: NSInternalInconsistencyException
		      format: @"-unacquireThread: called too many times"];
	}
      [((GSIOThread*)aThread) _setCount: --c];
      if (0 == c && [threads count] > maxThreads)
        {
          [aThread retain];
          [threads removeObjectIdenticalTo: aThread];
          [aThread performSelector: @selector(terminate:)
                          onThread: aThread
                        withObject: [NSDate date]
                     waitUntilDone: NO];
          [aThread release];
        }
    }
  [classLock unlock];
}

@end

