/* EOAccessAdditions.m created by lindberg on Mon 20-Dec-1999 */
/*-
 * Copyright (c) 2002-2006 Carl Lindberg, Mike Gentry, and Doug McClure
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "EOAccessAdditions.h"
#import "FoundationAdditions.h"
#import <Foundation/Foundation.h>
//#import <EOModeler/EOModelExtensions.h>

#ifdef EOF2_ONLY
@interface EOQualifier (EOF2AccessAdditions)
- (NSArray *)bindingKeys;
- (NSString *)keyPathForBindingKey:(NSString *)key;
@end
@interface EOEntity (JavaExtensions)
- (NSString *)referenceJavaClassName;
@end
@interface EOAttribute (JavaExtensions)
- (NSString *)javaValueClassName;
@end
@implementation EOQualifier (EOF2AccessAdditions)
- (NSArray *)bindingKeys { return [NSArray array]; }
- (NSString *)keyPathForBindingKey:(NSString *)key { return nil; }
@end
#endif

@implementation EOModel (EOAccessAdditions)

/*"
 * Tries to find the framework name. First looks to see if a value for
 * "EOGeneratorFrameworkName" exists in the model's -userInfo dictionary, and if
 * not, looks at the model's -path to find the framework the .eomodeld is in.
 * Both PB.project files and path components ending in ".framework" are looked
 * for, so it should work for .eomodels insided installed frameworks or in a
 * development tree. If the framework name could not be found, returns nil.
 * Several other custom methods use this information to generate \#import
 * statements better. If all your EO classes are in the same framework, this
 * doesn't make much difference, but if there are EOModels in several different
 * frameworks with interrelationships, these methods can make life a lot easier.
"*/
- (NSString *)frameworkName
{
    NSDictionary  *userInfo = [self userInfo];
    NSFileManager *manager  = [NSFileManager defaultManager];
    NSString      *name;

    name = [userInfo objectForKey:@"EOGeneratorFrameworkName"];

    if (name == nil)
    {
        NSString *fullPath = [self path];
        NSMutableDictionary *mutableInfo = [[userInfo mutableCopy] autorelease];

        if (mutableInfo == nil) mutableInfo = [NSMutableDictionary dictionary];

        if (fullPath != nil && ![fullPath isAbsolutePath])
            fullPath = [[manager currentDirectoryPath] stringByAppendingPathComponent:fullPath];

        while ([fullPath isAbsolutePath])
        {
            NSString *pbPath;

            fullPath = [fullPath stringByDeletingLastPathComponent];
            pbPath = [fullPath stringByAppendingPathComponent:@"PB.project"];

            if ([manager regularFileExistsAtPath:pbPath])
            {
                NSDictionary *project = [NSDictionary dictionaryWithContentsOfFile:pbPath];

                if ([[project objectForKey:@"PROJECTTYPE"] hasSuffix:@"Framework"])
                {
                    name = [project objectForKey:@"PROJECTNAME"];
                    break;
                }
            }
            else if ([[fullPath pathExtension] isEqual:@"framework"])
            {
                name = [[fullPath lastPathComponent] stringByDeletingPathExtension];
                break;
            }
        }

        [mutableInfo setObject:name? name : @"" forKey:@"EOGeneratorFrameworkName"];
        [self setUserInfo:mutableInfo];
    }

    return [name length] > 0 ? name : nil;
}

- (NSArray *)entityNamesMatchingWildcard:(NSString *)wildcard
{
    NSMutableArray *names = [NSMutableArray array];
    NSArray *entities = [self entities];
    int i, count = [entities count];

    for (i=0; i<count; i++)
    {
        NSString *entityName = [(EOEntity*)[entities objectAtIndex:i] name];
        if ([entityName isLike:wildcard])
            [names addObject:entityName];
    }

    return names;
}

@end


@implementation EOEntity (EOAccessAdditions)

/*" Returns YES/NO if the there is a parent entity for this EOEntity. "*/
- (BOOL)hasParentEntity
{
    return ([self parentEntity] != nil);
}

