//
//  MCPObject.h
//  MCPersistence
//
//  Created by aj on Tue Jan 01 2002.
//  Copyright (c) 2001 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "MCPersistentObject.h"
//#import "MCPDefines.h"

@class MCPEntity;
@class MCPObjectContext;
@class MCPRelationship;



@interface MCPObject : NSObject <MCPersistentObject>
{
	MCPEntity					*_entity;  
    id							_nonRetainedParentObject;
	MCPObjectContext			*_context;

	BOOL						_isPersistent;
	int							_primaryKeyState;
	int							_lockState;
    NSString*                   _lockName;
	BOOL						_needsRefresh;
	
	NSCalendarDate				*_lastFetchDate;

    NSMutableArray				*_changedAttributes;
    NSMutableDictionary			*_addedToRelationships;
    NSMutableDictionary			*_removedFromRelationships;

	id							_displayHelper;

    id                          _cloneOfObject;
	
	// a list of all the relationships this object is in (for other objects) and the objects that faulted it
	NSMutableDictionary        *_inRelationships; 
}


- (id)initForEntity:(MCPEntity *)ent;
- (id)initForEntity:(MCPEntity *)ent rawRow:(NSMutableDictionary *)dict 
	objectContext:(MCPObjectContext *)context;

- (MCPEntity *)entity;

- (void)setObjectContext:(MCPObjectContext *)aContext;
- (MCPObjectContext *)objectContext;

- (id)cloneOfObject;
- (void)setCloneOfObject:(id)aCloneOfObject;

// Returns a dictionary where the keys are the changed attribute or to-one relationship names, and the values are dictionaries which contain the new value (the value on the receiver) and the old value (from the passed in object)
- (NSDictionary*)changeDictionaryFromObject: (id)object;

// Returns a dictionary where the keys are the changed attribute and the values are dictionaries which contain the new value (the value on the receiver) and the old value (from the passed in dictionary)
- (NSDictionary*)changeDictionaryWithOldValues: (NSDictionary*)oldValues;

/*
 Called once the object is inserted into the object context. Walks both to-one and to-many relationships and add them to the object context if those objects have not been added to the context. Subclassers can override to handle exceptions.
 */
- (void)awakeFromInsertionInObjectContext:(MCPObjectContext *)oc;

- (void)flushFromCache;

- (void)setDisplayHelper:(id)val;
- (id)displayHelper;


- (void)takeRawRow:(NSMutableDictionary *)dict;
- (NSDictionary *)rawRow;

// This method will create a value dictionary based on the passed in keys.  
//  It does some special stuff w.r.t. to relationships so that they can be properly recreated when setting
//  a value dictionary back on an object.  (For example, for to-one relationships the primary key of the destination
//  is recorded as the value for the relationship key in the resulting dictionary, rather than an iterative value graph)
- (NSDictionary*)valueDictionaryForKeys: (NSArray*)keys;
- (void)takeValuesFromValueDictionary: (NSDictionary*)values;
// Following call uses the object context to populate relationships as needed unlike the call above that allows the normal faulting to kick in
- (void)takeValuesFromValueDictionary: (NSDictionary*)values withObjectContext: (MCPObjectContext*)oc;

// be careful with this method when doing archived objects!! It's will just clear the rels -- no way to get them back. Safe for SQL
- (void)clearFaultedRelationships;
- (void)clearFaultedRelationshipForKey:(NSString *)aKey;

- (void)emptyAttributesAndRelationships;
	// Added specifically for SS support in Daylite - does nothing in MCPObject but is overridden in MCPGenericObject and frees its storage dictionary

- (void)faultRelationshipForKey:(NSString *)relName;
- (void)refaultRelationshipForKeyIfNecessary:(NSString *)relName;

- (BOOL)isRelationshipFaulted:(MCPRelationship *)rel;

- (void)setLastFetchDate:(NSCalendarDate *)date;
- (NSCalendarDate *)lastFetchDate;

