//
//  NJRDateFormatter.m
//  Pester
//
//  Created by Nicholas Riley on Wed Oct 09 2002.
//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
//

#import "NJRDateFormatter.h"

// generated by perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
#include <EXTERN.h>
#include <perl.h>

EXTERN_C void xs_init (pTHX);

EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);

EXTERN_C void
xs_init(pTHX)
{
    char *file = __FILE__;
    dXSUB_SYS;
    
    /* DynaLoader is a special case */
    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
}
// end generated code

static PerlInterpreter *my_perl;
static NSDateFormatter *dateManipFormatter;

static NSDate *parse_natural_language_date(NSString *input) {
    if (my_perl == NULL) return nil;
    
    if ([input rangeOfString: @"|"].length > 0) {
	NSMutableString *sanitized = [[input mutableCopy] autorelease];
	[sanitized replaceOccurrencesOfString: @"|" withString: @""
				      options: NSLiteralSearch
					range: NSMakeRange(0, [sanitized length])];
	input = sanitized;
    }
    
    NSString *temp = [[NSString alloc] initWithFormat: @"UnixDate(q|%@|, '%%q')", input];
    // NSLog(@"%@", temp);
    SV *d = eval_pv([temp UTF8String], TRUE);
    [temp release];
    if (d == NULL) return nil;
    
    STRLEN s_len;
    char *s = SvPV(d, s_len);
    if (s == NULL || s_len == 0) return nil;
    
    NSDate *date = [dateManipFormatter dateFromString: [NSString stringWithUTF8String: s]];
    // NSLog(@"%@", date);
    
    return date;
}

static void init_perl(void) {
    const char *argv[] = {"", "-CSD", "-I", "", "-MDate::Manip", "-e", "0"};
    argv[3] = [[[NSBundle mainBundle] resourcePath] fileSystemRepresentation];
    PERL_SYS_INIT(0, NULL);
    my_perl = perl_alloc();
    if (my_perl == NULL) return;
    
    perl_construct(my_perl);
    if (perl_parse(my_perl, xs_init, 7, (char **)argv, NULL) != 0) goto fail;
    
    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
    if (perl_run(my_perl) != 0) goto fail;
    
    // XXX detect localization changes
    eval_pv("Date_Init(\"Language=English\", \"DateFormat=non-US\", \"Internal=1\"", TRUE);

    if (parse_natural_language_date(@"tomorrow") == nil) goto fail;
    
    return;
    
fail:
    perl_destruct(my_perl);
    perl_free(my_perl);
    PERL_SYS_TERM();
    my_perl = NULL;
}

// workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString:
NSString *stringByRemovingSurroundingWhitespace(NSString *string) {
    static NSCharacterSet *nonWhitespace = nil;
    NSRange firstValidCharacter, lastValidCharacter;
    
    if (!nonWhitespace) {
        nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString:
			   @" \t\r\n"] invertedSet] retain];
    }
    
    firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace];
    if (firstValidCharacter.length == 0)
        return @"";
    lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
    
    if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
        return string;
    else
        return [string substringWithRange: NSUnionRange(firstValidCharacter, lastValidCharacter)];
}

static const NSDateFormatterStyle formatterStyles[] = {
    NSDateFormatterShortStyle,
    NSDateFormatterMediumStyle,
    NSDateFormatterLongStyle,
    NSDateFormatterFullStyle,
    NSDateFormatterNoStyle
};

@implementation NJRDateFormatter

#pragma mark initialize-release

+ (void)initialize;
{
    dateManipFormatter = [[NSDateFormatter alloc] init];
    [dateManipFormatter setDateFormat: @"yyyyMMddHHmmss"]; // Date::Manip's "%q"
    init_perl();
}

+ (NJRDateFormatter *)dateFormatter;
{
    NJRDateFormatter *formatter = [[self alloc] init];
    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
    
    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
	[tryFormatter setLenient: YES];
	[tryFormatter setTimeStyle: NSDateFormatterNoStyle];
	[tryFormatter setDateStyle: *s];
	[tryFormatters addObject: tryFormatter];
	[tryFormatter release];
    }
    // XXX do this in init
    formatter->tryFormatters = tryFormatters;

    return [formatter autorelease];
}

+ (NJRDateFormatter *)timeFormatter;
{
    NJRDateFormatter *formatter = [[self alloc] init];
    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
    
    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
	[tryFormatter setLenient: YES];
	[tryFormatter setTimeStyle: *s];
	[tryFormatter setDateStyle: NSDateFormatterNoStyle];
	[tryFormatters addObject: tryFormatter];
	[tryFormatter release];
    }
    formatter->tryFormatters = tryFormatters;
    
    return [formatter autorelease];
}

- (void)dealloc;
{
    [tryFormatters release]; tryFormatters = nil;
    [super dealloc];
}

#pragma mark primitive formatter 

- (NSString *)stringForObjectValue:(id)obj;
{
    return [super stringForObjectValue: obj];
}

- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
{
    return [super attributedStringForObjectValue: obj
			   withDefaultAttributes: attrs];
}

- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
{
    if ([super getObjectValue: anObject forString: string errorDescription: error])
	return YES;
	
    NSDate *date;
    NSEnumerator *e = [tryFormatters objectEnumerator];
    NSDateFormatter *tryFormatter;
    
    // XXX untested; does this work?
    while ( (tryFormatter = [e nextObject]) != nil) {
	date = [tryFormatter dateFromString: string];
	if (date != nil) goto success;
    }
    
    date = parse_natural_language_date(string);
    if (date != nil) goto success;
    
    return NO;

success:
    *anObject = date;
    if (error != NULL) *error = nil;
    return YES;
}

#pragma mark miscellaneous

+ (BOOL)naturalLanguageParsingAvailable;
{
    return (my_perl != NULL);
}
@end
