// [The "BSD licence"]
// Copyright (c) 2006-2007 Kay Roepke 2010 Alan Condit
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


#import "UnbufferedCommonTreeNodeStream.h"
#import "UnbufferedCommonTreeNodeStreamState.h"
#import "BaseTree.h"
#import "Token.h"

#define INITIAL_LOOKAHEAD_BUFFER_SIZE 5
@implementation ANTLRUnbufferedCommonTreeNodeStream

@synthesize root;
@synthesize currentNode;
@synthesize previousNode;
@synthesize treeAdaptor;
@synthesize tokenStream;
@synthesize nodeStack;
@synthesize indexStack;
@synthesize markers;
@synthesize lastMarker;
@synthesize currentChildIndex;
@synthesize absoluteNodeIndex;
@synthesize lookahead;
@synthesize head;
@synthesize tail;

- (id) initWithTree:(CommonTree *)theTree
{
	return [self initWithTree:theTree treeAdaptor:nil];
}

- (id) initWithTree:(CommonTree *)theTree treeAdaptor:(CommonTreeAdaptor *)theAdaptor
{
	if ((self = [super init]) != nil) {
		[self setRoot:theTree];
		if ( theAdaptor == nil ) 
			[self setTreeAdaptor:[CommonTreeAdaptor newTreeAdaptor]];
		else
			[self setTreeAdaptor:theAdaptor];
		nodeStack = [[NSMutableArray arrayWithCapacity:5] retain];
		indexStack = [[NSMutableArray arrayWithCapacity:5] retain];
		markers = [[PtrBuffer newPtrBufferWithLen:100] retain];
        // [markers insertObject:[NSNull null] atIndex:0];	// markers is one based - maybe fix this later
		lookahead = [NSMutableArray arrayWithCapacity:INITIAL_LOOKAHEAD_BUFFER_SIZE];	// lookahead is filled with [NSNull null] in -reset
        [lookahead retain];
		[self reset];
	}
	return self;
}

- (void) dealloc
{
	[self setRoot:nil];
	[self setTreeAdaptor:nil];
	
	[nodeStack release];	nodeStack = nil;
	[indexStack release];	indexStack = nil;
	[markers release];		markers = nil;
	[lookahead release];	lookahead = nil;
	
	[super dealloc];
}

- (void) reset
{
	currentNode = root;
	previousNode = nil;
	currentChildIndex = -1;
	absoluteNodeIndex = -1;
	head = tail = 0;
	[nodeStack removeAllObjects];
	[indexStack removeAllObjects];
	[markers removeAllObjects];
    // [markers insertObject:[NSNull null] atIndex:0];	// markers is one based - maybe fix this later
	[lookahead removeAllObjects];
	// TODO: this is not ideal, but works for now. optimize later
	int i;
	for (i = 0; i < INITIAL_LOOKAHEAD_BUFFER_SIZE; i++)
		[lookahead addObject:[NSNull null]];
}


#pragma mark ANTLRTreeNodeStream conformance

- (id) LT:(NSInteger)k
{
	if (k == -1)
		return previousNode;
	if (k < 0)
		@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-LT: looking back more than one node unsupported for unbuffered streams" userInfo:nil];
	if (k == 0)
		return BaseTree.INVALID_NODE;
	[self fillBufferWithLookahead:k];
	return [lookahead objectAtIndex:(head+k-1) % [lookahead count]];
}

- (id) treeSource
{
	return [self root];
}

- (id<TreeAdaptor>) getTreeAdaptor;
{
	return treeAdaptor;
}

- (void)setTreeAdaptor:(id<TreeAdaptor>)aTreeAdaptor
{
    if (treeAdaptor != aTreeAdaptor) {
        [aTreeAdaptor retain];
        [treeAdaptor release];
        treeAdaptor = aTreeAdaptor;
    }
}

- (id<TokenStream>) getTokenStream
{
	return tokenStream;
}

- (void) setTokenStream:(id<TokenStream>)aTokenStream
{
	if (tokenStream != aTokenStream) {
		[tokenStream release];
		[aTokenStream retain];
		tokenStream = aTokenStream;
	}
}

- (void) setUsesUniqueNavigationNodes:(BOOL)flag
{
	shouldUseUniqueNavigationNodes = flag;
}

- (id) nodeAtIndex:(NSUInteger) idx
{
	@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-nodeAtIndex: unsupported for unbuffered streams" userInfo:nil];
}