- (void)refreshRelationship:(MCPRelationship *)rel;
- (void)refreshRelationshipNamed:(NSString *)aName;
// fetches objects from db and replaced what was already there. Keeps the NSArray's but updates the contents, faults currently unfaulted relationships
- (void)refreshRelationships;
- (void)refreshFromRawRow:(NSMutableDictionary *)dict;

// if prop is true, expects MCRefreshRelationshipKeys in the userInfo of the entity to know which *to-one* relationships to propagate the refresh to
- (void)refreshAndPropagate:(BOOL)prop;

- (void)takePrimaryKeyValue:(id)aValue forAttributeName:(NSString *)aName;

- (void)refreshIfNeeded;

/*
If we are persistent, we return yes immediately, otherwise we a more thorough test
*/
- (int)primaryKeyState;
- (void)resetPrimaryKeys;



- (void)willChangeAttributeForName:(NSString *)anAttributeName;
- (void)didChangeAttributeForName:(NSString *)anAttributeName;
- (NSArray *)changedAttributeNames;
- (BOOL)hasChanges;

- (void)postDidModifyNotification;

- (void)willAddObject:object toRelationshipWithKey:(NSString *)key;
- (void)didAddObject:object toRelationshipWithKey:(NSString *)key;

- (void)forcedRemoveObject: object fromRelationshipWithKey: (NSString *)key;
- (void)willRemoveObject:object fromRelationshipWithKey:(NSString *)key;
- (void)didRemoveObject:object fromRelationshipWithKey:(NSString *)key;


- (void)willSetNewValue:object oldValue:oldValue onRelationshipWithKey:(NSString *)key;
- (void)didSetNewValue:object oldValue:oldValue onRelationshipWithKey:(NSString *)key;

// IN RELATIONSHIPS
- (NSDictionary *)addedToRelationships;
- (NSDictionary *)removedFromRelationships;

- (NSMutableDictionary *)inRelationships;
- (void)setInRelationships:(NSMutableDictionary *)anInRelationships;

- (void)addToInRelationships:(id)anObj forRelationship:(MCPRelationship *)rel;
- (void)removeFromInRelationships:(id)anObj forRelationship:(MCPRelationship *)rel;

- (void)removeObjectFromAllInMemoryRelationships;


/*
validateForSave gets called first, and then forInsert or forUpdate is called depending on whether the object is going to be inserted or updated. If you subclass or override, always call super at the end of your code (if you don't have an error). The default validation behavour is too check for string lengths and non-null values. Don't raise the exception, just return one. If you return null, the system things all is good.
*/
- (NSException *)validateForSave;
- (NSException *)validateForInsert;
- (NSException *)validateForUpdate;
- (NSException *)validateForDelete;


- (BOOL)isPersistent;
- (void)setIsPersistent:(BOOL)flag;

// checks to see if the object is actually in the storage - use in the event you think you are out of sync. This is much heavier operation than isPersistent
- (BOOL)isTrulyPersistent;

- (BOOL)needsRefresh;
- (void)setNeedsRefresh:(BOOL)flag;

/*
If you override didSave, you must call super!! Once an object is saved, it's change state is cleared (changedAttributes, addedToRelationships, removedFromRelationships) and the persistence flag is set to YES

*/
- (void)didUnlockAfterSave;
- (void)didSave;
- (void)didInsert;
- (void)didUpdate;

- (void)willDelete;
- (void)didDelete;

- (void)addIndividualObjects:(NSArray *)objects toRelationshipWithKey:(NSString *)key;
- (void)addObject:object toRelationshipWithKey:(NSString *)key;
- (void)removeObject:object fromRelationshipWithKey:(NSString *)key;

- (void)addIndividualObjects:(NSArray *)objects toBothSidesOfRelationshipWithKey:(NSString *)key;
- (void)addObject:object toBothSidesOfRelationshipWithKey:(NSString *)key;
- (void)removeObject:object fromBothSidesOfRelationshipWithKey:(NSString *)key;

