//
//  RKConnectionDescription.m
//  RestKit
//
//  Created by Blake Watters on 11/20/12.
//  Copyright (c) 2012 RestKit. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

#import "RKConnectionDescription.h"

static NSSet *RKSetWithInvalidAttributesForEntity(NSArray *attributes, NSEntityDescription *entity)
{
    NSMutableSet *attributesSet = [NSMutableSet setWithArray:attributes];
    NSSet *validAttributeNames = [NSSet setWithArray:[[entity attributesByName] allKeys]];
    [attributesSet minusSet:validAttributeNames];
    return attributesSet;
}

// Provides support for connecting a relationship by
@interface RKForeignKeyConnectionDescription : RKConnectionDescription
@end

// Provides support for connecting a relationship by traversing the object graph
@interface RKKeyPathConnectionDescription : RKConnectionDescription
@end

@interface RKConnectionDescription ()
@property (nonatomic, strong, readwrite) NSRelationshipDescription *relationship;
@property (nonatomic, copy, readwrite) NSDictionary *attributes;
@property (nonatomic, copy, readwrite) NSString *keyPath;
@end

@implementation RKConnectionDescription

- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)attributes
{
    NSParameterAssert(relationship);
    NSParameterAssert(attributes);
    NSAssert([attributes count], @"Cannot connect a relationship without at least one pair of attributes describing the connection");
    NSSet *invalidSourceAttributes = RKSetWithInvalidAttributesForEntity([attributes allKeys], [relationship entity]);
    NSAssert([invalidSourceAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for source entity '%@': %@", [[relationship entity] name], [[invalidSourceAttributes allObjects] componentsJoinedByString:@", "]);
    NSSet *invalidDestinationAttributes = RKSetWithInvalidAttributesForEntity([attributes allValues], [relationship destinationEntity]);
    NSAssert([invalidDestinationAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for destination entity '%@': %@", [[relationship destinationEntity] name], [[invalidDestinationAttributes allObjects] componentsJoinedByString:@", "]);
    
    self = [[RKForeignKeyConnectionDescription alloc] init];
    if (self) {
        self.relationship = relationship;
        self.attributes = attributes;
        self.includesSubentities = YES;
    }
    return self;
}

- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath
{
    NSParameterAssert(relationship);
    NSParameterAssert(keyPath);
    self = [[RKKeyPathConnectionDescription alloc] init];
    if (self) {
        self.relationship = relationship;
        self.keyPath = keyPath;
    }
    return self;
}

- (id)init
{
    if ([self class] == [RKConnectionDescription class]) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                       reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. "
                                               "Invoke initWithRelationship:attributes: instead.",
                                               NSStringFromClass([self class])]
                                     userInfo:nil];
    }
    return [super init];
}

- (id)copyWithZone:(NSZone *)zone
{
    if ([self isForeignKeyConnection]) {
        return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship attributes:self.attributes];
    } else if ([self isKeyPathConnection]) {
        return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship keyPath:self.keyPath];
    }
    
    return nil;
}

- (BOOL)isForeignKeyConnection
{
    return NO;
}

- (BOOL)isKeyPathConnection
{
    return NO;
}

@end

@implementation RKForeignKeyConnectionDescription

- (BOOL)isForeignKeyConnection
{
    return YES;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' from Entity '%@' to Destination Entity '%@' with attributes=%@>",
            NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name],
            [[self.relationship destinationEntity] name], self.attributes];
}

@end

@implementation RKKeyPathConnectionDescription

- (BOOL)isKeyPathConnection
{
    return YES;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' of Entity '%@' with keyPath=%@>",
            NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name], self.keyPath];
}

@end