/*" Returns the framework name that the model is in. "*/
- (NSString *)frameworkName
{
    return [[self model] frameworkName];
}

/*"
 * Returns <Framework/ClassName.h> if inside a framework, "ClassName.h"
 * otherwise.
"*/
- (NSString *)objcImportString
{
    return [self objcImportStringInRelationToEntity:nil];
}

/*"
 * If sourceEntity is nil, returns <Framework/ClassName.h> if inside a
 * framework, "ClassName.h" otherwise. If sourceEntity is non-nil, the
 * <Framework/ClassName.h> version will only be returned if the receiver's
 * framework and sourceEntity's framework are different.  This depends on
 * the -#frameworkName method doing the right thing.
"*/
- (NSString *)objcImportStringInRelationToEntity:(EOEntity *)sourceEntity
{
    NSString *myClass = [self className];
    NSString *myFramework = [self frameworkName];
    NSString *sourceFramework = [sourceEntity frameworkName];

    if ([myClass hasSuffix:@"EOGenericRecord"]) return nil;

    if (myFramework != nil &&
        (sourceEntity == nil || ![sourceFramework isEqualToString:myFramework]))
    {
        return [NSString stringWithFormat:@"<%@/%@.h>", myFramework, myClass];
    }
    else
    {
        return [NSString stringWithFormat:@"\"%@.h\"", myClass];
    }
}

/*"
 * If there is no parent entity, returns nil. Otherwise, uses
 * -#objcImportStringInRelationToEntity: to determine the string needed to
 * \#import the parent class. I'm not sure if EOModeler lets you have the parent
 * entity in an external .eomodeld, but we'll provide this to be safe.
"*/
- (NSString *)parentObjCImportString
{
    return [[self parentEntity] objcImportStringInRelationToEntity:self];
}

/*"
 * If -#parentObjCImportString is nil, returns an empty array, otherwise returns
 * a single-element array with the parent import string.
"*/
- (NSArray *)arrayWithParentObjCImportStringIfNeeded
{
    NSString *import = [self parentObjCImportString];

    if (import != nil)
        return [NSArray arrayWithObject:import];
    else
        return [NSArray array];
}

/*"
 * A parallel for -#referencedClassNames for dealing with import strings. The
 * array will contain a uniqued list of \#import strings for all related
 * entities.
"*/
- (NSArray *)referencedObjCImportStrings
{
    NSArray        *relationships = [self relationships];
    int            i, count = [relationships count];
    NSMutableArray *refImports = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
    {
        EORelationship *relationship = [relationships objectAtIndex:i];
        EOEntity       *destEntity   = [relationship destinationEntity];

        if (![[destEntity className] hasSuffix:@"EOGenericRecord"])
        {
            NSString *import = [relationship objcImportString];
            if (import && ![refImports containsObject:import])
                [refImports addObject:import];
        }
    }

    return [refImports sortedArrayUsingSelector:@selector(compare:)];
}


- (NSString *)classPackageName
{
    NSString *className = [self className];
    NSRange  dotRange = [className rangeOfString:@"." options:NSBackwardsSearch];

    if (dotRange.length > 0)
        return [className substringToIndex:dotRange.location];

    return nil;
}

#ifndef EOF2_ONLY
- (NSString *)clientClassPackageName
{
    NSString *className = [self clientClassName];
    NSRange  dotRange = [className rangeOfString:@"." options:NSBackwardsSearch];

    if (dotRange.length > 0)
        return [className substringToIndex:dotRange.location];

    return nil;
}

- (NSString *)javaParentClientClassName
{
    NSString *parentClass = [[self parentEntity] clientClassName];
    if (parentClass == nil || [parentClass length] == 0)
        parentClass = @"EOGenericRecord";
    return parentClass;
}

/*
 * Implementations for some JavaClient-specific methods that EOModeler gets
 * because they are in the EOJavaClientExtensions.EOMBundle. We could possibly
 * load the bundle, but this is easier.
 */

