/* 
   NSTimeZone.m

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

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
	   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/common.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSProcessInfo.h>
#include <Foundation/NSUserDefaults.h>
#include <Foundation/NSException.h>
#include <Foundation/NSCoder.h>

#include "NSConcreteTimeZone.h"

/*
 * NSTimeZone cluster abstract class
 */

static NSString* timeZoneInfoFilesPath = nil;
static NSMutableDictionary *abbreviationDictionary = nil;
static NSArray* timeZoneArray = nil;
static NSMutableDictionary* regionsByOffset = nil;
static NSMutableDictionary* timeZonesByName = nil;
static NSTimeZone* defaultTimeZone = nil;
static NSTimeZone* localTimeZone = nil;

@implementation NSTimeZone

+ (NSTimeZone*)_createTimeZoneWithName:(NSString*)name
  checkDuplicates:(BOOL)checkDuplicates
{
    NSString* filename;
    NSTimeZone* concreteTimeZone;

    if (checkDuplicates) {
	if ([timeZonesByName objectForKey:name])
	    return nil;
    }

    filename = [[[timeZoneInfoFilesPath
		    stringByAppendingPathComponent:@"TimeZoneInfo"]
		    stringByAppendingPathComponent:name]
		    stringByResolvingSymlinksInPath];
    if (!filename || ![filename length]) {
	NSLog (@"Cannot find the time zone description file '%@' in "
		@"resource directory '%@'", name, timeZoneInfoFilesPath);
	return nil;
    }

    concreteTimeZone = [[[NSConcreteTimeZoneFile alloc]
			    initFromFile:filename withName:name]
			    autorelease];
    [timeZonesByName setObject:concreteTimeZone forKey:name];

    return concreteTimeZone;
}

+ (void)initialize
{
    static BOOL didInit = NO;
    NSArray* searchPaths;
    NSArray* timeZoneInfoPaths;
    NSString* timeZoneInfoPathString;
    id regionsDictionary;
    id regionsFile = nil;
    NSString* offset;
    NSEnumerator* enumerator;
    NSString* abbreviation;
    NSAutoreleasePool* pool;
    int i, count;

    if (didInit)
	return;

    didInit = YES;
    pool = [NSAutoreleasePool new];

    timeZonesByName = [NSMutableDictionary new];
    searchPaths = [NSMutableArray arrayWithObject:@RESOURCES_PATH];
    timeZoneInfoPathString = [[[NSProcessInfo processInfo] environment]
				objectForKey:@"LIB_FOUNDATION_RESOURCES_PATH"];
    if (timeZoneInfoPathString) {
	timeZoneInfoPaths
	    = [timeZoneInfoPathString componentsSeparatedByString:@":"];
	searchPaths
	    = [timeZoneInfoPaths arrayByAddingObjectsFromArray:searchPaths];
    }

    for (i = 0, count = [searchPaths count]; i < count; i++) {
	timeZoneInfoFilesPath = [searchPaths objectAtIndex:i];
	regionsFile = [[[timeZoneInfoFilesPath
		    stringByAppendingPathComponent:@"TimeZoneInfo"]
		    stringByAppendingPathComponent:@"RegionsDictionary"]
		    stringByResolvingSymlinksInPath];
	if(regionsFile && [regionsFile length])
	    break;
    }
    if (!regionsFile || ![regionsFile length]) {
	NSLog (@"Cannot find the 'TimeZoneInfo/RegionsDictionary' resource "
	       @"file for NSTimeZone");
	return;
    }
    [timeZoneInfoFilesPath retain];
    regionsDictionary = [[NSString stringWithContentsOfFile:regionsFile]
			    propertyList];

    regionsByOffset = [[regionsDictionary objectForKey:@"RegionsByOffset"]
			    mutableCopy];
    if (!regionsByOffset) {
	NSLog (@"No regions by offset in the '%@' resource file (under "
	       @"key 'RegionsByOffset')", regionsFile);
	return;
    }

    timeZoneArray = [[[regionsDictionary objectForKey:@"RegionsByOffset"]
			    allValues]
			    retain];
    enumerator = [regionsByOffset keyEnumerator];
    while ((offset = [enumerator nextObject])) {
	NSMutableArray* longitudinalRegions
	    = [[[regionsByOffset objectForKey:offset] mutableCopy]
		    autorelease];
	int j, count2;

	[regionsByOffset setObject:longitudinalRegions forKey:offset];
	for (j = 0, count2 = [longitudinalRegions count]; j < count2; j++) {
	    NSString* timeZoneName = [longitudinalRegions objectAtIndex:j];
	    NSTimeZone* timeZone;

	    timeZone = [timeZonesByName objectForKey:timeZoneName];
	    if (!timeZone)
		timeZone = [self _createTimeZoneWithName:timeZoneName
				 checkDuplicates:NO];

	    [longitudinalRegions replaceObjectAtIndex:j withObject:timeZone];
	}
    }

    abbreviationDictionary = [[regionsDictionary objectForKey:@"Abbreviations"]
				    mutableCopy];
    if (!abbreviationDictionary) {
	NSLog (@"No abbreviation dictionary in the '%@' resource file (under "
	       @"key 'Abbreviations')", regionsFile);
	return;
    }
    enumerator = [abbreviationDictionary keyEnumerator];
    while ((abbreviation = [enumerator nextObject])) {
	NSString* timeZoneName
		= [abbreviationDictionary objectForKey:abbreviation];
	NSTimeZone* timeZone = [timeZonesByName objectForKey:timeZoneName];

	if (!timeZone) {
	    NSLog (@"warning: time zone '%@' is not declared in the "
		    @"'RegionsByOffset' dictionary in resource file '%@'",
		    timeZoneName, regionsFile);
	    timeZone = [self _createTimeZoneWithName:timeZoneName
			     checkDuplicates:NO];
	}

	if (timeZone)
	    [abbreviationDictionary setObject:timeZone forKey:abbreviation];
    }

    [pool release];
}

