/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.brooklyn.core.entity;

import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.BrooklynObject.RelationSupport;
import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.relations.RelationshipType;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import org.apache.brooklyn.core.relations.AbstractBasicRelationSupport;
import org.apache.brooklyn.core.relations.RelationshipTypes;
import org.apache.brooklyn.util.collections.MutableMap;

import com.google.common.annotations.Beta;

/** TODO these relations are not used yet; see issue where this is introduced and email thread */
@Beta
public class EntityRelations<T extends BrooklynObject> {

    /** {@link #MANAGER_OF} indicates that one entity is the manager of another entity,
     * in the internal Brooklyn management hierarchy model.
     * Apart from root {@link Application} entities, every deployed entity must have exactly one manager.  
     * The inverse relationship is {@link #MANAGED_BY}. */ 
    public static final RelationshipType<Entity,Entity> MANAGER_OF = RelationshipTypes.newRelationshipPair(
        "manager", "managers", Entity.class, "manager_of", 
        "managed child", "managed children", Entity.class, "managed_by");
    /** Inverse of {@link #MANAGER_OF}. */
    public static final RelationshipType<Entity,Entity> MANAGED_BY = MANAGER_OF.getInverseRelationshipType();
    
    /** {@link #GROUP_CONTAINS} indicates that one entity, typically a {@link Group},
     * has zero or more entities which are labelled as "members" of that group entity.
     * What membership means will depend on the group entity.
     * An entity may be a member of any number of other entities.  
     * The inverse relationship is {@link #IN_GROUP}. */ 
    public static final RelationshipType<Entity,Entity> GROUP_CONTAINS = RelationshipTypes.newRelationshipPair(
        "group", "groups", Entity.class, "group_contains",
        "member", "members", Entity.class, "in_group");
    /** Inverse of {@link #GROUP_CONTAINS}. */
    public static final RelationshipType<Entity,Entity> IN_GROUP = GROUP_CONTAINS.getInverseRelationshipType();

    /** {@link #HAS_TARGET} indicates that one entity directs to one or more other entities.
     * What this targeting relationship means depends on the targetter.
     * The inverse relationship is {@link #TARGETTED_BY}. */
    public static final RelationshipType<Entity,Entity> HAS_TARGET = RelationshipTypes.newRelationshipPair(
        "targetter", "targetters", Entity.class, "has_target", 
        "target", "targets", Entity.class, "targetted_by");
    /** Inverse of {@link #HAS_TARGET}. */
    public static final RelationshipType<Entity,Entity> TARGETTED_BY = HAS_TARGET.getInverseRelationshipType();

    /** {@link #ACTIVE_PARENT_OF} indicates that one entity is should be considered as the logical parent of another,
     * e.g. for presentation purposes to the end user.
     * Frequently this relationship coincides with a {@link #MANAGED_BY} relationship, 
     * but sometimes some managed children are there for purposes the designers consider less important,
     * and they can choose to suppress the {@link #ACTIVE_PARENT_OF} relationship 
     * so that the active children is a subset of the managed children.
     * <p>
     * One recommended consideration is whether the child should be shown in a default tree view.
     * Whilst a user can always fina a way to see all managed children, 
     * it may be the case that only some of those are of primary interest,
     * and it is to identify those that this relationship exists.
     * <p>
     * It is permitted that an entity be an {@link #ACTIVE_PARENT_OF} an entity for which it is not a manager,
     * but in most cases a different relationship type is more appropriate where there is not also a management relationship.
     * <p> 
     * The inverse relationship is {@link #ACTIVE_CHILD_OF},
     * and an entity should normally be an {@link #ACTIVE_CHILD_OF} zero or one entities. */
    public static final RelationshipType<Entity,Entity> ACTIVE_PARENT_OF = RelationshipTypes.newRelationshipPair(
        "parent", "parents", Entity.class, "parent_of_active", 
        "active child", "active children", Entity.class, "active_child_of");
    /** Inverse of {@link #ACTIVE_PARENT_OF}. */
    public static final RelationshipType<Entity,Entity> ACTIVE_CHILD_OF = ACTIVE_PARENT_OF.getInverseRelationshipType();
    
    /** {@link #HAS_POLICY} indicates that an entity has a policy associated to it.
     * The inverse relationship is {@link #POLICY_FOR}. */
    public static final RelationshipType<Entity,Policy> HAS_POLICY = RelationshipTypes.newRelationshipPair(
        "entity", "entities", Entity.class, "has_policy", 
        "policy", "policies", Policy.class, "policy_for");
    /** Inverse of {@link #HAS_POLICY}. */
    public static final RelationshipType<Policy,Entity> POLICY_FOR = HAS_POLICY.getInverseRelationshipType();

