/*
  Copyright (C) 2000-2005 SKYRIX Software AG

  This file is part of SOPE.

  SOPE 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 2, or (at your option) any
  later version.

  SOPE 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 SOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

#include "common.h"
#include "NGStack.h"
#include "NGMemoryAllocation.h"

@interface _NGConcreteStackEnumerator : NSEnumerator
{
  NGStack  *stack; // for retain
  id       *trace;
  unsigned toGo;
  BOOL     downWard; // top=>down
}

- (id)initWithStack:(NGStack *)_stack trace:(id *)_ptr count:(int)_size
  topDown:(BOOL)_downWard;

- (id)nextObject;

@end

@implementation NGStack

+ (id)stackWithCapacity:(unsigned int)_capacity {
  return [[[self alloc] initWithCapacity:_capacity] autorelease];
}
+ (id)stack {
  return [[[self alloc] init] autorelease];
}
+ (id)stackWithArray:(NSArray *)_array {
  return [[[self alloc] initWithArray:_array] autorelease];
}

- (id)init {
  return [self initWithCapacity:256];
}
- (id)initWithCapacity:(unsigned int)_capacity {
  if ((self = [super init])) {
    stackPointer = 0;
    capacity     = (_capacity > 0) ? _capacity : 16;

    stack = NGMalloc(sizeof(id) * capacity);
  }
  return self;
}
- (id)initWithArray:(NSArray *)_array {
  register unsigned int count = [_array count];

  if ((self = [self initWithCapacity:(count + 1)])) {
    unsigned cnt;

    for (cnt = 0; cnt < count; cnt++)
      [self push:[_array objectAtIndex:cnt]];
  }
  return self;
}

- (void)dealloc {
  if (self->stack) {
    [self clear];
    NGFree(self->stack);
  }
  [super dealloc];
}

/* sizing */

- (void)_increaseStack {
  if (capacity > 256) capacity += 256;
  else capacity *= 2;

  stack = NGRealloc(stack, sizeof(id) * capacity);
}

/* state */

- (unsigned int)capacity {
  return capacity;
}
- (unsigned int)stackPointer {
  return stackPointer;
}
- (unsigned int)count {
  return stackPointer;
}
- (BOOL)isEmpty {
  return (stackPointer == 0);
}

/* operations */

- (void)push:(id)_obj {
  stackPointer++;
  if (stackPointer >= capacity) [self _increaseStack];
  stack[stackPointer] = [_obj retain];
}

- (id)pop {
  id obj = stack[stackPointer];
  if (stackPointer <= 0) {
    [[[NGStackException alloc] initWithName:@"StackException"
      reason:@"tried to pop an object from an empty stack !"
      userInfo:nil] raise];
  }
  stack[stackPointer] = nil;
  stackPointer--;
  return [obj autorelease];
}

- (void)clear {
  unsigned cnt;
  for (cnt = 1; cnt <= stackPointer; cnt++) {
#if !LIB_FOUNDATION_BOEHM_GC
    [stack[cnt] release];
#endif
    stack[cnt] = nil;
  }
  stackPointer = 0;
}

/* elements */

- (id)elementAtTop {
  return (stackPointer == 0) ? nil : stack[stackPointer];
}
- (id)elementAtBottom {
  return (stackPointer == 0) ? nil : stack[1];
}

- (NSEnumerator *)topDownEnumerator {
  if (stackPointer == 0)
    return nil;

  return [[[_NGConcreteStackEnumerator alloc]
                        initWithStack:self trace:&(stack[stackPointer])
                        count:stackPointer topDown:YES] autorelease];
}
- (NSEnumerator *)bottomUpEnumerator {
  if (stackPointer == 0)
    return nil;

  return [[[_NGConcreteStackEnumerator alloc]
                        initWithStack:self trace:&(stack[1])
                        count:stackPointer topDown:NO] autorelease];
}

/* NSCoding */

- (Class)classForCoder {
  return [NGStack class];
}