// gets the intermediate and marks it for delete directly in the object context
- (void)removeObject:(id)object fromManyToManyRelationshipWithKey:(NSString *)key;

// Subclassers can auto add the given objects
- (void)autoAddObjects:(NSArray *)array;



// set and get a parent object, this way you can always get to the theoretical owner of an object
// the parentObject is weakly retain to present retain-deadlock
- (id)parentObject;
- (void)setParentObject:(id)anObj;
// if the parent is already set, ignores the passed in value
- (void)takeParentObjectIfNotSet:(id)anObj;
// passes nil to related objects
- (void)prepareForDealloc;


// travels up the parent tree and return the first object of class aClass, nil otherwise
- (id)firstParentObjectOfClass:(Class)aClass;

// travels up the parent tree until nil is returned for the parent and returns that object
- (id)rootObject;
// travels up the parent tree until it finds class type not the same of the receiver and returns
// the last found obj matching receiver class type
- (id)sameClassRootObject;

// calls [[self rootObject] objectGraphDocument];
- (id)document;

- (void)didInitWithCoder:(NSCoder *)aCoder;
- (void)didEncodeWithCoder:(NSCoder *)aCoder;


// lock the object for editing ...
- (int)lockState;
- (void)markForUnlockAfterSave;
- (BOOL)lockForEditing;
- (void)unlockFromEditing;
- (NSString*)lockName;
- (BOOL)isLockNamed:(NSString*)aName;
- (BOOL)lockForEditingWithLockName:(NSString*)aName;

- (BOOL)lockObject;
- (void)unlockObject;
- (BOOL)isLocked;
- (BOOL)isLockedOrIsLockable;
- (BOOL)isLockedByOtherConnection;
- (BOOL)isLockedByCurrentConnection;

// does ensures that to-ones and to-manies have a parentObject set. Subclassers can add additional checks. Throw an exception if required. Each entity that is passed is added to 'array'. The default stops at 5 entities to prevent circular references. If no array is passed, one is created. Each relationship is passed a copy of the array
- (void)performIntegrityCheckUsingParent:(id)aParent passedEntityNames:(NSMutableArray *)array;

- (id)primaryKeyValue;
- (void)setPrimaryKeyValue: (id)value;

- (NSString *)uniqueStringForPrimaryKeyWithPrefix:(NSString *)prefix;

// Key/Values for the daylite URL scheme
- (NSString *)urlRepresentation;



/*!
 Collects related objects, as specified in the followOn dictionary into one set. See appendXMLValues... on how to construct the followOn dictionary.
 */
- (NSSet *)flattenedRelatedObjectsWithFollowOnRelationshipKeys:(NSDictionary *)followOn;
- (void)appendRelatedObjectsTo:(NSMutableSet *)theSet followOnRelationshipKeys:(NSDictionary *)followOn;

/*!
 Looks at owned relationships and nullifying relationship and puts them into a plist that could be used to restore the objects.
 */
- (NSDictionary *)archivableRepresentation;


- (void)appendPropertyListValuesTo:(NSMutableDictionary *)prop 
		  followOnRelationshipKeys:(NSDictionary *)followOn
				 includeNullValues:(BOOL)include;

/*!
 Builds up an XML tree.
 If no "fields" are passed in, then it includes all fields in the entity.
 You can specify additional fields by using the KVC name.
 "followOn" specifies how deep you want to go. fields and additional conform to the same concept.
 
 Example followOn = {defaultGeoAddress; notes = {largeData;};}
 Example fields = {firstname; lastname; defaultGeoAddress = {street; city;};}
 Example additionals = {derivedFullname; defaultGeoAddress = {typeString;}; notes = {plainTextRepresentation;};}
 
 WARNING - don't include relationship names in fields and additional - those should be simple foundation objects.
 
 */