    // ----
    
    // TODO replace by relations stored in catalog when catalog supports arbitrary types
    private static Map<String,RelationshipType<? extends BrooklynObject, ? extends BrooklynObject>> KNOWN_RELATIONSHIPS = MutableMap.of();
    private static void addRelationship(RelationshipType<? extends BrooklynObject, ? extends BrooklynObject> r) {
        KNOWN_RELATIONSHIPS.put(r.getRelationshipTypeName(), r);
        if (r.getInverseRelationshipType()!=null) {
            KNOWN_RELATIONSHIPS.put(r.getInverseRelationshipType().getRelationshipTypeName(), r.getInverseRelationshipType());
        }
    }
    static {
        addRelationship(MANAGER_OF);
        addRelationship(GROUP_CONTAINS);
        addRelationship(HAS_TARGET);
        addRelationship(HAS_POLICY);
    }
    
    /** Find the typed Relationship instance for the given relationship name, if known;
     * behaviour is not guaranteed by the API if not known (hence the Beta marker),
     * it may fail fast or return null or create a poor-man's relationship instance. 
     */
    @Beta
    public static RelationshipType<? extends BrooklynObject, ? extends BrooklynObject> lookup(ManagementContext mgmt, String relationshipTypeName) {
        if (relationshipTypeName==null) return null;
        RelationshipType<? extends BrooklynObject, ? extends BrooklynObject> result = KNOWN_RELATIONSHIPS.get(relationshipTypeName);
        if (result!=null) return result;
        
        /* TODO ultimately we'd like to support arbitrary relationships via persistence and lookup against the catalog;
         * however for now, so that we can persist nicely (without catalog items for relationships) 
         * we are smart about the relationships defined here, and we return a poor-man's version for items elsewhere.
         * 
         * for now, a poor-man's relationship; if not in catalog ultimately we should fail. */
        return RelationshipTypes.newRelationshipOneway("source", "sources", BrooklynObject.class, relationshipTypeName, "target", "targets", BrooklynObject.class);
    }
    
    /** 
     * As {@link RelationSupport#getRelationshipTypes()} for the given object.  Callers can use either method.
     * See {@link AbstractBasicRelationSupport} for a discussion of why double dispatch is used and both methods are present.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static <T extends BrooklynObject> Set<RelationshipType<? super T,? extends BrooklynObject>> getRelationshipTypes(T source) {
        return (Set) ((BrooklynObjectInternal)source).relations().getLocalBackingStore().getRelationshipTypes();
    }

    /** 
     * As {@link RelationSupport#getRelations(RelationshipType)} for the given object.  Callers can use either method.
     * See {@link AbstractBasicRelationSupport} for a discussion of why double dispatch is used and both methods are present.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static <T extends BrooklynObject,U extends BrooklynObject> Set<U> getRelations(RelationshipType<? super T,U> relationship, T source) {
        return ((BrooklynObjectInternal)source).relations().getLocalBackingStore().getRelations((RelationshipType)relationship);
    }

    /** 
     * As {@link RelationSupport#add(RelationshipType, BrooklynObject)} for the given object.  Callers can use either method.
     * See {@link AbstractBasicRelationSupport} for a discussion of why double dispatch is used and both methods are present.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static <T extends BrooklynObject,U extends BrooklynObject> void add(T source, RelationshipType<? super T,? super U> relationship, U target) {
        ((BrooklynObjectInternal)source).relations().getLocalBackingStore().add((RelationshipType)relationship, target);
        if (relationship.getInverseRelationshipType()!=null)
            ((BrooklynObjectInternal)target).relations().getLocalBackingStore().add((RelationshipType)relationship.getInverseRelationshipType(), source);
    }

    /** 
     * As {@link RelationSupport#remove(RelationshipType, BrooklynObject)} for the given object.  Callers can use either method.
     * See {@link AbstractBasicRelationSupport} for a discussion of why double dispatch is used and both methods are present.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static <T extends BrooklynObject,U extends BrooklynObject> void remove(T source, RelationshipType<? super T,? super U> relationship, U target) {
        ((BrooklynObjectInternal)source).relations().getLocalBackingStore().remove((RelationshipType)relationship, target);
        if (relationship.getInverseRelationshipType()!=null)
            ((BrooklynObjectInternal)target).relations().getLocalBackingStore().remove((RelationshipType)relationship.getInverseRelationshipType(), source);
    }

}