- (NSArray *)clientClassAttributes
{
    NSArray *names = [self clientClassPropertyAttributeNames];
    int     i, count = [names count];
    NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
        [attributes addObject:[self attributeNamed:[names objectAtIndex:i]]];

    return attributes;
}

- (NSArray *)clientClassToOneRelationships
{
    NSArray *names = [self clientClassPropertyToOneRelationshipNames];
    int     i, count = [names count];
    NSMutableArray *relationships = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
        [relationships addObject:[self relationshipNamed:[names objectAtIndex:i]]];

    return relationships;
}

- (NSArray *)clientClassToManyRelationships
{
    NSArray *names = [self clientClassPropertyToManyRelationshipNames];
    int     i, count = [names count];
    NSMutableArray *relationships = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
        [relationships addObject:[self relationshipNamed:[names objectAtIndex:i]]];

    return relationships;
}

// These next two aren't used, but the bundle implements them, so we might as
// well too in case someone wants to use them.
- (NSArray *)clientClassScalarAttributes
{
    NSArray *attributes = [self clientClassAttributes];
    int     i, count = [attributes count];
    NSMutableArray *scalars = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
    {
        EOAttribute *attrib = [attributes objectAtIndex:i];
        if ([attrib isScalar])
            [scalars addObject:attrib];
    }

    return scalars;
}

- (NSArray *)clientClassNonScalarAttributes
{
    NSArray *attributes = [self clientClassAttributes];
    int     i, count = [attributes count];
    NSMutableArray *nonscalars = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
    {
        EOAttribute *attrib = [attributes objectAtIndex:i];
        if (![attrib isScalar])
            [nonscalars addObject:attrib];
    }

    return nonscalars;
}

#endif

#ifdef EOF2_ONLY
/* Define for use by EOF2 systems */
- (NSString *)classNameWithoutPackage
{
    NSString *className = [self className];
    NSRange  dotRange = [className rangeOfString:@"." options:NSBackwardsSearch];

    if (dotRange.length > 0)
        className = [className substringFromIndex:NSMaxRange(dotRange)];

    return className;
}

- (NSString *)referenceJavaClassName
{
    return [self className];
}

- (NSArray *)fetchSpecificationNames
{
    return [NSArray array];
}
- (EOFetchSpecification *)fetchSpecificationNamed:(NSString *)name
{
    return nil;
}
- (NSArray *)clientClassProperties
{
    return [NSArray array];
}

#endif

- (NSArray *)fetchSpecifications
{
    NSArray *fetchSpecificationNames = [self fetchSpecificationNames];
    NSMutableArray *fetchSpecifications = [NSMutableArray arrayWithCapacity:[fetchSpecificationNames count]];
    NSEnumerator *fetchNameEnumerator = [fetchSpecificationNames objectEnumerator];
    NSString *fetchName;

    while ( fetchName = [fetchNameEnumerator nextObject] ) {
        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
        [dictionary setObject:fetchName forKey:@"name"];
        [dictionary setObject:[self fetchSpecificationNamed:fetchName] forKey:@"spec"];
        [dictionary setObject:[fetchName beautifyString] forKey:@"niceName"];
        [fetchSpecifications addObject:dictionary];
    }

    return fetchSpecifications;
}