- (NSString *) toString
{
	@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-toString unsupported for unbuffered streams" userInfo:nil];
}

- (NSString *) toStringWithRange:(NSRange) aRange
{
	@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-toString: unsupported for unbuffered streams" userInfo:nil];
}

- (NSString *) toStringFromNode:(id)startNode ToNode:(id)stopNode
{
	@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-toStringFromNode:toNode: unsupported for unbuffered streams" userInfo:nil];
}

#pragma mark ANTLRIntStream conformance

- (void) consume
{
	[self fillBufferWithLookahead:1];
	absoluteNodeIndex++;
	previousNode = [lookahead objectAtIndex:head];
	head = (head+1) % [lookahead count];
}

- (NSInteger) LA:(NSUInteger) i
{
	CommonTree *node = [self LT:i];
	if (!node) 
		return TokenTypeInvalid;
	int ttype = [node getType];
	return ttype;
}

- (NSUInteger) mark
{
	ANTLRUnbufferedCommonTreeNodeStreamState *state = [[[ANTLRUnbufferedCommonTreeNodeStreamState alloc] init] retain];
	[state setCurrentNode:currentNode];
	[state setPreviousNode:previousNode];
	[state setIndexStackSize:[indexStack count]];
	[state setNodeStackSize:[nodeStack count]];
	[state setCurrentChildIndex:currentChildIndex];
	[state setAbsoluteNodeIndex:absoluteNodeIndex];
	unsigned int lookaheadSize = [self lookaheadSize];
	unsigned int k;
	for ( k = 0; k < lookaheadSize; k++) {
		[state addToLookahead:[self LT:k+1]];
	}
	[markers addObject:state];
	//[state release];
	return [markers count];
}

- (NSUInteger) getIndex
{
	return absoluteNodeIndex + 1;
}

- (void) rewind:(NSUInteger) marker
{
	if ( [markers count] < marker ) {
		return;
	}
	ANTLRUnbufferedCommonTreeNodeStreamState *state = [markers objectAtIndex:marker];
	[markers removeObjectAtIndex:marker];

	absoluteNodeIndex = [state absoluteNodeIndex];
	currentChildIndex = [state currentChildIndex];
	currentNode = [state currentNode];
	previousNode = [state previousNode];
	// drop node and index stacks back to old size
	[nodeStack removeObjectsInRange:NSMakeRange([state nodeStackSize], [nodeStack count]-[state nodeStackSize])];
	[indexStack removeObjectsInRange:NSMakeRange([state indexStackSize], [indexStack count]-[state indexStackSize])];
	
	head = tail = 0; // wack lookahead buffer and then refill
	[lookahead release];
	lookahead = [[NSMutableArray alloc] initWithArray:[state lookahead]];
	tail = [lookahead count];
	// make some room after the restored lookahead, so that the above line is not a bug ;)
	// this also ensures that a subsequent -addLookahead: will not immediately need to resize the buffer
	[lookahead addObjectsFromArray:[NSArray arrayWithObjects:[NSNull null], [NSNull null], [NSNull null], [NSNull null], [NSNull null], nil]];
}

- (void) rewind
{
	[self rewind:[markers count]];
}

- (void) release:(NSUInteger) marker
{
	@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-release: unsupported for unbuffered streams" userInfo:nil];
}

- (void) seek:(NSUInteger) anIndex
{
	if ( anIndex < (NSUInteger) index )
		@throw [NSException exceptionWithName:@"ANTLRTreeException" reason:@"-seek: backwards unsupported for unbuffered streams" userInfo:nil];
	while ( (NSUInteger) index < anIndex ) {
		[self consume];
	}
}

- (NSUInteger) size;
{
	return absoluteNodeIndex + 1;	// not entirely correct, but cheap.
}


#pragma mark Lookahead Handling
- (void) addLookahead:(id<BaseTree>)aNode
{
	[lookahead replaceObjectAtIndex:tail withObject:aNode];
	tail = (tail+1) % [lookahead count];
	
	if ( tail == head ) {
		NSMutableArray *newLookahead = [[[NSMutableArray alloc] initWithCapacity:[lookahead count]*2] retain];
		
		NSRange headRange = NSMakeRange(head, [lookahead count]-head);
		NSRange tailRange = NSMakeRange(0, tail);
		
		[newLookahead addObjectsFromArray:[lookahead objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:headRange]]];
		[newLookahead addObjectsFromArray:[lookahead objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:tailRange]]];
		
		unsigned int i;
		unsigned int lookaheadCount = [newLookahead count];
		for (i = 0; i < lookaheadCount; i++)
			[newLookahead addObject:[NSNull null]];
		[lookahead release];
		lookahead = newLookahead;
		
		head = 0;
		tail = lookaheadCount;	// tail is the location the _next_ lookahead node will end up in, not the last element's idx itself!
	}
	
}

