//
//  $Id$
//
//  MCPAttribute.m
//  MCPkit
//
//  Created by Serge Cohen (serge.cohen@m4x.org) on 09/08/04.
//  Copyright (c) 2004 Serge Cohen. All rights reserved.
//
//  Forked by the Sequel Pro team (sequelpro.com), April 2009
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  More info at <http://mysql-cocoa.sourceforge.net/>
//  More info at <http://code.google.com/p/sequel-pro/>

#import "MCPAttribute.h"

#import "MCPEntrepriseNotifications.h"

#import "MCPModel.h"
#import "MCPClassDescription.h"
#import "MCPRelation.h"
#import "MCPJoin.h"

static NSArray *MCPRecognisedInternalType;

@interface MCPAttribute (Private)

- (void)setValueClassName:(NSString *) iClassName;

@end

@implementation MCPAttribute

#pragma mark Class methods
+ (void) initialize
{
	if (self == [MCPAttribute class]) {
		[self setVersion:010101]; // Ma.Mi.Re -> MaMiRe
		MCPRecognisedInternalType = [[NSArray alloc] initWithObjects:@"NSCalendarDate", @"NSData", @"NSNumber", @"NSString", nil];
		[self setKeys:[NSArray arrayWithObject:@"internalType"] triggerChangeNotificationsForDependentKey:@"valueClassName"];
		[self setKeys:[NSArray arrayWithObject:@"valueClassName"] triggerChangeNotificationsForDependentKey:@"internalType"];
	}
	return;
}


#pragma mark Life cycle
- (id) initForClassDescription:(MCPClassDescription *) iClassDescription withName:(NSString *) iName
{
	self = [super init];
	{
		classDescription = iClassDescription;
		[self setName:iName];
//		relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL));
		joins = [[NSMutableArray alloc] init];
	}
	return self;
}

- (void) dealloc
{
//	NSArray           *theRelations;
//	unsigned int      i;

	[name release];
	[internalType release];
	[externalName release];
	[externalType release];
	[defaultValue release];
/*
	while ([relations count]) {
		[(MCPRelation *)[relations objectAtIndex:0] unjoinAttribute:self];
	}
// By now relation should be empty anyway...
	[relations release];
 */
	while ([joins count]) {
		[[self objectInJoinsAtIndex:0] invalidate];
	}
	// By now the joins array should be empty
	[joins release];
	[super dealloc];
}


#pragma mark NSCoding protocol
- (id) initWithCoder:(NSCoder *) decoder
{
	self = [super init];
	if ((self) && ([decoder allowsKeyedCoding])) {
		NSString    *theClassName = [decoder decodeObjectForKey:@"MCPvalueClassName"];
		
		classDescription = [decoder decodeObjectForKey:@"MCPclassDescription"];
		[self setName:[decoder decodeObjectForKey:@"MCPname"]];
		if (theClassName) {
			[self setValueClass:NSClassFromString(theClassName)];
		}
		[self setInternalType:[decoder decodeObjectForKey:@"MCPinternalType"]];
		[self setExternalName:[decoder decodeObjectForKey:@"MCPexternalName"]];
		[self setExternalType:[decoder decodeObjectForKey:@"MCPexternalType"]];
		[self setWidth:(NSUInteger)[decoder decodeInt32ForKey:@"MCPwidth"]];
		[self setAllowsNull:[decoder decodeBoolForKey:@"MCPallowsNull"]];
		[self setAutoGenerated:[decoder decodeBoolForKey:@"MCPautoGenerated"]];
		[self setIsPartOfKey:[decoder decodeBoolForKey:@"MCPisPartOfKey"]];
		[self setIsPartOfIdentity:[decoder decodeBoolForKey:@"MCPisPartOfIdentity"]];
		[self setHasAccessor:[decoder decodeBoolForKey:@"MCPhasAccessor"]];
		[self setDefaultValue:[decoder decodeObjectForKey:@"MCPdefaultValue"]];
// Not sure that the next line is working (getting an array holding weak references), hence doing the thing expelcitly:
//      relations = [[decoder decodeObjectForKey:@"MCPrelations"] retain];
//		relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL));
//		[relations addObjectsFromArray:[decoder decodeObjectForKey:@"MCPrelations"]];
		joins = [[NSMutableArray alloc] init]; // Will be filled in when the relations are read in.
	}
	else {
		NSLog(@"For some reason, unable to decode MCPAttribute from the coder!!!");
	}
//	NSLog(@"MAKING a new object : %@", self);
	return self;
}

