/* 
   Copyright (C) 1996 
	Ovidiu Predescu <ovidiu@bx.logicnet.ro>
	Mircea Oancea <mircea@jupiter.elcom.pub.ro>

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of the FoundationExtensions 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 <ctype.h>

#include "common.h"

#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSCharacterSet.h>

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

@implementation FormatScanner

enum { SPECIFIER_SIZE = 1000 }; /* This value should be sufficient */

- init
{
    currentSpecifier = Malloc(specifierSize = SPECIFIER_SIZE);
    boolFlags.allowFlags = YES;
    boolFlags.allowWidth = YES;
    boolFlags.allowPeriod = YES;
    boolFlags.allowPrecision = YES;
    boolFlags.allowModifier = YES;
    return self;
}

- (void)dealloc
{
    Free(currentSpecifier);
    [handler release];
    [super dealloc];
}

- (void)setFormatScannerHandler:(id)anObject
{
    [anObject retain];
    [handler release];
    handler = anObject;
}

- setAllowOnlySpecifier:(BOOL)flag
{
    boolFlags.allowFlags = !flag;
    boolFlags.allowWidth = !flag;
    boolFlags.allowPeriod = !flag;
    boolFlags.allowPrecision = !flag;
    boolFlags.allowModifier = !flag;
    return self;
}

- setAllowFlags:(BOOL)flag	{ boolFlags.allowFlags = flag; return self; }
- setAllowWidth:(BOOL)flag	{ boolFlags.allowWidth = flag; return self; }
- setAllowPeriod:(BOOL)flag	{ boolFlags.allowPeriod = flag; return self; }
- setAllowPrecision:(BOOL)flag { boolFlags.allowPrecision = flag; return self;}
- setAllowModifier:(BOOL)flag  { boolFlags.allowModifier = flag; return self; }

- (id)formatScannerHandler	{ return handler; }
- (unsigned int)flags		{ return flags; }
- (int)width			{ return width; }
- (int)precision		{ return precision; }
- (char)modifier		{ return modifier; }
- (char)characterSpecifier	{ return characterSpecifier; }
- (const char*)currentSpecifier	{ return currentSpecifier; }
- (BOOL)allowFlags		{ return boolFlags.allowFlags; }
- (BOOL)allowWidth		{ return boolFlags.allowWidth; }
- (BOOL)allowPeriod		{ return boolFlags.allowPeriod; }
- (BOOL)allowPrecision		{ return boolFlags.allowPrecision; }
- (BOOL)allowModifier		{ return boolFlags.allowModifier; }

#define CHECK_END \
    if(i >= length) { \
	/* An unterminated specifier. Break the loop. */ \
	[self handleOrdinaryString: \
		    [NSString stringWithCString:currentSpecifier]]; \
	goto _return; \
    }

/* Scans the format string looking after specifiers. Doesn't handle '*' as a
   valid width or precision. */
- (BOOL)parseFormatString:(NSString*)format context:(void*)context
{
    NSRange searchRange, foundRange;
    int i = 0, length = [format length];
    unichar ch;
    NSCharacterSet* decimals = [NSCharacterSet decimalDigitCharacterSet];

    *currentSpecifier = 0;
    specifierLen = 0;

    while(i < length) {
	searchRange.location = i;
	searchRange.length = length - i;

	foundRange = [format rangeOfString:@"%" options:0 range:searchRange];
	if (foundRange.length == 0)
	    foundRange.location = length;
	searchRange.length = foundRange.location - searchRange.location;

	if (searchRange.length) {
	    if (![self handleOrdinaryString:
			[format substringWithRange:searchRange]])
		return NO;
	}

	i = foundRange.location;
	CHECK_END

	i++;
	strcpy(currentSpecifier, "%");
	specifierLen = 1;
	CHECK_END

	flags = width = precision = modifier = characterSpecifier = 0;

	/* Check for flags. */
	if(boolFlags.allowFlags) {
	    for(; i < length; i++) {
		ch = [format characterAtIndex:i];
		switch(ch) {
		    case '#':	strcat(currentSpecifier, "#");
				flags |= FS_ALTERNATE_FORM;
				break;
		    case '0':	strcat(currentSpecifier, "0");
				flags |= FS_ZERO;
				break;
		    case '-':	strcat(currentSpecifier, "-");
				flags |= FS_MINUS_SIGN;
				break;
		    case '+':	strcat(currentSpecifier, "+");
				flags |= FS_PLUS_SIGN;
				break;
		    case ' ':	strcat(currentSpecifier, " ");
				flags |= FS_BLANK;
				break;
		    default:	goto quit;
		}
		if(++specifierLen == specifierSize)
		    currentSpecifier = Realloc(currentSpecifier,
					    specifierSize += SPECIFIER_SIZE);
	    }
	quit:
	    CHECK_END
	}

	/* Check for width. */
	if(boolFlags.allowWidth) {
	    for(; i < length; i++) {
		char str[2] = { 0, 0 };

		ch = [format characterAtIndex:i];
		if (![decimals characterIsMember:ch])
		    break;
		str[0] = ch;

		strcat(currentSpecifier, str);
		if(++specifierLen == specifierSize)
		    currentSpecifier = Realloc(currentSpecifier,
					    specifierSize += SPECIFIER_SIZE);

		width = 10 * width + (ch - '0');
	    }
	    CHECK_END
	}

	/* Check for period. */
	if(boolFlags.allowPeriod) {
	    ch = [format characterAtIndex:i];
	    if(ch == '.') {
		char str[2] = { ch, 0 };

		strcat(currentSpecifier, str);
		if(++specifierLen == specifierSize)
		    currentSpecifier = Realloc(currentSpecifier,
					    specifierSize += SPECIFIER_SIZE);

		i++;
		CHECK_END
	    }
	}

	/* Check for precision. */
	if(boolFlags.allowPrecision) {
	    for(; i < length; i++) {
		char str[2] = { 0, 0 };

		ch = [format characterAtIndex:i];
		if (![decimals characterIsMember:ch])
		    break;
		str[0] = ch;

		strcat(currentSpecifier, str);
		if(++specifierLen == specifierSize)
		    currentSpecifier = Realloc(currentSpecifier,
					    specifierSize += SPECIFIER_SIZE);

		precision = 10 * precision + (ch - '0');
	    }
	    CHECK_END
	}

	/* Check for data-width modifier. */
	if(boolFlags.allowModifier) {
	    ch = [format characterAtIndex:i];
	    if(ch == 'h' || ch == 'l') {
		char str[2] = { ch, 0 };

		strcat(currentSpecifier, str);
		if(++specifierLen == specifierSize)
		    currentSpecifier = Realloc(currentSpecifier,
					    specifierSize += SPECIFIER_SIZE);

		modifier = ch;
		i++;
		CHECK_END
	    }
	}

	/* Finally, the conversion character. */
	{
	    char str[2] = { 0, 0 };
	    ch = [format characterAtIndex:i];
	    str[0] = ch;

	    strcat(currentSpecifier, str);
	    if(++specifierLen == specifierSize)
		currentSpecifier = Realloc(currentSpecifier,
					specifierSize += SPECIFIER_SIZE);

	    characterSpecifier = ch;
	}

	if (![self handleFormatSpecifierWithContext:context])
	    return NO;

	i++;
	*currentSpecifier = 0;
	specifierLen = 0;

	CHECK_END
    }

_return:
    return YES;
}

- (BOOL)handleOrdinaryString:(NSString*)string
{
    return YES;
}

- (BOOL)handleFormatSpecifierWithContext:(void*)context
{
    return YES;
}

@end /* FormatScanner */