- (NSArray *)_beautifiedFetchSpecificationDictionariesIsJava:(BOOL)isJava
{
    NSArray *fetchSpecificationNames = [self fetchSpecificationNames];
    NSMutableArray *fetchSpecifications = [NSMutableArray arrayWithCapacity:[fetchSpecificationNames count]];
    NSMutableArray *addedNames = [NSMutableArray arrayWithCapacity:[fetchSpecificationNames count]];
    NSEnumerator *fetchNameEnumerator = [fetchSpecificationNames objectEnumerator];
    NSString *fetchName;

    while ( fetchName = [fetchNameEnumerator nextObject] ) {
        NSString *beautifyName = [fetchName beautifyString];
        NSMutableString *methodName = [NSMutableString stringWithFormat:@"objectsFor%@", beautifyName];
        EOFetchSpecification *fetchSpec = [self fetchSpecificationNamed:fetchName];
        NSArray *bindings = [fetchSpec bindingParametersIsJava:isJava];
        NSEnumerator *bindingEnumerator = [bindings objectEnumerator];
        NSDictionary *binding;

        if ( !isJava ) {
            // To prevent duplicate method names, we must composite a complete ObjC name which will be
            // the fetch specification name, beautified, and each of the bindings as a parameter
            // ie: objectForFetchEmployees:departmentNumber:projectNumber:
            [methodName appendString:@":"];

            while ( binding = [bindingEnumerator nextObject] ) {
                [methodName appendFormat:@"%@:", [binding objectForKey:@"name"]];
            }
        }
        else {
            // To prevent duplicate method names, we must composite a complete Java method name which will be
            // the fetch specification name, beautified, and each binding's type as a parameter
            // ie: objectForFetchEmployees(EOEditingContext,Department,Project)
            [methodName appendString:@"(EOEditingContext"];

            while ( binding = [bindingEnumerator nextObject] ) {
                [methodName appendFormat:@",%@", [binding objectForKey:@"type"]];
            }

            [methodName appendString:@")"];
        }

        if ( [addedNames containsObject:methodName] ) {
            ErrPrintf(@"Ignoring '%@' fetch specification in entity %@. Method name '%@' already used.", fetchName, [self name], methodName);
        }
        else {
            NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
            [dictionary setObject:methodName forKey:@"name"];
            [dictionary setObject:fetchName forKey:@"fetchName"];
            [dictionary setObject:beautifyName forKey:@"niceName"];
            [dictionary setObject:fetchSpec forKey:@"fetchSpec"];
            [dictionary setObject:bindings forKey:@"bindings"];
            [fetchSpecifications addObject:dictionary];
            [addedNames addObject:methodName];
        }
    }

    return fetchSpecifications;
}

- (NSArray *)beautifiedFetchSpecificationDictionaries
{
    return [self _beautifiedFetchSpecificationDictionariesIsJava:NO];
}

- (NSArray *)javaBeautifiedFetchSpecificationDictionaries
{
    return [self _beautifiedFetchSpecificationDictionariesIsJava:YES];
}

@end


// [PJYF June 3 2003]
// Added this implemention to use the valueType in EOModeler for Java
@implementation EOAttribute (EOAccessAdditions)

- (NSString *)javaValueTypeClassName
{
    NSString *valueClassName = [self javaValueClassName];
    NSString *valueType = [self valueType];

    if ( [valueClassName isEqualToString:@"Number"] && ([valueType length] > 0) ) {
        switch ( [valueType characterAtIndex:0] ) {
            case 'b': return @"Byte";
            case 's': return @"Short";
            case 'i': return @"Integer";
            case 'l': return @"Long";
            case 'f': return @"Float";
            case 'd': return @"Double";
            case 'B': return @"java.math.BigDecimal";
            case 'c': return @"Boolean";
        }
    }

    return valueClassName;
}

- (NSString *)javaScalarValueTypeClassName
{
    NSString *valueClassName = [self javaValueClassName];
    NSString *valueType = [self valueType];

    if ( [valueClassName isEqualToString:@"Number"] && ([valueType length] > 0) ) {
        switch ( [valueType characterAtIndex:0] ) {
            case 'b': return @"byte";
            case 's': return @"short";
            case 'i': return @"int";
            case 'l': return @"long";
            case 'f': return @"float";
            case 'd': return @"double";
            case 'c': return @"boolean";
        }
    }

    return nil;
}

@end


@implementation EORelationship (EOAccessAdditions)

- (NSString *)objcImportString
{
    return [[self destinationEntity] objcImportStringInRelationToEntity:[self entity]];
}

@end


@implementation EOFetchSpecification (EOAccessAdditions)