- (void) encodeWithCoder:(NSCoder *) encoder
{
	NSString    *theValueClassName;
	
	if (! [encoder allowsKeyedCoding]) {
		NSLog(@"In MCPAttribute -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!");
		return;
	}
//	theValueClassName = (valueClass) ? [valueClass className] : nil;
	theValueClassName = (valueClass) ? NSStringFromClass(valueClass) : nil;
	[encoder encodeObject:[self classDescription] forKey:@"MCPclassDescription"];
	[encoder encodeObject:[self name] forKey:@"MCPname"];
	if (theValueClassName) {
		[encoder encodeObject:theValueClassName forKey:@"MCPvalueClassName"];
	}
	[encoder encodeObject:[self internalType] forKey:@"MCPinternalType"];
	[encoder encodeObject:[self externalName] forKey:@"MCPexternalName"];
	[encoder encodeObject:[self externalType] forKey:@"MCPexternalType"];
	[encoder encodeInt32:(int32_t)[self width] forKey:@"MCPwidth"];
	[encoder encodeBool:[self allowsNull] forKey:@"MCPallowsNull"];
	[encoder encodeBool:[self autoGenerated] forKey:@"MCPautoGenerated"];
	[encoder encodeBool:[self isPartOfKey] forKey:@"MCPisPartOfKey"];
	[encoder encodeBool:[self isPartOfIdentity] forKey:@"MCPisPartOfIdentity"];
	[encoder encodeBool:[self hasAccessor] forKey:@"MCPhasAccessor"];
	[encoder encodeObject:[self defaultValue] forKey:@"MCPdefaultValue"];
//	[encoder encodeObject:relations forKey:@"MCPrelation"];
	// We don't have to save the joins here ... the joins are saving there attributes.
	// The links are recreated when the joins are decoded.
}