- (void)encodeWithCoder:(NSCoder *)_encoder {
  unsigned cnt;
  
  [_encoder encodeValueOfObjCType:@encode(unsigned int) at:&capacity];
  [_encoder encodeValueOfObjCType:@encode(unsigned int) at:&stackPointer];

  for (cnt = 1; cnt <= stackPointer; cnt++) {
    id obj = stack[cnt];
    [_encoder encodeObject:obj];
  }
}

- (id)initWithCoder:(NSCoder *)_decoder {
  int tmpCapacity;
  int tmpStackPointer;

  [_decoder decodeValueOfObjCType:@encode(unsigned int) at:&tmpCapacity];
  [_decoder decodeValueOfObjCType:@encode(unsigned int) at:&tmpStackPointer];

  self = [self initWithCapacity:tmpCapacity];
  {
    register int cnt;

    for (cnt = 1; cnt <= tmpStackPointer; cnt++) {
      id obj = [_decoder decodeObject];
      stack[cnt] = [obj retain];
    }
    stackPointer = tmpStackPointer;
  }
  return self;
}

/* copying */

- (id)copyWithZone:(NSZone *)_zone {
  register NGStack *newStack = nil;
  register unsigned cnt;

  newStack = [[NGStack allocWithZone:(_zone ? _zone : NSDefaultMallocZone())]
                       initWithCapacity:[self stackPointer]];

  for (cnt = 1; cnt <= stackPointer; cnt++)
    [newStack push:stack[cnt]];

  return newStack;
}

/* description */

- (NSString *)description {
  return [NSString stringWithFormat:
                     @"<%@[0x%p] capacity=%u SP=%u count=%u content=%s>",
                     NSStringFromClass([self class]), self,
                     [self capacity], [self stackPointer], [self count],
                     [[[self toArray] description] cString]];
}

- (NSArray *)toArray {
  register NSMutableArray *array = nil;
  register unsigned cnt;

  array = [[NSMutableArray alloc] initWithCapacity:stackPointer];

  for (cnt = 1; cnt <= stackPointer; cnt++)
    [array addObject:stack[cnt]];

  return [array autorelease];
}

@end /* NGStack */

@implementation _NGConcreteStackEnumerator

- (id)initWithStack:(NGStack *)_stack trace:(id *)_ptr count:(int)_size
  topDown:(BOOL)_downWard {

  stack    = [_stack retain];
  trace    = _ptr;
  toGo     = _size;
  downWard = _downWard;

  return self;
}

- (void)dealloc {
  [self->stack release];
  trace = NULL;
  [super dealloc];
}

- (id)nextObject {
  id result = nil;
  
  if (toGo == 0)
    return nil;

  toGo--;

  result = *trace;
  
  if (downWard) trace--; // top=>bottom (downward)
  else          trace++; // bottom=>top (upward)

  return result;
}

@end /* NGStack */

@implementation NGStackException
@end /* NGStackException */

@implementation NSMutableArray(StackImp)

/* state */

- (unsigned int)stackPointer {
  return ([self count] - 1);
}

- (BOOL)isEmpty {
  return ([self count] == 0) ? YES : NO;
}

/* operations */

- (void)push:(id)_obj {
  [self addObject:_obj];
}
- (id)pop {
  unsigned lastIdx = ([self count] - 1);

  if (lastIdx >= 0) {
    id element = [self objectAtIndex:lastIdx];
    [self removeObjectAtIndex:lastIdx];
    return element;
  }
  else {
    [[[NGStackException alloc] initWithName:@"StackException"
        reason:@"tried to pop an object from an empty stack !"
        userInfo:nil] raise];
    return nil;
  }
}

- (void)clear {
  [self removeAllObjects];
}

/* elements */

- (id)elementAtTop {
  return [self lastObject];
}

- (NSEnumerator *)topDownEnumerator {
  return [self reverseObjectEnumerator];
}
- (NSEnumerator *)bottomUpEnumerator {
  return [self objectEnumerator];
}

@end /* NSMutableArray(NGStack) */

void __link_NGExtensions_NGStack() {
  __link_NGExtensions_NGStack();
}
