/* 
   NSCalendarDateScanf.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

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

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <Foundation/NSString.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSCharacterSet.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSScanner.h>

#include "NSCalendarDateScanf.h"

@implementation NSCalendarDateScanf

static NSCharacterSet* blanks = nil;
static id timeZoneCharSet = nil;

+ (void)initialize
{
    blanks = [NSCharacterSet whitespaceAndNewlineCharacterSet];

    timeZoneCharSet = [NSMutableCharacterSet
			    characterSetWithCharactersInString:@"+-"];
    [timeZoneCharSet formUnionWithCharacterSet:
			   [NSCharacterSet alphanumericCharacterSet]];
    timeZoneCharSet = [timeZoneCharSet copy];
}

- init
{
    /* Set an acceptable value for day. If it is not read from description this
       prevents a failed assertion in NSCalendarDate. */
    day = 1;
    scanner = [NSScanner new];
    temporary = [NSScanner new];
    return [super init];
}

- (void)dealloc
{
    [locale release];
    [scannedString release];
    [scanner release];
    [temporary release];
    [super dealloc];
}

- setString:(NSString*)description withLocale:(NSDictionary*)_locale
{
    [scannedString release];
    scannedString = [description retain];

    [_locale retain];
    [locale release];
    locale = _locale;

    [scanner initWithString:scannedString];
    [scanner scanCharactersFromSet:blanks intoString:NULL];

    return self;
}

- (BOOL)handleOrdinaryString:(NSString*)string
{
    id substring;
    NSRange range;

    /* Skip the blanks in scannedString */
    [scanner scanCharactersFromSet:blanks intoString:NULL];

    /* Determine the substring from string that doesn't begin with blanks. */
    [temporary initWithString:string];
    [temporary setScanLocation:0];
    [temporary scanCharactersFromSet:blanks intoString:NULL];
    range.location = [temporary scanLocation];
    if (range.location) {
	range.length = [string length] - range.location;
	substring = [string substringWithRange:range];
    }
    else
	substring = string;

    /* Match the current part of `scannedString' with `substring'. */
    if ([scanner scanString:substring intoString:NULL])
	return YES;

    return NO;
}

- (int)_searchInArray:(NSArray*)array
{
    int i, count = [array count];

    for (i = 0; i < count; i++) {
	id name = [array objectAtIndex:i];

	if ([scanner scanString:name intoString:NULL])
	    return i;
    }

    return -1;
}

/* This method skips the weekday name and the day of the year, so if you supply
   a wrong name of day related to the actual date this will not be an error. */
- (BOOL)handleFormatSpecifierWithContext:(void*)context
{
    char specifier = [self characterSpecifier];
    NSArray* days = nil;
    NSArray* months = nil;
    int result;

    /* Skip the blanks in scannedString */
    [scanner scanCharactersFromSet:blanks intoString:NULL];

    switch (specifier) {
	case 'a': {
	    days = [locale objectForKey:@"NSShortWeekDayNameArray"];
	    if ((result = [self _searchInArray:days]) != -1)
		return YES;
	    break;
	}
	case 'A': {
	    days = [locale objectForKey:@"NSWeekDayNameArray"];
	    if ((result = [self _searchInArray:days]) != -1)
		return YES;
	    break;
	}
	case 'b': {
	    months = [locale objectForKey:@"NSShortMonthNameArray"];
	    if ((result = [self _searchInArray:months]) != -1) {
		month = result + 1;
		return YES;
	    }
	    break;
	}
	case 'B': {
	    months = [locale objectForKey:@"NSMonthNameArray"];
	    if ((result = [self _searchInArray:months]) != -1) {
		month = result + 1;
		return YES;
	    }
	    break;
	}
	case 'd': {
	    if ([scanner scanInt:&day])
		return YES;
	    break;
	}
	case 'I':
	    hourIsUnder12 = YES;
	    /* No `break' here */
	case 'H': {
	    if ([scanner scanInt:&hour])
		return YES;
	    break;
	}
	case 'j': {
	    int dayOfYear;

	    if ([scanner scanInt:&dayOfYear]
		&& (dayOfYear >= 1 && dayOfYear <= 366))
		return YES;
	    break;
	}
	case 'm': {
	    if ([scanner scanInt:&month] && (month >= 1 && month <= 12))
		return YES;
	    break;
	}
	case 'M': {
	    if ([scanner scanInt:&minute] && (minute >= 0 && minute <= 59))
		return YES;
	    break;
	}
	case 'p': {
	    NSArray* ampm = [locale objectForKey:@"NSAMPMDesignation"];
	    if ((result = [self _searchInArray:ampm]) != -1) {
		isPM = (result == 1);
		return YES;
	    }
	    break;
	}
	case 'S': {
	    if ([scanner scanInt:&second] && (second >= 0 && second <= 61))
		return YES;
	    break;
	}
	case 'w': {
	    int weekday;

	    if ([scanner scanInt:&weekday] && (weekday >= 0 && weekday <= 6))
		return YES;
	    break;
	}
	case 'Y': {
	    if ([scanner scanInt:&year])
		return YES;
	    break;
	}
	case 'Z': {
	    NSString* timeZoneName;

	    if ([scanner scanCharactersFromSet:timeZoneCharSet
			 intoString:&timeZoneName]) {
		timeZone = [NSTimeZone timeZoneWithAbbreviation:timeZoneName];
		[timeZone retain];
		return YES;
	    }
	    break;
	}
	case 'z': {
	    int hourMinute, hours, minutes;
	    BOOL is_negative;

	    if ([scanner scanInt:&hourMinute]) {
		is_negative = (hourMinute < 0);
		hourMinute = abs (hourMinute);
		hours = hourMinute / 100;
		minutes = hourMinute % 100;
		if (!(hours < 24 && minutes < 60))
		    return NO;
		hourMinute = (hours * 3600 + minutes * 60);
		if (is_negative)
		    hourMinute = -hourMinute;
		timeZone = [[NSTimeZone timeZoneForSecondsFromGMT:hourMinute]
				retain];
		return YES;
	    }
	}
    }

    return NO;
}

- (int)hour
{
    return (hourIsUnder12 && isPM) ? hour + 12 : hour;
}

- (int)year			{ return year; }
- (int)month			{ return month; }
- (int)day			{ return day; }
- (int)minute			{ return minute; }
- (int)second			{ return second; }
- (NSTimeZone*)timeZone		{ return timeZone; }

@end /* NSCalendarDateScanf */
