/*****************************************************************
 *   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
 *
 *    https://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.cayenne.dbsync.reverse.dbimport;

import org.apache.cayenne.dbsync.naming.NameBuilder;
import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * An ObjEntity that may be removed as a result of flattenning relationships.
 */
class ManyToManyCandidateEntity {

    private static final Logger LOG = LoggerFactory.getLogger(ManyToManyCandidateEntity.class);

    private final ObjEntity joinEntity;

    private final DbRelationship dbRel1;
    private final DbRelationship dbRel2;

    private final ObjEntity entity1;
    private final ObjEntity entity2;

    private final DbRelationship reverseRelationship1;
    private final DbRelationship reverseRelationship2;

    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
        joinEntity = entityValue;

        ObjRelationship rel1 = relationships.get(0);
        ObjRelationship rel2 = relationships.get(1);

        dbRel1 = rel1.getDbRelationships().get(0);
        dbRel2 = rel2.getDbRelationships().get(0);

        reverseRelationship1 = dbRel1.getReverseRelationship();
        reverseRelationship2 = dbRel2.getReverseRelationship();

        entity1 = rel1.getTargetEntity();
        entity2 = rel2.getTargetEntity();
    }

    /**
     * Method check - if current entity represent many to many temporary table
     *
     * @return true if current entity is represent many to many table; otherwise returns false
     */
    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
        ArrayList<ObjRelationship> relationships = new ArrayList<>(joinEntity.getRelationships());
        if (relationships.size() != 2 || (relationships.get(0).getDbRelationships().isEmpty() || relationships.get(1).getDbRelationships().isEmpty())) {
            return null;
        }

        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
        if (candidateEntity.isManyToMany()) {
            return candidateEntity;
        }

        return null;
    }

    private boolean isManyToMany() {
        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;

        return isNotHaveAttributes
                && reverseRelationship1 != null && reverseRelationship1.isToDependentPK()
                && reverseRelationship2 != null && reverseRelationship2.isToDependentPK()
                && entity1 != null && entity2 != null;
    }

    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
                                          DbRelationship rel1, DbRelationship rel2) {

        if (rel1.getSourceAttributes().isEmpty() && rel2.getTargetAttributes().isEmpty()) {
            LOG.warn("Wrong call ManyToManyCandidateEntity.addFlattenedRelationship(... , " + srcEntity.getName()
                    + ", " + dstEntity.getName() + ", ...)");

            return;
        }

        ObjRelationship newRelationship = new ObjRelationship();
        newRelationship.setName(NameBuilder
                .builder(newRelationship, srcEntity)
                .baseName(nameGenerator.relationshipName(rel1, rel2))
                .name());

        newRelationship.setSourceEntity(srcEntity);
        newRelationship.setTargetEntityName(dstEntity);

        newRelationship.addDbRelationship(rel1);
        newRelationship.addDbRelationship(rel2);

        srcEntity.addRelationship(newRelationship);
    }

    /**
     * Method make direct relationships between 2 entities and remove relationships to
     * many to many entity
     *
     * @param nameGenerator
     */
    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
        entity1.removeRelationship(reverseRelationship1.getName());
        entity2.removeRelationship(reverseRelationship2.getName());

        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
    }

}