#pragma mark Setters
- (void) setName:(NSString *) iName
{
	if (iName != name) {
		[name release];
		name = [iName retain];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setValueClass:(Class) iValueClass
{
	if (iValueClass != valueClass) {
		valueClass = iValueClass;
		if (valueClass) { // Not nil : set the internalType accrodingly.
								//         [internalType release];
								//         internalType = [[valueClass className] copy];
//			[self setValue:[NSString stringWithString:[valueClass className]] forKey:@"internalType"];
			[self setValue:[NSString stringWithString:NSStringFromClass(valueClass)] forKey:@"internalType"];
		}
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setInternalType:(NSString *) iInternalType
{   
	if (iInternalType != internalType) {
		[internalType release];
		internalType = [iInternalType retain];
		if ([MCPRecognisedInternalType containsObject:internalType]) {
			[self setValueClass:NSClassFromString(internalType)];
//         By itself does NOT provide observers the update.
//         but see setKeys:triggerChangeNotificationsForDependentKey... (in +initialize).
		}
		else {
			[self setValueClass:nil];
		}
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setExternalType:(NSString *) iExternalType
{
	if (iExternalType != externalType) {
		[externalType release];
		externalType = [iExternalType retain];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setExternalName:(NSString *) iExternalName
{
	if (iExternalName != externalName) {
		[externalName release];
		externalName = [iExternalName retain];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setWidth:(NSUInteger) iWidth
{
	if (iWidth != width) {
		width = iWidth;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setAllowsNull:(BOOL) iAllowsNull
{
	if (iAllowsNull != allowsNull) {
		allowsNull = iAllowsNull;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setAutoGenerated:(BOOL) iAutoGenerated
{
	if (iAutoGenerated != autoGenerated) {
		autoGenerated = iAutoGenerated;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setIsPartOfKey:(BOOL) iIsPartOfKey
{
	if (iIsPartOfKey != isPartOfKey) {
		isPartOfKey = iIsPartOfKey;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setIsPartOfIdentity:(BOOL) iIsPartOfIdentity
{
	if (iIsPartOfIdentity != isPartOfIdentity) {
		isPartOfIdentity = iIsPartOfIdentity;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setHasAccessor:(BOOL) iHasAccessor
{
	if (iHasAccessor != hasAccessor) {
		hasAccessor = iHasAccessor;
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) setDefaultValue:(id) iDefaultValue
{
	if (iDefaultValue != defaultValue) {
		[defaultValue release];
		defaultValue = [iDefaultValue retain];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription];
		[[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self];
	}
}

- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(NSUInteger) index
{
	[joins insertObject:iJoin atIndex:index];
}

- (void) removeObjectFromJoinsAtIndex:(NSUInteger) index
{
	[joins removeObjectAtIndex:index];
}

/*
- (void) addRelation:(MCPRelation *) iRelation
{
// Following implementation make sure that a given relation is only added once... but I don't see the reason for that to be true.
	/*   if (NSNotFound == [relations indexOfObjectIdenticalTo:iRelation]) {
	[relations addObject:iRelation];
	}
	*//*
	[relations addObject:iRelation];
}

- (void) removeRelation:(MCPRelation *) iRelation
{
// Following implementation needs only one reference to a given relation to be working properly (not true)
//   [relations removeObjectIdenticalTo:iRelation];
	unsigned int      i;
	
	i = [relations indexOfObjectIdenticalTo:iRelation];
	if (NSNotFound != i) {
		[relations removeObjectAtIndex:i];
	}
// If the relation is there more than once, remove it only once.
}
*/

#pragma mark Getters
- (MCPClassDescription *) classDescription
{
	return classDescription;
}

- (NSString *) name
{
	return name;
}

- (Class) valueClass
{
	return valueClass;
}

- (NSString *) valueClassName
{
	return NSStringFromClass(valueClass);
}

- (NSString *) internalType
{
	return internalType;
}

- (NSString *) externalName
{
	return externalName;
}

- (NSString *) externalType
{
	return externalType;
}

- (NSUInteger) width
{
	return width;
}

- (BOOL) allowsNull
{
	return allowsNull;
}

- (BOOL) autoGenerated
{
	return autoGenerated;
}

- (BOOL) isPartOfKey
{
	return isPartOfKey;
}

- (BOOL) isPartOfIdentity
{
	return isPartOfIdentity;
}

- (BOOL) hasAccessor
{
	return hasAccessor;
}

- (id) defaultValue
{
	return defaultValue;
}

- (NSUInteger) countOfJoins
{
	return [joins count];
}

- (MCPJoin *) objectInJoinsAtIndex:(NSUInteger) index
{
	return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil);
}

- (NSUInteger) indexOfJoinIdenticalTo:(id) iJoin
{
	return [joins indexOfObjectIdenticalTo:iJoin];
}

#pragma mark Some general methods:
- (BOOL) isEqual:(id) iObject
// Equal to another attribute, if they have the same name and same class description.
// Equal to a string (NSString), if the name of the attribute is equal to the string.
{
	if ([iObject isKindOfClass:[MCPAttribute class]]) {
		MCPAttribute    *theAttribute = (MCPAttribute *) iObject;
		
		return ([name isEqualToString:[theAttribute name]]) && ([classDescription isEqual:[theAttribute classDescription]]);
	}
	if ([iObject isKindOfClass:[NSString class]]) {
		return [name isEqualToString:(NSString *)iObject];
	}
	return NO;
}

#pragma mark For debugging the retain counting
- (id) retain
{
	[super retain];
	return self;
}

- (void) release
{
	[super release];
	return;
}

@end

@implementation MCPAttribute (Private)

- (void)setValueClassName:(NSString *) iClassName
{
	if (NSClassFromString(iClassName) != valueClass) {
		[self setValueClass:NSClassFromString(iClassName)];    
	}
}

@end