+ (void)setDefaultTimeZone:(NSTimeZone*)aTimeZone
{
    NSUserDefaults* userDef = [NSUserDefaults standardUserDefaults];

    [aTimeZone retain];
    [defaultTimeZone release];
    defaultTimeZone = aTimeZone;

    [aTimeZone retain];
    [localTimeZone release];
    localTimeZone = aTimeZone;

    [userDef setObject:[defaultTimeZone timeZoneName] forKey:@"TimeZoneName"];
    [userDef synchronize];
}

+ (NSTimeZoneDetail*)defaultTimeZone
{
    if (defaultTimeZone == nil) {
	NSUserDefaults* userDef = [NSUserDefaults standardUserDefaults];
	NSString* zName;

	zName = [userDef stringForKey:@"TimeZoneName"];
	defaultTimeZone = [[self timeZoneWithName:zName] retain];

	if (defaultTimeZone == nil)
	    defaultTimeZone = [[self timeZoneWithAbbreviation:zName] retain];

	if (defaultTimeZone == nil)
	    defaultTimeZone = [[self timeZoneWithAbbreviation:@"GMT"] retain];
    }

    return defaultTimeZone;
}

+ (NSTimeZone*)localTimeZone
{
    if (localTimeZone == nil) {
	// TODO : subclass NS*TimeZone to have a special encoding class
	localTimeZone = [[self defaultTimeZone] retain];
    }
    
    return localTimeZone;
}

+ (NSDictionary*)abbreviationDictionary
{
    return abbreviationDictionary;
}

+ (NSArray*)timeZoneArray
{
    return timeZoneArray;
}

+ (NSTimeZone*)timeZoneWithName:(NSString*)name
{
    NSTimeZone* timezone;

    if (!name)
	return nil;

    if (!(timezone = [timeZonesByName objectForKey:name]))
	timezone = [self _createTimeZoneWithName:name checkDuplicates:NO];

    return timezone;
}