- (NSUInteger) lookaheadSize
{
	return tail < head
		? ([lookahead count] - head + tail) 
		: (tail - head);
}

- (void) fillBufferWithLookahead:(NSInteger)k
{
	unsigned int n = [self lookaheadSize];
	unsigned int i;
	id lookaheadObject = self; // any valid object would do.
	for (i=1; i <= k-n && lookaheadObject != nil; i++) {
		lookaheadObject = [self nextObject];
	}
}

- (id) nextObject
{
	// NOTE: this could/should go into an NSEnumerator subclass for treenode streams.
	if (currentNode == nil) {
        if ( navigationNodeEOF == nil ) {
            navigationNodeEOF = [[TreeNavigationNodeEOF alloc] init];
        }
		[self addLookahead:navigationNodeEOF];
		return nil;
	}
	if (currentChildIndex == -1) {
		return [self handleRootNode];
	}
	if (currentChildIndex < (NSInteger)[currentNode getChildCount]) {
		return [self visitChild:currentChildIndex];
	}
	[self walkBackToMostRecentNodeWithUnvisitedChildren];
	if (currentNode != nil) {
		return [self visitChild:currentChildIndex];
	}
	
	return nil;
}	

#pragma mark Node visiting
- (CommonTree *) handleRootNode
{
	CommonTree *node = currentNode;
	currentChildIndex = 0;
	if ([node isNil]) {
		node = [self visitChild:currentChildIndex];
	} else {
		[self addLookahead:node];
		if ([currentNode getChildCount] == 0) {
			currentNode = nil;
		}
	}
	return node;
}

- (CommonTree *) visitChild:(NSInteger)childNumber
{
	CommonTree *node = nil;
	
	[nodeStack addObject:currentNode];
	[indexStack addObject:[NSNumber numberWithInt:childNumber]];
	if (childNumber == 0 && ![currentNode isNil])
		[self addNavigationNodeWithType:TokenTypeDOWN];

	currentNode = [currentNode getChild:childNumber];
	currentChildIndex = 0;
	node = currentNode;  // record node to return
	[self addLookahead:node];
	[self walkBackToMostRecentNodeWithUnvisitedChildren];
	return node;
}

- (void) walkBackToMostRecentNodeWithUnvisitedChildren
{
	while (currentNode != nil && currentChildIndex >= (NSInteger)[currentNode getChildCount])
	{
		currentNode = (CommonTree *)[nodeStack lastObject];
		[nodeStack removeLastObject];
		currentChildIndex = [(NSNumber *)[indexStack lastObject] intValue];
		[indexStack removeLastObject];
		currentChildIndex++; // move to next child
		if (currentChildIndex >= (NSInteger)[currentNode getChildCount]) {
			if (![currentNode isNil]) {
				[self addNavigationNodeWithType:TokenTypeUP];
			}
			if (currentNode == root) { // we done yet?
				currentNode = nil;
			}
		}
	}
	
}

- (void) addNavigationNodeWithType:(NSInteger)tokenType
{
	// TODO: this currently ignores shouldUseUniqueNavigationNodes.
	switch (tokenType) {
		case TokenTypeDOWN: {
            if (navigationNodeDown == nil) {
                navigationNodeDown = [[TreeNavigationNodeDown alloc] init];
            }
			[self addLookahead:navigationNodeDown];
			break;
		}
		case TokenTypeUP: {
            if (navigationNodeUp == nil) {
                navigationNodeUp = [[TreeNavigationNodeUp alloc] init];
            }
			[self addLookahead:navigationNodeUp];
			break;
		}
	}
}

#pragma mark Accessors
- (CommonTree *) root
{
    return root; 
}

- (void) setRoot: (CommonTree *) aRoot
{
    if (root != aRoot) {
        [aRoot retain];
        [root release];
        root = aRoot;
    }
}

@end