- (NSArray *)bindingParametersIsJava:(BOOL)forJava
{
    NSArray *bindings = [[self qualifier] bindingKeys];
    EOClassDescription *classDesc = [EOClassDescription classDescriptionForEntityName:[self entityName]];
    int i, count = [bindings count];
    NSMutableArray *parameters = [NSMutableArray arrayWithCapacity:count];

    for (i=0; i<count; i++)
    {
        NSString *binding = [bindings objectAtIndex:i];
        NSString *keyPath = [[self qualifier] keyPathForBindingKey:binding];
        EOClassDescription *currDesc = classDesc;
        NSRange dotRange = [keyPath rangeOfString:@"."];
        NSMutableDictionary *param = [NSMutableDictionary dictionary];

        while (dotRange.length > 0)
        {
            currDesc = [currDesc classDescriptionForDestinationKey:[keyPath substringToIndex:dotRange.location]];
            keyPath  = [keyPath substringFromIndex:NSMaxRange(dotRange)];
            dotRange = [keyPath rangeOfString:@"."];
        }

        [param setObject:binding forKey:@"name"];
        [param setObject:((forJava) ? @"java.lang.Object" : @"id") forKey:@"type"]; //set a default
        [param setObject:((forJava) ? @"java.lang.Object " : @"id ") forKey:@"codeType"]; //set a default

        if ([currDesc isKindOfClass:[EOEntityClassDescription class]])
        {
            EOEntity *entity = [(EOEntityClassDescription *)currDesc entity];

            if ([entity attributeNamed:keyPath])
            {
                EOAttribute *attrib = [entity attributeNamed:keyPath];
                NSString *type;

                if (forJava) {
                    type = [attrib javaValueClassName];
                    [param setObject:type forKey:@"type"];
                    [param setObject:[type stringByAppendingString:@" "] forKey:@"codeType"];
                }
                else {
                    type = [attrib valueClassName];
                    [param setObject:type forKey:@"type"];
                    [param setObject:[type stringByAppendingString:@" *"] forKey:@"codeType"];
                }

                [param setObject:attrib forKey:@"attribute"];
            }
            else if ([entity relationshipNamed:keyPath])
            {
                EORelationship *rel = [entity relationshipNamed:keyPath];
                NSString *type;

                if (forJava) {
                    type = [[rel destinationEntity] referenceJavaClassName];
                    [param setObject:type forKey:@"type"];
                    [param setObject:[type stringByAppendingString:@" "] forKey:@"codeType"];
                }
                else {
                    type = [[rel destinationEntity] referenceClassName];
                    [param setObject:[type substringToIndex:[type length]-2] forKey:@"type"]; // Removes the ' *' from the reference value type
                    [param setObject:type forKey:@"codeType"];
                }

                [param setObject:rel forKey:@"relationship"];
            }
        }

        [parameters addObject:param];
    }

    return parameters;
}

- (NSArray *)bindingParameters
{
    return [self bindingParametersIsJava:NO];
}
- (NSArray *)javaBindingParameters
{
    return [self bindingParametersIsJava:YES];
}

@end

@implementation NSString (ModelStringManipulations)

/*" Useful for turning key names into constant names "*/
- (NSString *)uppercaseUnderbarString
{
    return [NSString externalNameForInternalName:self separatorString:@"_" useAllCaps:YES];
}

/*" Inverse of above method, just for completeness "*/
- (NSString *)lowercaseNonUnderbarString
{
    return [NSString nameForExternalName:self separatorString:@"_" initialCaps:YES];
}

@end

/*
 * WO5 supports a valueType of "c" to juse java.lang.Boolean, but the
 * javaValueClassName method still generates "Number".  We need to pose
 * to fix this.
 */
@implementation EOGeneratorAttributePoser : EOAttribute
- (NSString *)javaValueClassName
{
    if ([[self valueClassName] isEqual:@"NSNumber"] && [[self valueType] isEqual:@"c"])
        return @"Boolean";
    return [super javaValueClassName];
}
@end

/* In WO5, some library that gets loaded looks for this class (it's in EOModeler).
Not necessary but prevents an error from being printed out.
*/
@interface EOSchemaSynchronizationController : NSObject
@end
@implementation EOSchemaSynchronizationController
@end