+ (NSTimeZone*)timeZoneWithAbbreviation:(NSString*)abbreviation
{
    return [abbreviationDictionary objectForKey:abbreviation];
}

+ (NSTimeZone*)timeZoneForSecondsFromGMT:(int)seconds
{
    BOOL isNegative = (seconds < 0);
    int hours, minutes;
    NSString* offset;
    NSArray* timeZoneArray;
    NSArray* details;
    NSTimeZoneDetail* detail;
    NSTimeZone* timeZone;
    int i, j, count1, count2;

    hours = abs (seconds) / 3600;
    minutes = (abs (seconds) - hours * 3600) / 60;

    offset = [NSString stringWithFormat:@"%c%02d%02d",
			    (isNegative ? '-' : '+'), hours, minutes];

    timeZoneArray = [regionsByOffset objectForKey:offset];

    /* If there is no matching zone create an instance of NSConcreteTimeZone
       with a single detail whose offset is `seconds'. */
    if (!timeZoneArray)
	return [NSConcreteTimeZone timeZoneWithOffset:seconds];

    /* Do a search to find a zone that has a detail with no daylight
       saving which has the offset equal with `seconds'. */
    for (i = 0, count1 = [timeZoneArray count]; i < count1; i++) {
	timeZone = [timeZoneArray objectAtIndex:i];
	details = [timeZone timeZoneDetailArray];

	/* Find a time zone that has a detail which is not daylight saving
	   and has the offset equal with `seconds'. If no such detail is
	   found the search either continues or ends depending on the offset
	   of detail. */
	for (j = 0, count2 = [details count]; j < count2; j++) {
	    detail = [details objectAtIndex:j];
	    if (![detail isDaylightSavingTimeZone]
		&& [detail timeZoneSecondsFromGMT] == seconds)
		    return timeZone;
	}
    }

    NSLog (@"warning: unexpected failure of +timeZoneForSecondsFromGMT: "
	   @"method. All the timezones specified for offset %@ do not "
	   @"contain a detail whose non daylight saving offset is equal to "
	   @"%@!", offset, offset);

    /* Return something useful even if there was an error! */
    return [NSConcreteTimeZone timeZoneWithOffset:seconds];
}

- (NSString*)timeZoneName
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSArray*)timeZoneDetailArray
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSTimeZoneDetail*)timeZoneDetailForDate:(NSDate*)date
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (id)copyWithZone:(NSZone*)zone
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSString*)description
{
    return [[self timeZoneName] description];
}

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

- (void)encodeWithCoder:(NSCoder*)aCoder
{
    [aCoder encodeObject:[self timeZoneName]];
}

- (id)initWithCoder:(NSCoder*)aDecoder
{
    NSString* timeZoneName = [aDecoder decodeObject];
    return [NSTimeZone timeZoneWithName:timeZoneName];
}

@end /* NSTimeZone */

/*
 * NSTimeZone detail (concrete non-mutable subclass of NSTimeZone)
 */

@implementation NSTimeZoneDetail

- (int)timeZoneSecondsFromGMT
{	
    [self subclassResponsibility:_cmd];
    return 0;
}

- (NSString*)timeZoneAbbreviation
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (BOOL)isDaylightSavingTimeZone
{
    [self subclassResponsibility:_cmd];
    return NO;
}

- (BOOL)isEqual:anObject
{
    if (anObject == self) return YES;

    return ([super isEqual:anObject]
	// this checks to ensure that they're the same class
	&& [[self timeZoneName] isEqual:[anObject timeZoneName]]
	&& [[self timeZoneAbbreviation] 
		isEqual:[anObject timeZoneAbbreviation]]
	&& [self isDaylightSavingTimeZone] 
		== [anObject isDaylightSavingTimeZone]
	&& [self timeZoneSecondsFromGMT] 
		== [anObject timeZoneSecondsFromGMT]);
}

- (unsigned int)hash
{
    // This should be sufficient for hashing
    return [self timeZoneSecondsFromGMT];
}

@end /* NSTimeZoneDetail */