- (NSXMLElement *)XMLValueWithFields:(NSDictionary *)fields 
					additionalFields:(NSDictionary *)additional
			followOnRelationshipKeys:(NSDictionary *)followOn
				   includeNullValues:(BOOL)include;

- (void)appendXMLValuesToElement:(NSXMLElement *)element 
						  fields:(NSDictionary *)fields
				additionalFields:(NSDictionary *)additional
		followOnRelationshipKeys:(NSDictionary *)followOn
			   includeNullValues:(BOOL)include;



- (id)cloneValueAttributes:(BOOL)cloneValues toOneRels:(BOOL)cloneToOne toManyRels:(BOOL)cloneToMany andInstanceAttribs:(NSArray*)instanceAttribs;

- (id)clone;
- (void)mergeValueAttributesWithClone:(id)aClone;
- (void)mergeWithClone:(id)aClone;
- (void)mergeWithClone:(id)aClone saveChanges:(BOOL)saveChanges;

- (id)cloneWithKeys:(NSArray*)keys;
- (void)mergeClone:(id)aClone WithKeys:(NSArray*)keys;

- (NSArray*)toOneKeysDependentOnOtherKeys;
- (NSArray*)toManyKeysToClone;

- (NSArray*)notPersistentValueAttributes;

- (void)syncToQuickSearch;

#pragma mark OptimizationHelp
- (BOOL)hasValidForeignKeyForToOneRelationshipWithName:(NSString *)aRelName;
- (NSNumber *)linkedFlagForRelationship:(MCPRelationship *)aRel;
- (NSNumber *)linkedFlagForRelationshipNamed:(NSString *)aRelName;

- (void)propagateToOneRelationships;
- (void)propagateToManyRelationships;

@end


@interface NSArray (MCPObjectLocking)
- (void)markForUnlockAfterSave;
- (BOOL)lockForEditing;
- (void)unlockFromEditing;

@end



@interface NSObject (MCPObjectDocumentDelegateSupport)
// could be used to refresh a model object before displaying
- (void)refreshModel:(id)aModel;
@end


// These method should be implemented by the root object in an object graph. Typically a subclass of NSDocument would be the object graph document
@interface NSObject (MCPersistentObjectGraph)
- (void)setObjectGraphDocument:(id)aDoc;
- (id)objectGraphDocument;

// calls [[self objectGraphDocument] openEditorForObject:self]
- (id)openAndLockInEditor;


- (NSUndoManager *)undoManager;

// usually sent from a child in a graph to its objectGraphDocument in response to an undo/redo action
- (void)undoManagerActedOnObject:(id)anObject;
@end

@interface NSArray (MCPersistenObjectObjectGraphSupport)

// calls [[self lastObject] document]; This is not exactly a safe method ... it assumes that all object in an array are from the same document -- Alternatively you can ask each object in the array for it's document
- (id)document;

- (void)appendPropertyListValuesInto:(NSMutableArray *)newArray 
			followOnRelationshipKeys:(NSDictionary *)followOn
				   includeNullValues:(BOOL)include;

/*!
 Uses "elementName" as the encapsulating container, then calls XMLValueWithFields: additionalFields: followOnRelationshipKeys: includeNullValues:
 */
- (NSXMLElement *)XMLValueWithName:(NSString *)elementName
					  objectFields:(NSDictionary *)fields 
				  additionalFields:(NSDictionary *)additional
		  followOnRelationshipKeys:(NSDictionary *)followOn
				 includeNullValues:(BOOL)include;

@end


@interface NSObject (MCChangeNotificationSupport)

//May override this to fire specific notifcations.

//if an editor wants observers to know when the editor is finished changing this object, then invoke this method after your changes. Observers can register on the defaultNotficationCenter and listen to MCPersistentObjectDidChangeNotification.
- (void)editorDidFinishChanging:(id)editor;

- (void)editorWillStartChanging:(id)editor;

@end

@interface NSObject (MCPasteboardType)
- (NSString *)pasteboardType;
@end
